spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
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