SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
4 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
2.0 KB · 41 lines tsx
Raw Blame History
1import Link from "next/link";2import type { ReactNode } from "react";3import { cn } from "./cn";45export interface SegmentedOption<V extends string = string> {6  value: V;7  label: ReactNode;8  /** link version (server-friendly URL state) */9  href?: string;10  title?: string;11  disabled?: boolean;12}1314/**15 * Compact segmented control (view switchers, radius toggles, density modes). Server-safe when options carry `href`;16 * pass `onChange` from a client component for local state. 40 px tall under 640 px, 28–32 px from `sm`.17 */18export function SegmentedControl<V extends string>({ options, value, onChange, label, size = "sm", className, scroll = false }: { options: Array<SegmentedOption<V>>; value: V; onChange?: (v: V) => void; label: string; size?: "xs" | "sm" | "md"; className?: string; scroll?: boolean }) {19  const h = size === "md" ? "h-10 sm:h-9 px-3 text-[13px]" : size === "xs" ? "h-10 sm:h-7 px-2 text-[11.5px]" : "h-10 sm:h-8 px-2.5 text-[12.5px]";20  return (21    <div role={onChange ? "radiogroup" : "navigation"} aria-label={label} className={cn("inline-flex max-w-full items-stretch rounded-sm border border-hair-2 bg-surface p-[2px]", scroll && "overflow-x-auto no-scrollbar", className)}>22      {options.map((o) => {23        const active = o.value === value;24        const cls = cn("inline-flex shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-xs font-medium transition-colors", h, active ? "bg-ink text-paper" : "text-ink-2 hover:bg-surface-2 hover:text-ink", o.disabled && "pointer-events-none opacity-40");25        if (o.href && !onChange) {26          return (27            <Link key={o.value} href={o.href} scroll={scroll} aria-current={active ? "page" : undefined} title={o.title} className={cls}>28              {o.label}29            </Link>30          );31        }32        return (33          <button key={o.value} type="button" role="radio" aria-checked={active} title={o.title} disabled={o.disabled} onClick={() => onChange?.(o.value)} className={cls}>34            {o.label}35          </button>36        );37      })}38    </div>39  );40}41