Breakpoint Display

PreviousNext

A breakpoint display component.

Docs
abuicomponent

Preview

Loading preview…
registry/abui/utils/breakpoint-display.tsx
"use client"

import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const breakpointDisplayVariants = cva(
  "pointer-events-none select-none bg-foreground text-background px-2 py-1 z-50 rounded-b",
  {
    variants: {
      position: {
        fixed: "fixed top-0 right-[50%] translate-x-1/2",
        relative: "relative",
        absolute: "absolute top-0 right-[50%] translate-x-1/2",
      },
    },
    defaultVariants: {
      position: "fixed",
    },
  },
)

// Canonical ordering of breakpoints from smallest to largest
// This defines the semantic order - actual pixel values are defined in Tailwind config
const BREAKPOINT_ORDER = ["xxs", "xs", "sm", "md", "lg", "xl", "2xl", "3xl"] as const

type BreakpointKey = (typeof BREAKPOINT_ORDER)[number]

// Map breakpoint keys to their display labels
const BREAKPOINT_LABELS: Record<BreakpointKey, string> = {
  xxs: "XXS",
  xs: "XS",
  sm: "SM",
  md: "MD",
  lg: "LG",
  xl: "XL",
  "2xl": "2XL",
  "3xl": "3XL",
}

// Default Tailwind breakpoints (standard v3/v4)
const DEFAULT_BREAKPOINTS: BreakpointKey[] = ["sm", "md", "lg", "xl", "2xl"]

interface BreakpointDisplayProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof breakpointDisplayVariants> {
  /**
   * Additional breakpoints to include beyond the default Tailwind breakpoints.
   * These will be automatically sorted by semantic order (xxs < xs < sm < md < lg < xl < 2xl < 3xl).
   * Actual pixel values are defined in your Tailwind configuration.
   * @example extraBreakpoints={["xxs", "xs", "3xl"]}
   */
  extraBreakpoints?: BreakpointKey[]
}

