"use client"; import * as React from "react"; import { cn } from "@/lib/utils"; export interface SegmentedOption { value: T; label: React.ReactNode; icon?: React.ReactNode; disabled?: boolean; count?: number; } /** * Segmented control (iOS-like). Keyboard: ←/→ move, Enter/Space select. Scrolls horizontally when it overflows. */ export function Segmented({ value, onChange, options, size = "md", className, ariaLabel, fill }: { value: T; onChange: (v: T) => void; options: SegmentedOption[]; size?: "sm" | "md" | "lg"; className?: string; ariaLabel?: string; fill?: boolean }) { const ref = React.useRef(null); const idx = options.findIndex((o) => o.value === value); const onKey = (e: React.KeyboardEvent) => { if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return; e.preventDefault(); const dir = e.key === "ArrowLeft" ? -1 : 1; let i = idx; for (let n = 0; n < options.length; n++) { i = (i + dir + options.length) % options.length; if (!options[i].disabled) break; } onChange(options[i].value); (ref.current?.children[i] as HTMLElement | undefined)?.focus(); }; const h = size === "sm" ? "h-8" : size === "lg" ? "h-11" : "h-9"; const item = size === "sm" ? "h-6 px-2.5 text-[12.5px]" : size === "lg" ? "h-9 px-4 text-[15px]" : "h-7 px-3 text-[13px]"; return (
{options.map((o) => { const active = o.value === value; return ( ); })}
); } /** Pill filter chips (multi or single select). Horizontal scroll on phones. */ export function ChipRow({ value, onChange, options, className, multiple }: { value: T | T[] | null; onChange: (v: T) => void; options: { value: T; label: React.ReactNode; icon?: React.ReactNode; count?: number }[]; className?: string; multiple?: boolean }) { const isActive = (v: T) => (Array.isArray(value) ? value.includes(v) : value === v); return (
{options.map((o) => { const active = isActive(o.value); return ( ); })}
); }