function BreakpointDisplay({ className, position, extraBreakpoints = [], ...props }: BreakpointDisplayProps) {
  // Combine default and extra breakpoints, remove duplicates, and sort by semantic order
  const activeBreakpoints = React.useMemo(() => {
    const combined = [...DEFAULT_BREAKPOINTS, ...extraBreakpoints]
    const unique = Array.from(new Set(combined))

    // Sort by their position in the canonical BREAKPOINT_ORDER array
    return unique.sort((a, b) => {
      return BREAKPOINT_ORDER.indexOf(a) - BREAKPOINT_ORDER.indexOf(b)
    })
  }, [extraBreakpoints])

  // Create a Set for O(1) lookup
  const activeSet = React.useMemo(() => new Set(activeBreakpoints), [activeBreakpoints])

  // Determine the first and last active breakpoints
  const firstBreakpoint = activeBreakpoints[0]
  const lastBreakpoint = activeBreakpoints[activeBreakpoints.length - 1]

  // Helper to check if a breakpoint is the next active one after current
  const getNextActiveBreakpoint = (current: BreakpointKey): BreakpointKey | null => {
    const currentIndex = BREAKPOINT_ORDER.indexOf(current)
    for (let i = currentIndex + 1; i < BREAKPOINT_ORDER.length; i++) {
      if (activeSet.has(BREAKPOINT_ORDER[i])) {
        return BREAKPOINT_ORDER[i]
      }
    }
    return null
  }

  return (
    <div className={cn(breakpointDisplayVariants({ position }), className)} {...props}>
      {/* Minimum - shows below the first breakpoint */}
      {firstBreakpoint === "xxs" && <p className="font-semibold block xxs:hidden">Minimum</p>}
      {firstBreakpoint === "xs" && <p className="font-semibold block xs:hidden">Minimum</p>}
      {firstBreakpoint === "sm" && <p className="font-semibold block sm:hidden">Minimum</p>}
      {firstBreakpoint === "md" && <p className="font-semibold block md:hidden">Minimum</p>}
      {firstBreakpoint === "lg" && <p className="font-semibold block lg:hidden">Minimum</p>}
      {firstBreakpoint === "xl" && <p className="font-semibold block xl:hidden">Minimum</p>}
      {firstBreakpoint === "2xl" && <p className="font-semibold block 2xl:hidden">Minimum</p>}
      {firstBreakpoint === "3xl" && <p className="font-semibold block 3xl:hidden">Minimum</p>}

      {/* XXS breakpoint */}
      {activeSet.has("xxs") && getNextActiveBreakpoint("xxs") === "xs" && (
        <p className="font-semibold hidden xxs:block xs:hidden">XXS</p>
      )}
      {activeSet.has("xxs") && getNextActiveBreakpoint("xxs") === "sm" && (
        <p className="font-semibold hidden xxs:block sm:hidden">XXS</p>
      )}
      {activeSet.has("xxs") && getNextActiveBreakpoint("xxs") === "md" && (
        <p className="font-semibold hidden xxs:block md:hidden">XXS</p>
      )}
      {activeSet.has("xxs") && getNextActiveBreakpoint("xxs") === "lg" && (
        <p className="font-semibold hidden xxs:block lg:hidden">XXS</p>
      )}
      {activeSet.has("xxs") && getNextActiveBreakpoint("xxs") === "xl" && (
        <p className="font-semibold hidden xxs:block xl:hidden">XXS</p>
      )}
      {activeSet.has("xxs") && getNextActiveBreakpoint("xxs") === "2xl" && (
        <p className="font-semibold hidden xxs:block 2xl:hidden">XXS</p>
      )}
      {activeSet.has("xxs") && getNextActiveBreakpoint("xxs") === "3xl" && (
        <p className="font-semibold hidden xxs:block 3xl:hidden">XXS</p>
      )}
      {activeSet.has("xxs") && lastBreakpoint === "xxs" && <p className="font-semibold hidden xxs:block">XXS</p>}

      {/* XS breakpoint */}
      {activeSet.has("xs") && getNextActiveBreakpoint("xs") === "sm" && (
        <p className="font-semibold hidden xs:block sm:hidden">XS</p>
      )}
      {activeSet.has("xs") && getNextActiveBreakpoint("xs") === "md" && (
        <p className="font-semibold hidden xs:block md:hidden">XS</p>
      )}
      {activeSet.has("xs") && getNextActiveBreakpoint("xs") === "lg" && (
        <p className="font-semibold hidden xs:block lg:hidden">XS</p>
      )}
      {activeSet.has("xs") && getNextActiveBreakpoint("xs") === "xl" && (
        <p className="font-semibold hidden xs:block xl:hidden">XS</p>
      )}
      {activeSet.has("xs") && getNextActiveBreakpoint("xs") === "2xl" && (
        <p className="font-semibold hidden xs:block 2xl:hidden">XS</p>
      )}
      {activeSet.has("xs") && getNextActiveBreakpoint("xs") === "3xl" && (
        <p className="font-semibold hidden xs:block 3xl:hidden">XS</p>
      )}
      {activeSet.has("xs") && lastBreakpoint === "xs" && <p className="font-semibold hidden xs:block">XS</p>}

      {/* SM breakpoint */}
      {activeSet.has("sm") && getNextActiveBreakpoint("sm") === "md" && (
        <p className="font-semibold hidden sm:block md:hidden">SM</p>
      )}
      {activeSet.has("sm") && getNextActiveBreakpoint("sm") === "lg" && (
        <p className="font-semibold hidden sm:block lg:hidden">SM</p>
      )}
      {activeSet.has("sm") && getNextActiveBreakpoint("sm") === "xl" && (
        <p className="font-semibold hidden sm:block xl:hidden">SM</p>
      )}
      {activeSet.has("sm") && getNextActiveBreakpoint("sm") === "2xl" && (
        <p className="font-semibold hidden sm:block 2xl:hidden">SM</p>
      )}
      {activeSet.has("sm") && getNextActiveBreakpoint("sm") === "3xl" && (
        <p className="font-semibold hidden sm:block 3xl:hidden">SM</p>
      )}
      {activeSet.has("sm") && lastBreakpoint === "sm" && <p className="font-semibold hidden sm:block">SM</p>}

      {/* MD breakpoint */}
      {activeSet.has("md") && getNextActiveBreakpoint("md") === "lg" && (
        <p className="font-semibold hidden md:block lg:hidden">MD</p>
      )}
      {activeSet.has("md") && getNextActiveBreakpoint("md") === "xl" && (
        <p className="font-semibold hidden md:block xl:hidden">MD</p>
      )}
      {activeSet.has("md") && getNextActiveBreakpoint("md") === "2xl" && (
        <p className="font-semibold hidden md:block 2xl:hidden">MD</p>
      )}
      {activeSet.has("md") && getNextActiveBreakpoint("md") === "3xl" && (
        <p className="font-semibold hidden md:block 3xl:hidden">MD</p>
      )}
      {activeSet.has("md") && lastBreakpoint === "md" && <p className="font-semibold hidden md:block">MD</p>}

      {/* LG breakpoint */}
      {activeSet.has("lg") && getNextActiveBreakpoint("lg") === "xl" && (
        <p className="font-semibold hidden lg:block xl:hidden">LG</p>
      )}
      {activeSet.has("lg") && getNextActiveBreakpoint("lg") === "2xl" && (
        <p className="font-semibold hidden lg:block 2xl:hidden">LG</p>
      )}
      {activeSet.has("lg") && getNextActiveBreakpoint("lg") === "3xl" && (
        <p className="font-semibold hidden lg:block 3xl:hidden">LG</p>
      )}
      {activeSet.has("lg") && lastBreakpoint === "lg" && <p className="font-semibold hidden lg:block">LG</p>}

      {/* XL breakpoint */}
      {activeSet.has("xl") && getNextActiveBreakpoint("xl") === "2xl" && (
        <p className="font-semibold hidden xl:block 2xl:hidden">XL</p>
      )}
      {activeSet.has("xl") && getNextActiveBreakpoint("xl") === "3xl" && (
        <p className="font-semibold hidden xl:block 3xl:hidden">XL</p>
      )}
      {activeSet.has("xl") && lastBreakpoint === "xl" && <p className="font-semibold hidden xl:block">XL</p>}

      {/* 2XL breakpoint */}
      {activeSet.has("2xl") && getNextActiveBreakpoint("2xl") === "3xl" && (
        <p className="font-semibold hidden 2xl:block 3xl:hidden">2XL</p>
      )}
      {activeSet.has("2xl") && lastBreakpoint === "2xl" && <p className="font-semibold hidden 2xl:block">2XL</p>}

      {/* 3XL breakpoint */}
      {activeSet.has("3xl") && lastBreakpoint === "3xl" && <p className="font-semibold hidden 3xl:block">3XL</p>}
    </div>
  )
}

export { BreakpointDisplay, breakpointDisplayVariants }
export type { BreakpointDisplayProps, BreakpointKey }

Installation

npx shadcn@latest add @abui/breakpoint-display

Usage

import { BreakpointDisplay } from "@/components/breakpoint-display"
<BreakpointDisplay />