@devalok/shilp-sutra 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/AGENTS.md +1 -1
  2. package/MIGRATION.md +12 -0
  3. package/dist/tokens/semantic.css +20 -1
  4. package/dist/ui/button-group.js +2 -0
  5. package/dist/ui/button-group.js.map +1 -1
  6. package/dist/ui/button-processing.d.ts.map +1 -1
  7. package/dist/ui/button-processing.js +1 -0
  8. package/dist/ui/button-processing.js.map +1 -1
  9. package/dist/ui/button.d.ts +3 -3
  10. package/dist/ui/button.d.ts.map +1 -1
  11. package/dist/ui/button.js +26 -0
  12. package/dist/ui/button.js.map +1 -1
  13. package/dist/ui/card.d.ts +5 -4
  14. package/dist/ui/card.d.ts.map +1 -1
  15. package/dist/ui/card.js +1 -1
  16. package/dist/ui/card.js.map +1 -1
  17. package/dist/ui/color-input.d.ts.map +1 -1
  18. package/dist/ui/color-input.js +82 -82
  19. package/dist/ui/color-input.js.map +1 -1
  20. package/dist/ui/icon.d.ts +2 -0
  21. package/dist/ui/icon.d.ts.map +1 -1
  22. package/dist/ui/icon.js +31 -26
  23. package/dist/ui/icon.js.map +1 -1
  24. package/dist/ui/search-input.d.ts.map +1 -1
  25. package/dist/ui/search-input.js +1 -0
  26. package/dist/ui/search-input.js.map +1 -1
  27. package/dist/ui/segmented-control.d.ts +28 -6
  28. package/dist/ui/segmented-control.d.ts.map +1 -1
  29. package/dist/ui/segmented-control.js +61 -43
  30. package/dist/ui/segmented-control.js.map +1 -1
  31. package/dist/ui/split-button.d.ts.map +1 -1
  32. package/dist/ui/split-button.js +7 -0
  33. package/dist/ui/split-button.js.map +1 -1
  34. package/dist/ui/stat-card.d.ts +3 -3
  35. package/dist/ui/stat-card.d.ts.map +1 -1
  36. package/dist/ui/stat-card.js.map +1 -1
  37. package/docs/components/ui/button.md +2 -2
  38. package/docs/components/ui/segmented-control.md +31 -11
  39. package/docs/components/ui/stat-card.md +1 -1
  40. package/docs/recipes/install-remix.md +3 -3
  41. package/docs/recipes/install-vite.md +3 -3
  42. package/docs/recipes/server-components.md +2 -2
  43. package/llms.txt +1 -1
  44. package/make-kit/components/card.md +4 -4
  45. package/make-kit/foundations/color.md +20 -0
  46. package/make-kit/foundations/dark-mode.md +6 -9
  47. package/make-kit/foundations/icons.md +3 -3
  48. package/make-kit/setup.md +4 -4
  49. package/mcp-manifest.json +62 -11
  50. package/package.json +1 -1
  51. package/scripts/welcome.mjs +59 -1
  52. package/skill/SKILL.md +1 -1
  53. package/skill/references/components.md +1 -1
  54. package/skill/references/server-components.md +2 -2
  55. package/skill/references/setup-remix.md +3 -3
  56. package/skill/references/setup-vite.md +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"color-input.js","names":[],"sources":["../../src/ui/color-input.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { HexColorPicker } from 'react-colorful'\n\nimport { useFormField } from './form'\nimport { durations,springs } from './lib/motion'\nimport { cn } from './lib/utils'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from './popover'\n\n// ── Color conversion helpers ──\n\nfunction hexToRgb(hex: string): { r: number; g: number; b: number } | null {\n const m = /^#?([0-9a-f]{6})$/i.exec(hex)\n if (!m) return null\n const n = parseInt(m[1], 16)\n return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 }\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n return '#' + [r, g, b].map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('')\n}\n\nfunction hexToHsl(hex: string): { h: number; s: number; l: number } | null {\n const rgb = hexToRgb(hex)\n if (!rgb) return null\n const r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255\n const max = Math.max(r, g, b), min = Math.min(r, g, b)\n const l = (max + min) / 2\n if (max === min) return { h: 0, s: 0, l: Math.round(l * 100) }\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max - min)\n let h = 0\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6\n else if (max === g) h = ((b - r) / d + 2) / 6\n else h = ((r - g) / d + 4) / 6\n return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }\n}\n\nfunction hslToHex(h: number, s: number, l: number): string {\n const sn = s / 100, ln = l / 100\n const a = sn * Math.min(ln, 1 - ln)\n const f = (n: number) => {\n const k = (n + h / 30) % 12\n const c = ln - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)\n return Math.round(255 * c)\n }\n return rgbToHex(f(0), f(8), f(4))\n}\n\n// ── Named color presets ──\n// Brand spectrum derived from our OKLCH scales (step-9), spanning the wheel —\n// intentional and on-brand, NOT the raw framework palette. Led by red, not indigo.\n\nconst NAMED_PRESETS: { hex: string; label: string }[] = [\n { hex: '#C53637', label: 'Red' },\n { hex: '#BE5A0A', label: 'Orange' },\n { hex: '#DF911A', label: 'Amber' },\n { hex: '#308639', label: 'Green' },\n { hex: '#118659', label: 'Emerald' },\n { hex: '#11846E', label: 'Teal' },\n { hex: '#028A9B', label: 'Cyan' },\n { hex: '#1479B0', label: 'Blue' },\n { hex: '#7D5FAD', label: 'Purple' },\n { hex: '#C22D6D', label: 'Pink' },\n]\n\n// ── Contrast helper ──\n\nfunction isLightColor(hex: string): boolean {\n const rgb = hexToRgb(hex)\n if (!rgb) return false\n // Relative luminance (sRGB)\n const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((c) => {\n const s = c / 255\n return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4)\n })\n return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.4\n}\n\n// ── Format mode type ──\n\ntype ColorFormat = 'hex' | 'rgb' | 'hsl'\n\n// ── Props ──\n\nexport interface ColorInputProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Current color value (hex string, e.g. \"#d33163\") */\n value?: string\n /** Called when the color changes */\n onChange?: (value: string) => void\n /** Preset color swatches. Defaults to 10 named colors. Pass `false` to hide. */\n presets?: { hex: string; label: string }[] | string[] | false\n /** Whether the input is disabled */\n disabled?: boolean\n /** Show the interactive color picker. Default: true. */\n showPicker?: boolean\n /** Default format for the input fields. Default: 'hex'. */\n defaultFormat?: ColorFormat\n /** Popover alignment. Default: 'start'. */\n align?: 'start' | 'center' | 'end'\n /**\n * Trigger style variant.\n * - `default`: Swatch bleeds to left edge + hex text\n * - `inline`: Entire trigger is the selected color with hex text overlaid\n */\n variant?: 'default' | 'inline'\n}\n\n// ── Small format input ──\n\nfunction FormatInput({\n label,\n value,\n onChange,\n onBlur,\n disabled,\n maxLength = 3,\n prefix,\n className,\n id,\n}: {\n label: string\n value: string\n onChange: (v: string) => void\n onBlur?: () => void\n disabled?: boolean\n maxLength?: number\n prefix?: string\n className?: string\n id: string\n}) {\n return (\n <div className={cn('flex flex-col gap-ds-01', className)}>\n <label htmlFor={id} className=\"text-label-xs font-medium uppercase tracking-wider text-surface-fg-muted\">\n {label}\n </label>\n <div className=\"flex items-center\">\n {prefix && (\n <span className=\"text-body-sm text-surface-fg-muted\">{prefix}</span>\n )}\n <input\n id={id}\n type=\"text\"\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n onBlur={onBlur}\n maxLength={maxLength}\n className={cn(\n 'h-ds-xs-plus w-full rounded-control-inner border border-surface-border bg-surface-overlay px-ds-02 font-mono text-body-sm text-surface-fg transition-colors',\n 'focus:border-accent-7 focus:outline-hidden focus:ring-1 focus:ring-accent-9',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n />\n </div>\n </div>\n )\n}\n\n// ── Main component ──\n\nconst ColorInput = React.forwardRef<HTMLDivElement, ColorInputProps>(\n ({\n value = '#000000',\n onChange,\n presets,\n disabled = false,\n showPicker = true,\n defaultFormat = 'hex',\n align = 'start',\n variant = 'default',\n className,\n id: externalId,\n ...props\n }, ref) => {\n const [format, setFormat] = React.useState<ColorFormat>(defaultFormat)\n const [open, setOpen] = React.useState(false)\n const instanceId = React.useId()\n const fieldCtx = useFormField()\n // Explicit id wins; otherwise adopt FormField's inputId so <Label htmlFor> resolves\n // onto the (labelable) trigger button. The root is a <div>, which can't be a label target.\n const triggerId = externalId ?? fieldCtx.inputId\n\n // Internal color state — syncs with prop, allows uncontrolled use\n const [internalColor, setInternalColor] = React.useState(value)\n React.useEffect(() => { setInternalColor(value) }, [value])\n\n // Track color when popover opened (for reset) + undo history\n const [openColor, setOpenColor] = React.useState(value)\n const [undoStack, setUndoStack] = React.useState<string[]>([])\n // Track whether current change is from continuous drag (skip undo push)\n const isDragging = React.useRef(false)\n\n const handleOpenChange = (isOpen: boolean) => {\n if (disabled) return\n if (isOpen) {\n setOpenColor(internalColor)\n setUndoStack([])\n }\n setOpen(isOpen)\n }\n\n // Discrete change (preset click, field commit) — pushes to undo\n const handleDiscreteChange = (newValue: string) => {\n if (disabled) return\n const normalized = newValue.startsWith('#') ? newValue : `#${newValue}`\n const hex = normalized.toLowerCase()\n setUndoStack((prev) => {\n if (prev[prev.length - 1] === internalColor) return prev\n return [...prev.slice(-19), internalColor]\n })\n setInternalColor(hex)\n onChange?.(hex)\n }\n\n // Continuous change (picker drag) — updates color but only pushes undo on drag start\n const handleChange = (newValue: string) => {\n if (disabled) return\n const normalized = newValue.startsWith('#') ? newValue : `#${newValue}`\n const hex = normalized.toLowerCase()\n if (!isDragging.current) {\n // First change in a drag sequence — push current color to undo\n isDragging.current = true\n setUndoStack((prev) => {\n if (prev[prev.length - 1] === internalColor) return prev\n return [...prev.slice(-19), internalColor]\n })\n }\n setInternalColor(hex)\n onChange?.(hex)\n }\n\n // Called when picker drag ends\n const handlePickerChangeComplete = () => {\n isDragging.current = false\n }\n\n const handleUndo = () => {\n if (undoStack.length === 0) return\n const prev = undoStack[undoStack.length - 1]\n setUndoStack((s) => s.slice(0, -1))\n setInternalColor(prev)\n onChange?.(prev)\n }\n\n const handleReset = () => {\n setInternalColor(openColor)\n onChange?.(openColor)\n setUndoStack([])\n }\n\n // Resolve presets\n const resolvedPresets = presets === false\n ? []\n : presets\n ? presets.map((p) =>\n typeof p === 'string' ? { hex: p, label: p } : p\n )\n : NAMED_PRESETS\n\n // Parsed color values — use internal state\n const rgb = hexToRgb(internalColor)\n const hsl = hexToHsl(internalColor)\n\n // RGB field handlers (clamped 0-255)\n const handleRgbChange = (channel: 'r' | 'g' | 'b', v: string) => {\n if (!rgb) return\n const num = parseInt(v, 10)\n if (isNaN(num)) return\n const clamped = Math.max(0, Math.min(255, num))\n handleDiscreteChange(rgbToHex(\n channel === 'r' ? clamped : rgb.r,\n channel === 'g' ? clamped : rgb.g,\n channel === 'b' ? clamped : rgb.b,\n ))\n }\n\n // HSL field handlers (H: 0-360, S/L: 0-100)\n const handleHslChange = (channel: 'h' | 's' | 'l', v: string) => {\n if (!hsl) return\n const num = parseInt(v, 10)\n if (isNaN(num)) return\n const max = channel === 'h' ? 360 : 100\n const clamped = Math.max(0, Math.min(max, num))\n handleDiscreteChange(hslToHex(\n channel === 'h' ? clamped : hsl.h,\n channel === 's' ? clamped : hsl.s,\n channel === 'l' ? clamped : hsl.l,\n ))\n }\n\n const formats: ColorFormat[] = ['hex', 'rgb', 'hsl']\n\n return (\n <div ref={ref} className={cn('inline-flex flex-col', className)} {...props}>\n <Popover open={open} onOpenChange={handleOpenChange}>\n <PopoverTrigger asChild>\n {variant === 'inline' ? (\n <motion.button\n type=\"button\"\n disabled={disabled}\n className={cn(\n 'group flex items-center justify-center rounded-control px-ds-04 py-ds-02 font-mono text-body-sm font-medium',\n 'focus:outline-hidden focus:ring-2 focus:ring-accent-9 focus:ring-offset-2 focus:ring-offset-surface-base',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n animate={{\n backgroundColor: internalColor,\n color: isLightColor(internalColor) ? 'rgba(0,0,0,0.8)' : 'rgba(255,255,255,0.95)',\n }}\n whileHover={{ y: -1, boxShadow: '0 4px 12px rgba(0,0,0,0.12)' }}\n whileTap={{ scale: 0.97 }}\n transition={springs.smooth}\n id={triggerId}\n aria-describedby={fieldCtx.helperTextId}\n aria-invalid={fieldCtx.state === 'error' || undefined}\n // Inside a FormField, let the visible <Label> name the trigger; else describe it.\n aria-label={fieldCtx.inputId ? undefined : `Color picker: ${internalColor}`}\n >\n {internalColor.toUpperCase()}\n </motion.button>\n ) : (\n <motion.button\n type=\"button\"\n disabled={disabled}\n className={cn(\n 'group relative flex items-center overflow-hidden rounded-control border border-surface-border-strong',\n 'hover:border-accent-7 focus:border-accent-7 focus:outline-hidden focus:ring-1 focus:ring-accent-9',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n whileHover={{ scale: 1.02 }}\n whileTap={{ scale: 0.98 }}\n transition={springs.snappy}\n id={triggerId}\n aria-describedby={fieldCtx.helperTextId}\n aria-invalid={fieldCtx.state === 'error' || undefined}\n // Inside a FormField, let the visible <Label> name the trigger; else describe it.\n aria-label={fieldCtx.inputId ? undefined : `Color picker: ${internalColor}`}\n >\n {/* Gradient background: color → surface */}\n <motion.span\n className=\"absolute inset-0\"\n animate={{\n background: `linear-gradient(to right, ${internalColor} 0%, ${internalColor} 35%, transparent 70%)`,\n }}\n /* Between durations.moderate02 (0.24) and durations.slow01 (0.4) — gradient lerp feel */\n transition={{ duration: 0.3 }}\n />\n <span className=\"absolute inset-0 bg-surface-overlay/60\" style={{\n maskImage: 'linear-gradient(to right, transparent 0%, black 40%)',\n WebkitMaskImage: 'linear-gradient(to right, transparent 0%, black 40%)',\n }} />\n {/* Hex value */}\n <span className=\"relative z-10 py-ds-02 pl-6 pr-ds-03 font-mono text-body-sm text-surface-fg\">\n {internalColor.toUpperCase()}\n </span>\n </motion.button>\n )}\n </PopoverTrigger>\n\n <PopoverContent\n role=\"dialog\"\n aria-label=\"Color picker\"\n align={align}\n sideOffset={8}\n className=\"w-[272px] rounded-overlay-lg bg-surface-overlay p-0 shadow-floating\"\n >\n <div className=\"flex flex-col\">\n {/* Interactive picker */}\n {showPicker && (\n <div className=\"p-ds-04 pb-ds-03\" onPointerUp={handlePickerChangeComplete} onPointerLeave={handlePickerChangeComplete}>\n <HexColorPicker\n color={internalColor}\n onChange={handleChange}\n className=\"w-full!\"\n style={{ height: 160 }}\n />\n </div>\n )}\n\n {/* Format inputs */}\n <div className=\"border-t border-surface-border px-ds-04 py-ds-03\">\n {/* Format switcher */}\n <div className=\"mb-ds-03 flex items-center gap-ds-01\">\n {formats.map((f) => (\n <button\n key={f}\n type=\"button\"\n onClick={() => setFormat(f)}\n className={cn(\n 'relative min-h-6 rounded-control-inner px-ds-02 py-px text-label-xs font-semibold uppercase tracking-wider transition-colors',\n format === f\n ? 'text-accent-11'\n : 'text-surface-fg-muted hover:text-surface-fg',\n )}\n >\n {format === f && (\n <motion.span\n layoutId={`color-input-format-pill-${instanceId}`}\n className=\"absolute inset-0 rounded-control-inner bg-accent-3\"\n transition={springs.snappy}\n />\n )}\n <span className=\"relative z-10\">{f}</span>\n </button>\n ))}\n </div>\n\n {/* Format fields — animated swap */}\n <AnimatePresence mode=\"wait\">\n {format === 'hex' && (\n <motion.div\n key=\"hex\"\n initial={{ opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: durations.moderate01 }}\n className=\"flex gap-ds-02\"\n >\n <FormatInput\n id={`${instanceId}-hex`}\n label=\"Hex\"\n value={internalColor.replace('#', '').toUpperCase()}\n onChange={(v) => {\n const clean = v.replace(/[^0-9a-fA-F]/g, '').slice(0, 6)\n if (clean.length === 6) handleDiscreteChange(`#${clean}`)\n }}\n onBlur={() => {\n // Revert display to current color if input is incomplete\n const display = internalColor.replace('#', '').toUpperCase()\n if (display.length !== 6) setInternalColor(internalColor)\n }}\n disabled={disabled}\n maxLength={6}\n prefix=\"#\"\n className=\"flex-1\"\n />\n </motion.div>\n )}\n\n {format === 'rgb' && rgb && (\n <motion.div\n key=\"rgb\"\n initial={{ opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: durations.moderate01 }}\n className=\"flex gap-ds-02\"\n >\n <FormatInput id={`${instanceId}-r`} label=\"R\" value={String(rgb.r)} onChange={(v) => handleRgbChange('r', v)} disabled={disabled} className=\"flex-1\" />\n <FormatInput id={`${instanceId}-g`} label=\"G\" value={String(rgb.g)} onChange={(v) => handleRgbChange('g', v)} disabled={disabled} className=\"flex-1\" />\n <FormatInput id={`${instanceId}-b`} label=\"B\" value={String(rgb.b)} onChange={(v) => handleRgbChange('b', v)} disabled={disabled} className=\"flex-1\" />\n </motion.div>\n )}\n\n {format === 'hsl' && hsl && (\n <motion.div\n key=\"hsl\"\n initial={{ opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: durations.moderate01 }}\n className=\"flex gap-ds-02\"\n >\n <FormatInput id={`${instanceId}-h`} label=\"H\" value={String(hsl.h)} onChange={(v) => handleHslChange('h', v)} disabled={disabled} className=\"flex-1\" />\n <FormatInput id={`${instanceId}-s`} label=\"S\" value={String(hsl.s)} onChange={(v) => handleHslChange('s', v)} disabled={disabled} className=\"flex-1\" />\n <FormatInput id={`${instanceId}-l`} label=\"L\" value={String(hsl.l)} onChange={(v) => handleHslChange('l', v)} disabled={disabled} className=\"flex-1\" />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n\n {/* Preset swatches */}\n {resolvedPresets.length > 0 && (\n <div className=\"border-t border-surface-border px-ds-04 py-ds-03\">\n <div className=\"flex flex-wrap gap-ds-02\">\n {resolvedPresets.map((preset, i) => {\n const isSelected = internalColor.toLowerCase() === preset.hex.toLowerCase()\n return (\n <motion.button\n key={preset.hex}\n type=\"button\"\n disabled={disabled}\n onClick={() => handleDiscreteChange(preset.hex)}\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: isSelected ? 1.15 : 1 }}\n whileHover={{ scale: isSelected ? 1.15 : 1.1 }}\n whileTap={{ scale: 0.9 }}\n transition={{ ...springs.bouncy, delay: i * 0.02 }}\n className={cn(\n 'h-6 w-6 rounded-control-inner border',\n isSelected\n ? 'border-accent-7 ring-2 ring-accent-9/30'\n : 'border-surface-border hover:border-surface-border-strong',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n style={{ backgroundColor: preset.hex }}\n title={preset.label}\n aria-label={`${preset.label}: ${preset.hex}`}\n />\n )\n })}\n </div>\n </div>\n )}\n\n {/* Reset / Undo footer */}\n {(undoStack.length > 0 || internalColor !== openColor) && (\n <motion.div\n initial={{ opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: 'auto' }}\n exit={{ opacity: 0, height: 0 }}\n className=\"flex items-center gap-ds-02 border-t border-surface-border px-ds-04 py-ds-02\"\n >\n {/* Original color preview */}\n <span\n className=\"h-4 w-4 shrink-0 rounded-pill border border-surface-border\"\n style={{ backgroundColor: openColor }}\n title={`Original: ${openColor}`}\n />\n <span className=\"text-caption text-surface-fg-muted\">\n {openColor.toUpperCase()}\n </span>\n <span className=\"flex-1\" />\n {undoStack.length > 0 && (\n <button\n type=\"button\"\n onClick={handleUndo}\n className=\"min-h-6 rounded-control-inner px-ds-02 py-px text-caption font-medium text-surface-fg-muted transition-colors hover:text-surface-fg\"\n >\n Undo\n </button>\n )}\n {internalColor !== openColor && (\n <button\n type=\"button\"\n onClick={handleReset}\n className=\"min-h-6 rounded-control-inner px-ds-02 py-px text-caption font-medium text-surface-fg-muted transition-colors hover:text-error-11\"\n >\n Reset\n </button>\n )}\n </motion.div>\n )}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n )\n },\n)\nColorInput.displayName = 'ColorInput'\n\nexport { ColorInput }\n"],"mappings":"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"}
1
+ {"version":3,"file":"color-input.js","names":[],"sources":["../../src/ui/color-input.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence,motion } from 'framer-motion'\nimport * as React from 'react'\nimport { HexColorPicker } from 'react-colorful'\n\nimport { useFormField } from './form'\nimport { durations,springs } from './lib/motion'\nimport { cn } from './lib/utils'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from './popover'\n\n// ── Color conversion helpers ──\n\nfunction hexToRgb(hex: string): { r: number; g: number; b: number } | null {\n const m = /^#?([0-9a-f]{6})$/i.exec(hex)\n if (!m) return null\n const n = parseInt(m[1], 16)\n return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 }\n}\n\nfunction rgbToHex(r: number, g: number, b: number): string {\n return '#' + [r, g, b].map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('')\n}\n\nfunction hexToHsl(hex: string): { h: number; s: number; l: number } | null {\n const rgb = hexToRgb(hex)\n if (!rgb) return null\n const r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255\n const max = Math.max(r, g, b), min = Math.min(r, g, b)\n const l = (max + min) / 2\n if (max === min) return { h: 0, s: 0, l: Math.round(l * 100) }\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max - min)\n let h = 0\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6\n else if (max === g) h = ((b - r) / d + 2) / 6\n else h = ((r - g) / d + 4) / 6\n return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }\n}\n\nfunction hslToHex(h: number, s: number, l: number): string {\n const sn = s / 100, ln = l / 100\n const a = sn * Math.min(ln, 1 - ln)\n const f = (n: number) => {\n const k = (n + h / 30) % 12\n const c = ln - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)\n return Math.round(255 * c)\n }\n return rgbToHex(f(0), f(8), f(4))\n}\n\n// ── Named color presets ──\n// Brand spectrum derived from our OKLCH scales (step-9), spanning the wheel —\n// intentional and on-brand, NOT the raw framework palette. Led by red, not indigo.\n\nconst NAMED_PRESETS: { hex: string; label: string }[] = [\n { hex: '#C53637', label: 'Red' },\n { hex: '#BE5A0A', label: 'Orange' },\n { hex: '#DF911A', label: 'Amber' },\n { hex: '#308639', label: 'Green' },\n { hex: '#118659', label: 'Emerald' },\n { hex: '#11846E', label: 'Teal' },\n { hex: '#028A9B', label: 'Cyan' },\n { hex: '#1479B0', label: 'Blue' },\n { hex: '#7D5FAD', label: 'Purple' },\n { hex: '#C22D6D', label: 'Pink' },\n]\n\n// ── Contrast helper ──\n\nfunction isLightColor(hex: string): boolean {\n const rgb = hexToRgb(hex)\n if (!rgb) return false\n // Relative luminance (sRGB)\n const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((c) => {\n const s = c / 255\n return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4)\n })\n return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.4\n}\n\n// ── Format mode type ──\n\ntype ColorFormat = 'hex' | 'rgb' | 'hsl'\n\n// ── Props ──\n\nexport interface ColorInputProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Current color value (hex string, e.g. \"#d33163\") */\n value?: string\n /** Called when the color changes */\n onChange?: (value: string) => void\n /** Preset color swatches. Defaults to 10 named colors. Pass `false` to hide. */\n presets?: { hex: string; label: string }[] | string[] | false\n /** Whether the input is disabled */\n disabled?: boolean\n /** Show the interactive color picker. Default: true. */\n showPicker?: boolean\n /** Default format for the input fields. Default: 'hex'. */\n defaultFormat?: ColorFormat\n /** Popover alignment. Default: 'start'. */\n align?: 'start' | 'center' | 'end'\n /**\n * Trigger style variant.\n * - `default`: Swatch bleeds to left edge + hex text\n * - `inline`: Entire trigger is the selected color with hex text overlaid\n */\n variant?: 'default' | 'inline'\n}\n\n// ── Small format input ──\n\nfunction FormatInput({\n label,\n value,\n onChange,\n onBlur,\n disabled,\n maxLength = 3,\n prefix,\n className,\n id,\n}: {\n label: string\n value: string\n onChange: (v: string) => void\n onBlur?: () => void\n disabled?: boolean\n maxLength?: number\n prefix?: string\n className?: string\n id: string\n}) {\n return (\n <div className={cn('flex flex-col gap-ds-01', className)}>\n <label htmlFor={id} className=\"text-label-xs font-medium uppercase tracking-wider text-surface-fg-muted\">\n {label}\n </label>\n <div className=\"flex items-center\">\n {prefix && (\n <span className=\"text-body-sm text-surface-fg-muted\">{prefix}</span>\n )}\n <input\n id={id}\n type=\"text\"\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n onBlur={onBlur}\n maxLength={maxLength}\n className={cn(\n 'h-ds-xs-plus w-full rounded-control-inner border border-surface-border bg-surface-overlay px-ds-02 font-mono text-body-sm text-surface-fg transition-colors',\n 'focus:border-accent-7 focus:outline-hidden focus:ring-1 focus:ring-accent-9',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n />\n </div>\n </div>\n )\n}\n\n// ── Main component ──\n\nconst ColorInput = React.forwardRef<HTMLDivElement, ColorInputProps>(\n ({\n value = '#000000',\n onChange,\n presets,\n disabled = false,\n showPicker = true,\n defaultFormat = 'hex',\n align = 'start',\n variant = 'default',\n className,\n id: externalId,\n ...props\n }, ref) => {\n const [format, setFormat] = React.useState<ColorFormat>(defaultFormat)\n const [open, setOpen] = React.useState(false)\n const instanceId = React.useId()\n const fieldCtx = useFormField()\n // Explicit id wins; otherwise adopt FormField's inputId so <Label htmlFor> resolves\n // onto the (labelable) trigger button. The root is a <div>, which can't be a label target.\n const triggerId = externalId ?? fieldCtx.inputId\n\n // Internal color state — syncs with prop, allows uncontrolled use\n const [internalColor, setInternalColor] = React.useState(value)\n // Draft state for the hex field so in-progress (<6 char) typing isn't clobbered\n // by re-renders off internalColor. null = show the committed color.\n const [hexDraft, setHexDraft] = React.useState<string | null>(null)\n React.useEffect(() => { setInternalColor(value); setHexDraft(null) }, [value])\n\n // Track color when popover opened (for reset) + undo history\n const [openColor, setOpenColor] = React.useState(value)\n const [undoStack, setUndoStack] = React.useState<string[]>([])\n // Track whether current change is from continuous drag (skip undo push)\n const isDragging = React.useRef(false)\n\n const handleOpenChange = (isOpen: boolean) => {\n if (disabled) return\n if (isOpen) {\n setOpenColor(internalColor)\n setUndoStack([])\n }\n setHexDraft(null)\n setOpen(isOpen)\n }\n\n // Discrete change (preset click, field commit) — pushes to undo\n const handleDiscreteChange = (newValue: string) => {\n if (disabled) return\n const normalized = newValue.startsWith('#') ? newValue : `#${newValue}`\n const hex = normalized.toLowerCase()\n setUndoStack((prev) => {\n if (prev[prev.length - 1] === internalColor) return prev\n return [...prev.slice(-19), internalColor]\n })\n setInternalColor(hex)\n setHexDraft(null)\n onChange?.(hex)\n }\n\n // Continuous change (picker drag) — updates color but only pushes undo on drag start\n const handleChange = (newValue: string) => {\n if (disabled) return\n const normalized = newValue.startsWith('#') ? newValue : `#${newValue}`\n const hex = normalized.toLowerCase()\n if (!isDragging.current) {\n // First change in a drag sequence — push current color to undo\n isDragging.current = true\n setUndoStack((prev) => {\n if (prev[prev.length - 1] === internalColor) return prev\n return [...prev.slice(-19), internalColor]\n })\n }\n setInternalColor(hex)\n onChange?.(hex)\n }\n\n // Called when picker drag ends\n const handlePickerChangeComplete = () => {\n isDragging.current = false\n }\n\n const handleUndo = () => {\n if (undoStack.length === 0) return\n const prev = undoStack[undoStack.length - 1]\n setUndoStack((s) => s.slice(0, -1))\n setInternalColor(prev)\n onChange?.(prev)\n }\n\n const handleReset = () => {\n setInternalColor(openColor)\n onChange?.(openColor)\n setUndoStack([])\n }\n\n // Resolve presets\n const resolvedPresets = presets === false\n ? []\n : presets\n ? presets.map((p) =>\n typeof p === 'string' ? { hex: p, label: p } : p\n )\n : NAMED_PRESETS\n\n // Parsed color values — use internal state\n const rgb = hexToRgb(internalColor)\n const hsl = hexToHsl(internalColor)\n\n // RGB field handlers (clamped 0-255)\n const handleRgbChange = (channel: 'r' | 'g' | 'b', v: string) => {\n if (!rgb) return\n const num = parseInt(v, 10)\n if (isNaN(num)) return\n const clamped = Math.max(0, Math.min(255, num))\n handleDiscreteChange(rgbToHex(\n channel === 'r' ? clamped : rgb.r,\n channel === 'g' ? clamped : rgb.g,\n channel === 'b' ? clamped : rgb.b,\n ))\n }\n\n // HSL field handlers (H: 0-360, S/L: 0-100)\n const handleHslChange = (channel: 'h' | 's' | 'l', v: string) => {\n if (!hsl) return\n const num = parseInt(v, 10)\n if (isNaN(num)) return\n const max = channel === 'h' ? 360 : 100\n const clamped = Math.max(0, Math.min(max, num))\n handleDiscreteChange(hslToHex(\n channel === 'h' ? clamped : hsl.h,\n channel === 's' ? clamped : hsl.s,\n channel === 'l' ? clamped : hsl.l,\n ))\n }\n\n const formats: ColorFormat[] = ['hex', 'rgb', 'hsl']\n\n return (\n <div ref={ref} className={cn('inline-flex flex-col', className)} {...props}>\n <Popover open={open} onOpenChange={handleOpenChange}>\n <PopoverTrigger asChild>\n {variant === 'inline' ? (\n <motion.button\n type=\"button\"\n disabled={disabled}\n className={cn(\n 'group flex items-center justify-center rounded-control px-ds-04 py-ds-02 font-mono text-body-sm font-medium',\n 'focus:outline-hidden focus:ring-2 focus:ring-accent-9 focus:ring-offset-2 focus:ring-offset-surface-base',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n animate={{\n backgroundColor: internalColor,\n color: isLightColor(internalColor) ? 'rgba(0,0,0,0.8)' : 'rgba(255,255,255,0.95)',\n }}\n whileHover={{ y: -1, boxShadow: '0 4px 12px rgba(0,0,0,0.12)' }}\n whileTap={{ scale: 0.97 }}\n transition={springs.smooth}\n id={triggerId}\n aria-describedby={fieldCtx.helperTextId}\n aria-invalid={fieldCtx.state === 'error' || undefined}\n // Inside a FormField, let the visible <Label> name the trigger; else describe it.\n aria-label={fieldCtx.inputId ? undefined : `Color picker: ${internalColor}`}\n >\n {internalColor.toUpperCase()}\n </motion.button>\n ) : (\n <motion.button\n type=\"button\"\n disabled={disabled}\n className={cn(\n 'group relative flex items-center overflow-hidden rounded-control border border-surface-border-strong',\n 'hover:border-accent-7 focus:border-accent-7 focus:outline-hidden focus:ring-1 focus:ring-accent-9',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n whileHover={{ scale: 1.02 }}\n whileTap={{ scale: 0.98 }}\n transition={springs.snappy}\n id={triggerId}\n aria-describedby={fieldCtx.helperTextId}\n aria-invalid={fieldCtx.state === 'error' || undefined}\n // Inside a FormField, let the visible <Label> name the trigger; else describe it.\n aria-label={fieldCtx.inputId ? undefined : `Color picker: ${internalColor}`}\n >\n {/* Gradient background: color → surface */}\n <motion.span\n className=\"absolute inset-0\"\n animate={{\n background: `linear-gradient(to right, ${internalColor} 0%, ${internalColor} 35%, transparent 70%)`,\n }}\n /* Between durations.moderate02 (0.24) and durations.slow01 (0.4) — gradient lerp feel */\n transition={{ duration: 0.3 }}\n />\n <span className=\"absolute inset-0 bg-surface-overlay/60\" style={{\n maskImage: 'linear-gradient(to right, transparent 0%, black 40%)',\n WebkitMaskImage: 'linear-gradient(to right, transparent 0%, black 40%)',\n }} />\n {/* Hex value */}\n <span className=\"relative z-10 py-ds-02 pl-6 pr-ds-03 font-mono text-body-sm text-surface-fg\">\n {internalColor.toUpperCase()}\n </span>\n </motion.button>\n )}\n </PopoverTrigger>\n\n <PopoverContent\n role=\"dialog\"\n aria-label=\"Color picker\"\n align={align}\n sideOffset={8}\n className=\"w-[272px] rounded-overlay-lg bg-surface-overlay p-0 shadow-floating\"\n >\n <div className=\"flex flex-col\">\n {/* Interactive picker */}\n {showPicker && (\n <div className=\"p-ds-04 pb-ds-03\" onPointerUp={handlePickerChangeComplete} onPointerLeave={handlePickerChangeComplete}>\n <HexColorPicker\n color={internalColor}\n onChange={handleChange}\n className=\"w-full!\"\n style={{ height: 160 }}\n />\n </div>\n )}\n\n {/* Format inputs */}\n <div className=\"border-t border-surface-border px-ds-04 py-ds-03\">\n {/* Format switcher */}\n <div className=\"mb-ds-03 flex items-center gap-ds-01\">\n {formats.map((f) => (\n <button\n key={f}\n type=\"button\"\n onClick={() => setFormat(f)}\n className={cn(\n 'relative min-h-6 rounded-control-inner px-ds-02 py-px text-label-xs font-semibold uppercase tracking-wider transition-colors',\n format === f\n ? 'text-accent-11'\n : 'text-surface-fg-muted hover:text-surface-fg',\n )}\n >\n {format === f && (\n <motion.span\n layoutId={`color-input-format-pill-${instanceId}`}\n className=\"absolute inset-0 rounded-control-inner bg-accent-3\"\n transition={springs.snappy}\n />\n )}\n <span className=\"relative z-10\">{f}</span>\n </button>\n ))}\n </div>\n\n {/* Format fields — animated swap */}\n <AnimatePresence mode=\"wait\">\n {format === 'hex' && (\n <motion.div\n key=\"hex\"\n initial={{ opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: durations.moderate01 }}\n className=\"flex gap-ds-02\"\n >\n <FormatInput\n id={`${instanceId}-hex`}\n label=\"Hex\"\n value={hexDraft ?? internalColor.replace('#', '').toUpperCase()}\n onChange={(v) => {\n const clean = v.replace(/[^0-9a-fA-F]/g, '').slice(0, 6)\n setHexDraft(clean.toUpperCase())\n if (clean.length === 6) handleDiscreteChange(`#${clean}`)\n }}\n onBlur={() => {\n // Drop any incomplete draft; display falls back to the\n // committed color. A complete value already committed onChange.\n setHexDraft(null)\n }}\n disabled={disabled}\n maxLength={6}\n prefix=\"#\"\n className=\"flex-1\"\n />\n </motion.div>\n )}\n\n {format === 'rgb' && rgb && (\n <motion.div\n key=\"rgb\"\n initial={{ opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: durations.moderate01 }}\n className=\"flex gap-ds-02\"\n >\n <FormatInput id={`${instanceId}-r`} label=\"R\" value={String(rgb.r)} onChange={(v) => handleRgbChange('r', v)} disabled={disabled} className=\"flex-1\" />\n <FormatInput id={`${instanceId}-g`} label=\"G\" value={String(rgb.g)} onChange={(v) => handleRgbChange('g', v)} disabled={disabled} className=\"flex-1\" />\n <FormatInput id={`${instanceId}-b`} label=\"B\" value={String(rgb.b)} onChange={(v) => handleRgbChange('b', v)} disabled={disabled} className=\"flex-1\" />\n </motion.div>\n )}\n\n {format === 'hsl' && hsl && (\n <motion.div\n key=\"hsl\"\n initial={{ opacity: 0, y: 4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: durations.moderate01 }}\n className=\"flex gap-ds-02\"\n >\n <FormatInput id={`${instanceId}-h`} label=\"H\" value={String(hsl.h)} onChange={(v) => handleHslChange('h', v)} disabled={disabled} className=\"flex-1\" />\n <FormatInput id={`${instanceId}-s`} label=\"S\" value={String(hsl.s)} onChange={(v) => handleHslChange('s', v)} disabled={disabled} className=\"flex-1\" />\n <FormatInput id={`${instanceId}-l`} label=\"L\" value={String(hsl.l)} onChange={(v) => handleHslChange('l', v)} disabled={disabled} className=\"flex-1\" />\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n\n {/* Preset swatches */}\n {resolvedPresets.length > 0 && (\n <div className=\"border-t border-surface-border px-ds-04 py-ds-03\">\n <div className=\"flex flex-wrap gap-ds-02\">\n {resolvedPresets.map((preset, i) => {\n const isSelected = internalColor.toLowerCase() === preset.hex.toLowerCase()\n return (\n <motion.button\n key={preset.hex}\n type=\"button\"\n disabled={disabled}\n onClick={() => handleDiscreteChange(preset.hex)}\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: isSelected ? 1.15 : 1 }}\n whileHover={{ scale: isSelected ? 1.15 : 1.1 }}\n whileTap={{ scale: 0.9 }}\n transition={{ ...springs.bouncy, delay: i * 0.02 }}\n className={cn(\n 'h-6 w-6 rounded-control-inner border',\n isSelected\n ? 'border-accent-7 ring-2 ring-accent-9/30'\n : 'border-surface-border hover:border-surface-border-strong',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n style={{ backgroundColor: preset.hex }}\n title={preset.label}\n aria-label={`${preset.label}: ${preset.hex}`}\n />\n )\n })}\n </div>\n </div>\n )}\n\n {/* Reset / Undo footer */}\n {(undoStack.length > 0 || internalColor !== openColor) && (\n <motion.div\n initial={{ opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: 'auto' }}\n exit={{ opacity: 0, height: 0 }}\n className=\"flex items-center gap-ds-02 border-t border-surface-border px-ds-04 py-ds-02\"\n >\n {/* Original color preview */}\n <span\n className=\"h-4 w-4 shrink-0 rounded-pill border border-surface-border\"\n style={{ backgroundColor: openColor }}\n title={`Original: ${openColor}`}\n />\n <span className=\"text-caption text-surface-fg-muted\">\n {openColor.toUpperCase()}\n </span>\n <span className=\"flex-1\" />\n {undoStack.length > 0 && (\n <button\n type=\"button\"\n onClick={handleUndo}\n className=\"min-h-6 rounded-control-inner px-ds-02 py-px text-caption font-medium text-surface-fg-muted transition-colors hover:text-surface-fg\"\n >\n Undo\n </button>\n )}\n {internalColor !== openColor && (\n <button\n type=\"button\"\n onClick={handleReset}\n className=\"min-h-6 rounded-control-inner px-ds-02 py-px text-caption font-medium text-surface-fg-muted transition-colors hover:text-error-11\"\n >\n Reset\n </button>\n )}\n </motion.div>\n )}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n )\n },\n)\nColorInput.displayName = 'ColorInput'\n\nexport { ColorInput }\n"],"mappings":"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"}
package/dist/ui/icon.d.ts CHANGED
@@ -33,6 +33,8 @@ export interface IconProps {
33
33
  */
34
34
  state?: 'idle' | 'loading' | 'success' | 'error';
35
35
  className?: string;
36
+ /** Inline styles forwarded to the rendered icon (e.g. decorative opacity/transform). */
37
+ style?: React.CSSProperties;
36
38
  }
37
39
  /**
38
40
  * Icon — context-aware wrapper for Tabler icons with standardized sizing,
@@ -1 +1 @@
1
- {"version":3,"file":"icon.d.ts","sourceRoot":"","sources":["../../src/ui/icon.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAiB,MAAM,gBAAgB,CAAA;AAiB9E,6EAA6E;AAC7E,KAAK,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG;IACzF,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAqBD,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,IAAI,EAAE,KAAK,CAAC,yBAAyB,CAAC,kBAAkB,GAAG,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAA;IAC9F,oDAAoD;IACpD,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,wDAAwD;IACxD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7F;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,IAAI,iFAwHhB,CAAA"}
1
+ {"version":3,"file":"icon.d.ts","sourceRoot":"","sources":["../../src/ui/icon.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAiB,MAAM,gBAAgB,CAAA;AAiB9E,6EAA6E;AAC7E,KAAK,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG;IACzF,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAqBD,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,IAAI,EAAE,KAAK,CAAC,yBAAyB,CAAC,kBAAkB,GAAG,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAA;IAC9F,oDAAoD;IACpD,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,wDAAwD;IACxD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7F;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;IAChD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wFAAwF;IACxF,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;CAC5B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,IAAI,iFA2HhB,CAAA"}
package/dist/ui/icon.js CHANGED
@@ -79,10 +79,10 @@ var f = {
79
79
  lg: "md",
80
80
  xl: "lg",
81
81
  "2xl": "lg"
82
- }, g = a.forwardRef(({ icon: a, size: g, stroke: _, label: v, animate: y, state: b, className: x }, S) => {
83
- let C = e(), w = g ?? C.size ?? "md", T = _ ?? C.stroke ?? "regular", E = f[w], D = p[T][w], O = d();
82
+ }, g = a.forwardRef(({ icon: a, size: g, stroke: _, label: v, animate: y, state: b, className: x, style: S }, C) => {
83
+ let w = e(), T = g ?? w.size ?? "md", E = _ ?? w.stroke ?? "regular", D = f[T], O = p[E][T], k = d();
84
84
  if (b && b !== "idle") {
85
- let e = h[w], t = b === "loading" ? "spinning" : b;
85
+ let e = h[T], t = b === "loading" ? "spinning" : b;
86
86
  return /* @__PURE__ */ s(l, {
87
87
  mode: "wait",
88
88
  children: /* @__PURE__ */ s(u.span, {
@@ -91,6 +91,7 @@ var f = {
91
91
  exit: { opacity: 0 },
92
92
  transition: n.fade,
93
93
  className: "inline-flex",
94
+ style: S,
94
95
  children: /* @__PURE__ */ s(i, {
95
96
  size: e,
96
97
  state: t,
@@ -99,7 +100,7 @@ var f = {
99
100
  }, "spinner")
100
101
  });
101
102
  }
102
- if (y === "draw" && !O) {
103
+ if (y === "draw" && !k) {
103
104
  let e = {
104
105
  initial: {
105
106
  pathLength: 0,
@@ -118,16 +119,17 @@ var f = {
118
119
  }
119
120
  }, t = a.displayName ?? "", n = t === "Check" || t === "IconCheck" || t === "CircleCheck";
120
121
  if (n || t === "X" || t === "IconX") return /* @__PURE__ */ s("svg", {
121
- ref: S,
122
- width: E,
123
- height: E,
122
+ ref: C,
123
+ width: D,
124
+ height: D,
124
125
  viewBox: "0 0 24 24",
125
126
  fill: "none",
126
127
  stroke: "currentColor",
127
- strokeWidth: D,
128
+ strokeWidth: O,
128
129
  strokeLinecap: "round",
129
130
  strokeLinejoin: "round",
130
131
  className: x,
132
+ style: S,
131
133
  "aria-hidden": v ? void 0 : "true",
132
134
  "aria-label": v,
133
135
  role: v ? "img" : void 0,
@@ -160,41 +162,44 @@ var f = {
160
162
  })] })
161
163
  });
162
164
  }
163
- let k = typeof y == "string" && y !== "none" && y !== "draw" ? m[y] : null, A = typeof y == "object" && y && (y.rotate !== void 0 || y.scale !== void 0) ? y : null;
164
- if (O || !k && !A) return v ? /* @__PURE__ */ s(a, {
165
- ref: S,
166
- size: E,
167
- stroke: D,
165
+ let A = typeof y == "string" && y !== "none" && y !== "draw" ? m[y] : null, j = typeof y == "object" && y && (y.rotate !== void 0 || y.scale !== void 0) ? y : null;
166
+ if (k || !A && !j) return v ? /* @__PURE__ */ s(a, {
167
+ ref: C,
168
+ size: D,
169
+ stroke: O,
168
170
  className: x,
171
+ style: S,
169
172
  title: v,
170
173
  "aria-label": v,
171
174
  role: "img"
172
175
  }) : /* @__PURE__ */ s(a, {
173
- ref: S,
174
- size: E,
175
- stroke: D,
176
+ ref: C,
177
+ size: D,
178
+ stroke: O,
176
179
  className: x,
180
+ style: S,
177
181
  "aria-hidden": "true"
178
182
  });
179
- let j = k ?? {
180
- animate: A,
183
+ let M = A ?? {
184
+ animate: j,
181
185
  transition: t.snappy
182
- }, M = v ? /* @__PURE__ */ s(a, {
183
- size: E,
184
- stroke: D,
186
+ }, N = v ? /* @__PURE__ */ s(a, {
187
+ size: D,
188
+ stroke: O,
185
189
  title: v,
186
190
  "aria-label": v,
187
191
  role: "img"
188
192
  }) : /* @__PURE__ */ s(a, {
189
- size: E,
190
- stroke: D,
193
+ size: D,
194
+ stroke: O,
191
195
  "aria-hidden": "true"
192
196
  });
193
197
  return /* @__PURE__ */ s(u.span, {
194
- ref: S,
198
+ ref: C,
195
199
  className: r("inline-flex", x),
196
- ...j,
197
- children: M
200
+ style: S,
201
+ ...M,
202
+ children: N
198
203
  });
199
204
  });
200
205
  g.displayName = "Icon";
@@ -1 +1 @@
1
- {"version":3,"file":"icon.js","names":[],"sources":["../../src/ui/icon.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { type IconSize, type IconStroke,useIconContext } from './icon-context'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\n/** Icon size tier → pixel dimensions */\nconst SIZE_PX: Record<IconSize, number> = {\n xs: 14, sm: 16, md: 18, lg: 20, xl: 24, '2xl': 32,\n}\n\n/** Stroke weight → strokeWidth per size tier (lighter strokes on smaller icons) */\nconst STROKE_MAP: Record<IconStroke, Record<IconSize, number>> = {\n light: { xs: 1.25, sm: 1.5, md: 1.5, lg: 1.75, xl: 2, '2xl': 2 },\n regular: { xs: 1.5, sm: 2, md: 2, lg: 2, xl: 2, '2xl': 2.25 },\n bold: { xs: 2, sm: 2.5, md: 2.5, lg: 2.5, xl: 2.5, '2xl': 2.5 },\n}\n\n/** Props accepted by Tabler icon components (and most SVG icon libraries) */\ntype IconComponentProps = Partial<Omit<React.ComponentPropsWithoutRef<'svg'>, 'stroke'>> & {\n size?: string | number\n stroke?: string | number\n title?: string\n}\n\nconst ANIMATION_PRESETS = {\n spin: {\n animate: { rotate: 360 },\n transition: { duration: 1, repeat: Infinity, ease: 'linear' as const },\n },\n pulse: {\n animate: { scale: [1, 1.15, 1] },\n transition: { duration: 2, repeat: Infinity, ease: 'easeInOut' as const },\n },\n bounce: {\n animate: { y: [0, -4, 0] },\n transition: { duration: 1.5, repeat: Infinity, ease: 'easeInOut' as const },\n },\n}\n\nconst ICON_TO_SPINNER_SIZE: Record<IconSize, 'sm' | 'md' | 'lg'> = {\n xs: 'sm', sm: 'sm', md: 'md', lg: 'md', xl: 'lg', '2xl': 'lg',\n}\n\nexport interface IconProps {\n /** The Tabler icon component (or any ForwardRef SVG icon component) */\n icon: React.ForwardRefExoticComponent<IconComponentProps & React.RefAttributes<SVGSVGElement>>\n /** Size tier — reads from IconContext if not set */\n size?: IconSize\n /** Stroke weight — reads from IconContext if not set */\n stroke?: IconStroke\n /** Accessible label — renders <title> + sets aria-label. Without this, icon is aria-hidden. */\n label?: string\n /**\n * Preset animation or controlled motion. 'none' disables inherited animation.\n * 'draw' renders the icon as an SVG path that draws in like a pen stroke\n * (works with IconCheck and IconX — others fall back to fade-in).\n */\n animate?: 'spin' | 'pulse' | 'bounce' | 'draw' | 'none' | { rotate?: number; scale?: number }\n /**\n * State machine for loading→success/error transitions. Delegates to Spinner (bare variant).\n *\n * **Priority rule:** If both `state` and `animate` are set, `state` wins —\n * the state machine check runs first and short-circuits rendering.\n */\n state?: 'idle' | 'loading' | 'success' | 'error'\n className?: string\n}\n\n/**\n * Icon — context-aware wrapper for Tabler icons with standardized sizing,\n * stroke weights, accessibility, animation presets, and loading state machine.\n *\n * Reads size and stroke from IconContext (provided by Button, IconGroup, etc.).\n * Explicit props always override context.\n *\n * **Priority rule:** If both `state` and `animate` are set, `state` wins.\n *\n * @example\n * <Icon icon={IconPlus} /> // md size, regular stroke\n * <Icon icon={IconPlus} size=\"xs\" stroke=\"light\" /> // 14px, stroke 1.25\n * <Icon icon={IconPlus} label=\"Add item\" /> // accessible, not decorative\n * <Icon icon={IconPlus} animate=\"spin\" /> // continuous rotation\n * <Icon icon={IconPlus} animate=\"pulse\" /> // scale pulse\n * <Icon icon={IconPlus} state=\"loading\" /> // bare spinner\n * <Icon icon={IconPlus} state=\"success\" /> // animated checkmark\n */\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(\n ({ icon: TablerIcon, size, stroke, label, animate, state, className }, ref) => {\n const ctx = useIconContext()\n const resolvedSize = size ?? ctx.size ?? 'md'\n const resolvedStroke = stroke ?? ctx.stroke ?? 'regular'\n const px = SIZE_PX[resolvedSize]\n const sw = STROKE_MAP[resolvedStroke][resolvedSize]\n const prefersReduced = useReducedMotion()\n\n // ── State machine (priority: state > animate) ──────────────────────\n if (state && state !== 'idle') {\n const spinnerSize = ICON_TO_SPINNER_SIZE[resolvedSize]\n const spinnerState = state === 'loading' ? 'spinning' : state\n return (\n <AnimatePresence mode=\"wait\">\n <motion.span\n key=\"spinner\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n className=\"inline-flex\"\n >\n <Spinner size={spinnerSize} state={spinnerState} variant=\"bare\" />\n </motion.span>\n </AnimatePresence>\n )\n }\n\n // ── Draw animation (SVG pathLength) ─────────────────────────────────\n if (animate === 'draw' && !prefersReduced) {\n const drawProps = {\n initial: { pathLength: 0, opacity: 0 },\n animate: { pathLength: 1, opacity: 1 },\n transition: { pathLength: { duration: 0.35, ease: 'easeOut' as const }, opacity: { duration: 0.1 } },\n }\n // Check which icon this is to render the appropriate SVG path.\n // Tabler displayNames: IconCheck → \"Check\", IconX → \"X\"\n const name = TablerIcon.displayName ?? ''\n const isCheck = name === 'Check' || name === 'IconCheck' || name === 'CircleCheck'\n const isX = name === 'X' || name === 'IconX'\n\n if (isCheck || isX) {\n return (\n <svg\n ref={ref}\n width={px}\n height={px}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={sw}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n aria-hidden={label ? undefined : 'true'}\n aria-label={label}\n role={label ? 'img' : undefined}\n >\n {isCheck ? (\n <motion.polyline points=\"4 12 10 18 20 6\" {...drawProps} />\n ) : (\n <>\n <motion.line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" {...drawProps} />\n <motion.line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"\n {...drawProps}\n transition={{\n pathLength: { duration: 0.25, ease: 'easeOut', delay: 0.1 },\n opacity: { duration: 0.1, delay: 0.1 },\n }}\n />\n </>\n )}\n </svg>\n )\n }\n // Unknown icon: fall through to fade-in via static render\n }\n\n // ── Determine animation ────────────────────────────────────────────\n const animatePreset =\n typeof animate === 'string' && animate !== 'none' && animate !== 'draw'\n ? ANIMATION_PRESETS[animate as keyof typeof ANIMATION_PRESETS]\n : null\n const animateObject =\n typeof animate === 'object' && animate !== null && (animate.rotate !== undefined || animate.scale !== undefined)\n ? animate\n : null\n\n if (prefersReduced || (!animatePreset && !animateObject)) {\n // Static render (no animation or reduced motion)\n return label ? (\n <TablerIcon ref={ref as any} size={px} stroke={sw} className={className} title={label} aria-label={label} role=\"img\" />\n ) : (\n <TablerIcon ref={ref as any} size={px} stroke={sw} className={className} aria-hidden=\"true\" />\n )\n }\n\n // ── Animated render ────────────────────────────────────────────────\n const motionProps = animatePreset ?? {\n animate: animateObject!,\n transition: springs.snappy,\n }\n\n const iconEl = label ? (\n <TablerIcon size={px} stroke={sw} title={label} aria-label={label} role=\"img\" />\n ) : (\n <TablerIcon size={px} stroke={sw} aria-hidden=\"true\" />\n )\n\n return (\n <motion.span\n ref={ref as any}\n className={cn('inline-flex', className)}\n {...motionProps}\n >\n {iconEl}\n </motion.span>\n )\n },\n)\nIcon.displayName = 'Icon'\n"],"mappings":"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"}
1
+ {"version":3,"file":"icon.js","names":[],"sources":["../../src/ui/icon.tsx"],"sourcesContent":["'use client'\n\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { type IconSize, type IconStroke,useIconContext } from './icon-context'\nimport { springs, tweens } from './lib/motion'\nimport { cn } from './lib/utils'\nimport { Spinner } from './spinner'\n\n/** Icon size tier → pixel dimensions */\nconst SIZE_PX: Record<IconSize, number> = {\n xs: 14, sm: 16, md: 18, lg: 20, xl: 24, '2xl': 32,\n}\n\n/** Stroke weight → strokeWidth per size tier (lighter strokes on smaller icons) */\nconst STROKE_MAP: Record<IconStroke, Record<IconSize, number>> = {\n light: { xs: 1.25, sm: 1.5, md: 1.5, lg: 1.75, xl: 2, '2xl': 2 },\n regular: { xs: 1.5, sm: 2, md: 2, lg: 2, xl: 2, '2xl': 2.25 },\n bold: { xs: 2, sm: 2.5, md: 2.5, lg: 2.5, xl: 2.5, '2xl': 2.5 },\n}\n\n/** Props accepted by Tabler icon components (and most SVG icon libraries) */\ntype IconComponentProps = Partial<Omit<React.ComponentPropsWithoutRef<'svg'>, 'stroke'>> & {\n size?: string | number\n stroke?: string | number\n title?: string\n}\n\nconst ANIMATION_PRESETS = {\n spin: {\n animate: { rotate: 360 },\n transition: { duration: 1, repeat: Infinity, ease: 'linear' as const },\n },\n pulse: {\n animate: { scale: [1, 1.15, 1] },\n transition: { duration: 2, repeat: Infinity, ease: 'easeInOut' as const },\n },\n bounce: {\n animate: { y: [0, -4, 0] },\n transition: { duration: 1.5, repeat: Infinity, ease: 'easeInOut' as const },\n },\n}\n\nconst ICON_TO_SPINNER_SIZE: Record<IconSize, 'sm' | 'md' | 'lg'> = {\n xs: 'sm', sm: 'sm', md: 'md', lg: 'md', xl: 'lg', '2xl': 'lg',\n}\n\nexport interface IconProps {\n /** The Tabler icon component (or any ForwardRef SVG icon component) */\n icon: React.ForwardRefExoticComponent<IconComponentProps & React.RefAttributes<SVGSVGElement>>\n /** Size tier — reads from IconContext if not set */\n size?: IconSize\n /** Stroke weight — reads from IconContext if not set */\n stroke?: IconStroke\n /** Accessible label — renders <title> + sets aria-label. Without this, icon is aria-hidden. */\n label?: string\n /**\n * Preset animation or controlled motion. 'none' disables inherited animation.\n * 'draw' renders the icon as an SVG path that draws in like a pen stroke\n * (works with IconCheck and IconX — others fall back to fade-in).\n */\n animate?: 'spin' | 'pulse' | 'bounce' | 'draw' | 'none' | { rotate?: number; scale?: number }\n /**\n * State machine for loading→success/error transitions. Delegates to Spinner (bare variant).\n *\n * **Priority rule:** If both `state` and `animate` are set, `state` wins —\n * the state machine check runs first and short-circuits rendering.\n */\n state?: 'idle' | 'loading' | 'success' | 'error'\n className?: string\n /** Inline styles forwarded to the rendered icon (e.g. decorative opacity/transform). */\n style?: React.CSSProperties\n}\n\n/**\n * Icon — context-aware wrapper for Tabler icons with standardized sizing,\n * stroke weights, accessibility, animation presets, and loading state machine.\n *\n * Reads size and stroke from IconContext (provided by Button, IconGroup, etc.).\n * Explicit props always override context.\n *\n * **Priority rule:** If both `state` and `animate` are set, `state` wins.\n *\n * @example\n * <Icon icon={IconPlus} /> // md size, regular stroke\n * <Icon icon={IconPlus} size=\"xs\" stroke=\"light\" /> // 14px, stroke 1.25\n * <Icon icon={IconPlus} label=\"Add item\" /> // accessible, not decorative\n * <Icon icon={IconPlus} animate=\"spin\" /> // continuous rotation\n * <Icon icon={IconPlus} animate=\"pulse\" /> // scale pulse\n * <Icon icon={IconPlus} state=\"loading\" /> // bare spinner\n * <Icon icon={IconPlus} state=\"success\" /> // animated checkmark\n */\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(\n ({ icon: TablerIcon, size, stroke, label, animate, state, className, style }, ref) => {\n const ctx = useIconContext()\n const resolvedSize = size ?? ctx.size ?? 'md'\n const resolvedStroke = stroke ?? ctx.stroke ?? 'regular'\n const px = SIZE_PX[resolvedSize]\n const sw = STROKE_MAP[resolvedStroke][resolvedSize]\n const prefersReduced = useReducedMotion()\n\n // ── State machine (priority: state > animate) ──────────────────────\n if (state && state !== 'idle') {\n const spinnerSize = ICON_TO_SPINNER_SIZE[resolvedSize]\n const spinnerState = state === 'loading' ? 'spinning' : state\n return (\n <AnimatePresence mode=\"wait\">\n <motion.span\n key=\"spinner\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={tweens.fade}\n className=\"inline-flex\"\n style={style}\n >\n <Spinner size={spinnerSize} state={spinnerState} variant=\"bare\" />\n </motion.span>\n </AnimatePresence>\n )\n }\n\n // ── Draw animation (SVG pathLength) ─────────────────────────────────\n if (animate === 'draw' && !prefersReduced) {\n const drawProps = {\n initial: { pathLength: 0, opacity: 0 },\n animate: { pathLength: 1, opacity: 1 },\n transition: { pathLength: { duration: 0.35, ease: 'easeOut' as const }, opacity: { duration: 0.1 } },\n }\n // Check which icon this is to render the appropriate SVG path.\n // Tabler displayNames: IconCheck → \"Check\", IconX → \"X\"\n const name = TablerIcon.displayName ?? ''\n const isCheck = name === 'Check' || name === 'IconCheck' || name === 'CircleCheck'\n const isX = name === 'X' || name === 'IconX'\n\n if (isCheck || isX) {\n return (\n <svg\n ref={ref}\n width={px}\n height={px}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={sw}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={className}\n style={style}\n aria-hidden={label ? undefined : 'true'}\n aria-label={label}\n role={label ? 'img' : undefined}\n >\n {isCheck ? (\n <motion.polyline points=\"4 12 10 18 20 6\" {...drawProps} />\n ) : (\n <>\n <motion.line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" {...drawProps} />\n <motion.line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"\n {...drawProps}\n transition={{\n pathLength: { duration: 0.25, ease: 'easeOut', delay: 0.1 },\n opacity: { duration: 0.1, delay: 0.1 },\n }}\n />\n </>\n )}\n </svg>\n )\n }\n // Unknown icon: fall through to fade-in via static render\n }\n\n // ── Determine animation ────────────────────────────────────────────\n const animatePreset =\n typeof animate === 'string' && animate !== 'none' && animate !== 'draw'\n ? ANIMATION_PRESETS[animate as keyof typeof ANIMATION_PRESETS]\n : null\n const animateObject =\n typeof animate === 'object' && animate !== null && (animate.rotate !== undefined || animate.scale !== undefined)\n ? animate\n : null\n\n if (prefersReduced || (!animatePreset && !animateObject)) {\n // Static render (no animation or reduced motion)\n return label ? (\n <TablerIcon ref={ref as any} size={px} stroke={sw} className={className} style={style} title={label} aria-label={label} role=\"img\" />\n ) : (\n <TablerIcon ref={ref as any} size={px} stroke={sw} className={className} style={style} aria-hidden=\"true\" />\n )\n }\n\n // ── Animated render ────────────────────────────────────────────────\n const motionProps = animatePreset ?? {\n animate: animateObject!,\n transition: springs.snappy,\n }\n\n const iconEl = label ? (\n <TablerIcon size={px} stroke={sw} title={label} aria-label={label} role=\"img\" />\n ) : (\n <TablerIcon size={px} stroke={sw} aria-hidden=\"true\" />\n )\n\n return (\n <motion.span\n ref={ref as any}\n className={cn('inline-flex', className)}\n style={style}\n {...motionProps}\n >\n {iconEl}\n </motion.span>\n )\n },\n)\nIcon.displayName = 'Icon'\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"search-input.d.ts","sourceRoot":"","sources":["../../src/ui/search-input.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,KAAK,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACjG,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oBAAoB;IACpB,IAAI,CAAC,EAAE,eAAe,CAAA;CACvB;AAED,QAAA,MAAM,WAAW,2FA6ChB,CAAA;AAGD,OAAO,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"search-input.d.ts","sourceRoot":"","sources":["../../src/ui/search-input.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,KAAK,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACjG,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oBAAoB;IACpB,IAAI,CAAC,EAAE,eAAe,CAAA;CACvB;AAED,QAAA,MAAM,WAAW,2FA8ChB,CAAA;AAGD,OAAO,EAAE,WAAW,EAAE,CAAA"}
@@ -11,6 +11,7 @@ import { AnimatePresence as l, motion as u } from "framer-motion";
11
11
  //#region src/ui/search-input.tsx
12
12
  var d = a.forwardRef(({ className: a, value: d, onClear: f, loading: p, size: m = "md", placeholder: h, ...g }, _) => {
13
13
  let v = d !== void 0 && d !== "", y = p ? /* @__PURE__ */ o(t, { size: "sm" }) : /* @__PURE__ */ o(l, { children: v && f && /* @__PURE__ */ o(u.div, {
14
+ className: "flex items-center justify-center",
14
15
  initial: {
15
16
  opacity: 0,
16
17
  scale: .8
@@ -1 +1 @@
1
- {"version":3,"file":"search-input.js","names":[],"sources":["../../src/ui/search-input.tsx"],"sourcesContent":["'use client'\n\nimport { IconSearch, IconX } from '@tabler/icons-react'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from './button'\nimport { Icon } from './icon'\nimport { Input } from './input'\nimport { springs } from './lib/motion'\nimport { Spinner } from './spinner'\n\ntype SearchInputSize = 'xs' | 'sm' | 'md' | 'lg'\n\n/**\n * Props for SearchInput — a search field with a built-in leading magnifier icon, optional loading\n * spinner, and an auto-shown clear button when `value` is non-empty and `onClear` is provided.\n *\n * **Sizes:** `sm` | `md` (default) | `lg` — matches Input's `size` prop API.\n * HTML's native `size` attribute is excluded — use CSS width instead.\n *\n * **Clear button:** Appears automatically when `value !== ''` and `onClear` is provided.\n * When `loading` is true, a spinning loader replaces the clear button.\n *\n * @example\n * // Controlled search with clear:\n * <SearchInput\n * value={query}\n * onChange={(e) => setQuery(e.target.value)}\n * onClear={() => setQuery('')}\n * placeholder=\"Search tasks...\"\n * />\n *\n * @example\n * // Async search with loading state while fetching results:\n * <SearchInput\n * value={query}\n * onChange={handleSearch}\n * loading={isSearching}\n * placeholder=\"Search clients...\"\n * />\n *\n * @example\n * // Compact search bar in a toolbar:\n * <SearchInput size=\"sm\" value={q} onChange={(e) => setQ(e.target.value)} onClear={() => setQ('')} />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface SearchInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {\n onClear?: () => void\n loading?: boolean\n /** @default 'md' */\n size?: SearchInputSize\n}\n\nconst SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(\n ({ className, value, onClear, loading, size = 'md', placeholder, ...props }, ref) => {\n const hasValue = value !== undefined && value !== ''\n\n const endContent = loading ? (\n <Spinner size=\"sm\" />\n ) : (\n <AnimatePresence>\n {hasValue && onClear && (\n <motion.div\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.8 }}\n transition={springs.snappy}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n onClick={onClear}\n aria-label=\"Clear search\"\n title=\"Clear\"\n >\n <Icon icon={IconX} />\n </Button>\n </motion.div>\n )}\n </AnimatePresence>\n )\n\n return (\n <Input\n ref={ref}\n size={size}\n startSection={<Icon icon={IconSearch} />}\n endSection={endContent}\n endSectionClickable={!!hasValue && !loading}\n placeholder={placeholder}\n value={value}\n aria-busy={loading || undefined}\n className={className}\n {...props}\n />\n )\n },\n)\nSearchInput.displayName = 'SearchInput'\n\nexport { SearchInput }\n"],"mappings":";;;;;;;;;;;AAsDA,IAAM,IAAc,EAAM,YACvB,EAAE,cAAW,UAAO,YAAS,YAAS,UAAO,MAAM,gBAAa,GAAG,KAAS,MAAQ;CACnF,IAAM,IAAW,MAAU,KAAA,KAAa,MAAU,IAE5C,IAAa,IACjB,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA,IAEpB,kBAAC,GAAD,EAAA,UACG,KAAY,KACX,kBAAC,EAAO,KAAR;EACE,SAAS;GAAE,SAAS;GAAG,OAAO;EAAI;EAClC,SAAS;GAAE,SAAS;GAAG,OAAO;EAAE;EAChC,MAAM;GAAE,SAAS;GAAG,OAAO;EAAI;EAC/B,YAAY,EAAQ;YAEpB,kBAAC,GAAD;GACE,MAAK;GACL,SAAQ;GACR,MAAK;GACL,SAAS;GACT,cAAW;GACX,OAAM;aAEN,kBAAC,GAAD,EAAM,MAAM,EAAQ,CAAA;EACd,CAAA;CACE,CAAA,EAEC,CAAA;CAGnB,OACE,kBAAC,GAAD;EACO;EACC;EACN,cAAc,kBAAC,GAAD,EAAM,MAAM,EAAa,CAAA;EACvC,YAAY;EACZ,qBAAqB,CAAC,CAAC,KAAY,CAAC;EACvB;EACN;EACP,aAAW,KAAW,KAAA;EACX;EACX,GAAI;CACL,CAAA;AAEL,CACF;AACA,EAAY,cAAc"}
1
+ {"version":3,"file":"search-input.js","names":[],"sources":["../../src/ui/search-input.tsx"],"sourcesContent":["'use client'\n\nimport { IconSearch, IconX } from '@tabler/icons-react'\nimport { AnimatePresence, motion } from 'framer-motion'\nimport * as React from 'react'\n\nimport { Button } from './button'\nimport { Icon } from './icon'\nimport { Input } from './input'\nimport { springs } from './lib/motion'\nimport { Spinner } from './spinner'\n\ntype SearchInputSize = 'xs' | 'sm' | 'md' | 'lg'\n\n/**\n * Props for SearchInput — a search field with a built-in leading magnifier icon, optional loading\n * spinner, and an auto-shown clear button when `value` is non-empty and `onClear` is provided.\n *\n * **Sizes:** `sm` | `md` (default) | `lg` — matches Input's `size` prop API.\n * HTML's native `size` attribute is excluded — use CSS width instead.\n *\n * **Clear button:** Appears automatically when `value !== ''` and `onClear` is provided.\n * When `loading` is true, a spinning loader replaces the clear button.\n *\n * @example\n * // Controlled search with clear:\n * <SearchInput\n * value={query}\n * onChange={(e) => setQuery(e.target.value)}\n * onClear={() => setQuery('')}\n * placeholder=\"Search tasks...\"\n * />\n *\n * @example\n * // Async search with loading state while fetching results:\n * <SearchInput\n * value={query}\n * onChange={handleSearch}\n * loading={isSearching}\n * placeholder=\"Search clients...\"\n * />\n *\n * @example\n * // Compact search bar in a toolbar:\n * <SearchInput size=\"sm\" value={q} onChange={(e) => setQ(e.target.value)} onClear={() => setQ('')} />\n * // These are just a few ways — feel free to combine props creatively!\n */\nexport interface SearchInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {\n onClear?: () => void\n loading?: boolean\n /** @default 'md' */\n size?: SearchInputSize\n}\n\nconst SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(\n ({ className, value, onClear, loading, size = 'md', placeholder, ...props }, ref) => {\n const hasValue = value !== undefined && value !== ''\n\n const endContent = loading ? (\n <Spinner size=\"sm\" />\n ) : (\n <AnimatePresence>\n {hasValue && onClear && (\n <motion.div\n className=\"flex items-center justify-center\"\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.8 }}\n transition={springs.snappy}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n onClick={onClear}\n aria-label=\"Clear search\"\n title=\"Clear\"\n >\n <Icon icon={IconX} />\n </Button>\n </motion.div>\n )}\n </AnimatePresence>\n )\n\n return (\n <Input\n ref={ref}\n size={size}\n startSection={<Icon icon={IconSearch} />}\n endSection={endContent}\n endSectionClickable={!!hasValue && !loading}\n placeholder={placeholder}\n value={value}\n aria-busy={loading || undefined}\n className={className}\n {...props}\n />\n )\n },\n)\nSearchInput.displayName = 'SearchInput'\n\nexport { SearchInput }\n"],"mappings":";;;;;;;;;;;AAsDA,IAAM,IAAc,EAAM,YACvB,EAAE,cAAW,UAAO,YAAS,YAAS,UAAO,MAAM,gBAAa,GAAG,KAAS,MAAQ;CACnF,IAAM,IAAW,MAAU,KAAA,KAAa,MAAU,IAE5C,IAAa,IACjB,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA,IAEpB,kBAAC,GAAD,EAAA,UACG,KAAY,KACX,kBAAC,EAAO,KAAR;EACE,WAAU;EACV,SAAS;GAAE,SAAS;GAAG,OAAO;EAAI;EAClC,SAAS;GAAE,SAAS;GAAG,OAAO;EAAE;EAChC,MAAM;GAAE,SAAS;GAAG,OAAO;EAAI;EAC/B,YAAY,EAAQ;YAEpB,kBAAC,GAAD;GACE,MAAK;GACL,SAAQ;GACR,MAAK;GACL,SAAS;GACT,cAAW;GACX,OAAM;aAEN,kBAAC,GAAD,EAAM,MAAM,EAAQ,CAAA;EACd,CAAA;CACE,CAAA,EAEC,CAAA;CAGnB,OACE,kBAAC,GAAD;EACO;EACC;EACN,cAAc,kBAAC,GAAD,EAAM,MAAM,EAAa,CAAA;EACvC,YAAY;EACZ,qBAAqB,CAAC,CAAC,KAAY,CAAC;EACvB;EACN;EACP,aAAW,KAAW,KAAA;EACX;EACX,GAAI;CACL,CAAA;AAEL,CACF;AACA,EAAY,cAAc"}
@@ -2,20 +2,42 @@
2
2
  import { IconInput } from './lib/icon-input';
3
3
  import * as React from 'react';
4
4
  export type SegmentedControlSize = 'sm' | 'md' | 'lg';
5
- export type SegmentedControlVariant = 'default' | 'solid';
5
+ export type SegmentedControlVariant = 'soft' | 'solid';
6
+ /** @deprecated `variant="default"` was renamed to `"soft"`. Still accepted at
7
+ * runtime (maps to `"soft"`); update call sites — the alias is removed in a
8
+ * future major. */
9
+ type DeprecatedVariant = 'default';
6
10
  export interface SegmentedControlOption {
7
11
  id: string;
8
- text: string;
12
+ /** Visible label. Accepts a `ReactNode` so an option can carry a count badge
13
+ * or custom node, not just a string. Optional — omit for an icon-only
14
+ * segment, in which case set `ariaLabel`. */
15
+ text?: React.ReactNode;
9
16
  /** Optional icon rendered before the text label. Accepts any `IconInput`. */
10
17
  icon?: IconInput;
18
+ /** Accessible name for the segment. Required for icon-only segments (no
19
+ * `text`); otherwise the visible text provides the name. */
20
+ ariaLabel?: string;
11
21
  }
12
- export interface SegmentedControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
22
+ export interface SegmentedControlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect' | 'defaultValue'> {
13
23
  size?: SegmentedControlSize;
14
- variant?: SegmentedControlVariant;
24
+ variant?: SegmentedControlVariant | DeprecatedVariant;
15
25
  options: SegmentedControlOption[];
16
- selectedId: string;
17
- onSelect: (id: string) => void;
26
+ /** Selected option id (controlled). Aligns with Tabs/ToggleGroup. */
27
+ value?: string;
28
+ /** Initial selected id for uncontrolled mode. Ignored when `value` is set;
29
+ * defaults to the first option. */
30
+ defaultValue?: string;
31
+ /** Fires with the newly-selected id (both modes). */
32
+ onValueChange?: (id: string) => void;
18
33
  disabled?: boolean;
34
+ /** Fill the container: segments split the available width equally instead of
35
+ * hugging their content. Use for full-width toggles and view switchers. */
36
+ fullWidth?: boolean;
37
+ /** @deprecated Use `value`. Still accepted; kept for back-compat. */
38
+ selectedId?: string;
39
+ /** @deprecated Use `onValueChange`. Still accepted; kept for back-compat. */
40
+ onSelect?: (id: string) => void;
19
41
  }
20
42
  declare const SegmentedControl: React.ForwardRefExoticComponent<SegmentedControlProps & React.RefAttributes<HTMLDivElement>>;
21
43
  export { SegmentedControl };
@@ -1 +1 @@
1
- {"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../src/ui/segmented-control.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAMjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AACrD,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,CAAA;AAEzD,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,6EAA6E;IAC7E,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IACnG,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,OAAO,CAAC,EAAE,uBAAuB,CAAA;IACjC,OAAO,EAAE,sBAAsB,EAAE,CAAA;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AA6BD,QAAA,MAAM,gBAAgB,8FAkIrB,CAAA;AAKD,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../src/ui/segmented-control.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAMjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AACrD,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,OAAO,CAAA;AAEtD;;oBAEoB;AACpB,KAAK,iBAAiB,GAAG,SAAS,CAAA;AAElC,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV;;kDAE8C;IAC9C,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;iEAC6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,GAAG,cAAc,CAAC;IACpH,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,OAAO,CAAC,EAAE,uBAAuB,GAAG,iBAAiB,CAAA;IACrD,OAAO,EAAE,sBAAsB,EAAE,CAAA;IACjC,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;wCACoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qDAAqD;IACrD,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;gFAC4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AA2CD,QAAA,MAAM,gBAAgB,8FAkLrB,CAAA;AAKD,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
@@ -4,9 +4,19 @@ import { cn as t } from "./lib/utils.js";
4
4
  import { normalizeIcon as n } from "../_chunks/normalize-icon.js";
5
5
  import * as r from "react";
6
6
  import { jsx as i, jsxs as a } from "react/jsx-runtime";
7
- import { LayoutGroup as o, motion as s } from "framer-motion";
7
+ import { LayoutGroup as o, motion as s, useReducedMotion as c } from "framer-motion";
8
8
  //#region src/ui/segmented-control.tsx
9
- var c = {
9
+ function l(e) {
10
+ if (!e) return !1;
11
+ let t = e.closest("[dir]");
12
+ if (t) return (t.getAttribute("dir") ?? "").toLowerCase() === "rtl";
13
+ try {
14
+ return getComputedStyle(e).direction === "rtl";
15
+ } catch {
16
+ return !1;
17
+ }
18
+ }
19
+ var u = {
10
20
  sm: {
11
21
  button: "h-7 px-ds-04 text-body-sm",
12
22
  icon: "h-3.5 w-3.5"
@@ -19,76 +29,84 @@ var c = {
19
29
  button: "h-10 px-ds-06 text-body-md",
20
30
  icon: "h-4 w-4"
21
31
  }
22
- }, l = {
23
- default: "bg-surface-overlay shadow-raised",
24
- solid: "bg-accent-9"
25
- }, u = {
26
- default: "text-surface-fg",
27
- solid: "text-accent-fg"
28
32
  }, d = {
29
- type: "spring",
30
- stiffness: 400,
31
- damping: 30
32
- }, f = r.forwardRef(function({ size: f = "md", variant: p = "default", options: m, selectedId: h, onSelect: g, disabled: _ = !1, className: v, ...y }, b) {
33
- let x = r.useId(), S = r.useRef(null), C = r.useCallback((e) => {
34
- S.current = e, typeof b == "function" ? b(e) : b && (b.current = e);
35
- }, [b]), w = (e) => {
36
- if (_) return;
37
- let t = m.findIndex((e) => e.id === h), n = t;
33
+ soft: "bg-segment-thumb shadow-segment",
34
+ solid: "bg-accent-9 shadow-segment"
35
+ }, f = {
36
+ soft: "text-surface-fg",
37
+ solid: "text-accent-fg"
38
+ }, p = r.forwardRef(function({ size: p = "md", variant: m = "soft", options: h, value: g, defaultValue: _, onValueChange: v, selectedId: y, onSelect: b, disabled: x = !1, fullWidth: S = !1, className: C, ...w }, T) {
39
+ let E = r.useId(), D = r.useRef(null), O = c(), k = m === "default" ? "soft" : m, A = g ?? y, j = A !== void 0, [M, N] = r.useState(_ ?? h[0]?.id), P = j ? A : M, F = r.useCallback((e) => {
40
+ j || N(e), (v ?? b)?.(e);
41
+ }, [
42
+ j,
43
+ v,
44
+ b
45
+ ]), I = O ? { duration: 0 } : {
46
+ type: "spring",
47
+ duration: .3,
48
+ bounce: 0
49
+ }, L = r.useCallback((e) => {
50
+ D.current = e, typeof T == "function" ? T(e) : T && (T.current = e);
51
+ }, [T]), R = (e) => {
52
+ if (x) return;
53
+ let t = h.findIndex((e) => e.id === P), n = t, r = t > 0 ? t - 1 : h.length - 1, i = t < h.length - 1 ? t + 1 : 0, a = l(D.current);
38
54
  switch (e.key) {
39
55
  case "ArrowLeft":
40
- e.preventDefault(), n = t > 0 ? t - 1 : m.length - 1;
56
+ e.preventDefault(), n = a ? i : r;
41
57
  break;
42
58
  case "ArrowRight":
43
- e.preventDefault(), n = t < m.length - 1 ? t + 1 : 0;
59
+ e.preventDefault(), n = a ? r : i;
44
60
  break;
45
61
  case "Home":
46
62
  e.preventDefault(), n = 0;
47
63
  break;
48
64
  case "End":
49
- e.preventDefault(), n = m.length - 1;
65
+ e.preventDefault(), n = h.length - 1;
50
66
  break;
51
67
  default: return;
52
68
  }
53
- g(m[n].id), requestAnimationFrame(() => {
54
- (S.current?.querySelectorAll("[role=\"radio\"]"))?.[n]?.focus();
69
+ F(h[n].id), requestAnimationFrame(() => {
70
+ (D.current?.querySelectorAll("[role=\"radio\"]"))?.[n]?.focus();
55
71
  });
56
- }, { button: T, icon: E } = c[f];
72
+ }, { button: z, icon: B } = u[p];
57
73
  return /* @__PURE__ */ i("div", {
58
- ref: C,
74
+ ref: L,
59
75
  role: "radiogroup",
60
76
  tabIndex: -1,
61
- "aria-label": y["aria-label"] ?? "Segmented control",
62
- onKeyDown: w,
63
- className: t("inline-flex p-[3px] rounded-pill", "bg-surface-raised-hover border border-surface-border-subtle shadow-inset", _ && "opacity-action-disabled pointer-events-none", v),
64
- ...y,
77
+ "aria-label": w["aria-label"] ?? "Segmented control",
78
+ onKeyDown: R,
79
+ className: t("p-ds-01 rounded-surface bg-segment-track", S ? "flex w-full" : "inline-flex w-fit", x && "opacity-action-disabled pointer-events-none", C),
80
+ ...w,
65
81
  children: /* @__PURE__ */ i(o, {
66
- id: x,
67
- children: m.map((r) => {
68
- let o = r.id === h;
82
+ id: E,
83
+ children: h.map((r) => {
84
+ let o = r.id === P;
69
85
  return /* @__PURE__ */ a("button", {
70
86
  type: "button",
71
87
  role: "radio",
72
88
  "aria-checked": o,
89
+ "aria-label": r.ariaLabel,
73
90
  tabIndex: o ? 0 : -1,
74
- disabled: _,
75
- onClick: () => g(r.id),
76
- className: t("relative inline-flex items-center justify-center gap-ds-02 rounded-pill", "font-medium transition-colors duration-fast-02 ease-productive-standard", "outline-hidden focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2", T, o ? u[p] : "text-surface-fg-muted hover:text-surface-fg"),
91
+ disabled: x,
92
+ onClick: () => F(r.id),
93
+ className: t("relative inline-flex items-center justify-center gap-ds-02 rounded-control", "font-medium outline-hidden", "touch-target", "transition-[color,transform] duration-fast-02 ease-productive-standard", "motion-safe:active:scale-[0.97]", "focus-visible:ring-2 focus-visible:ring-accent-9 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent", z, S && "flex-1 min-w-16", o ? f[k] : "text-surface-fg-muted hover:text-surface-fg"),
77
94
  children: [
78
95
  o && /* @__PURE__ */ i(s.span, {
79
- layoutId: "segment-pill",
80
- className: t("absolute inset-0 rounded-pill pointer-events-none", l[p]),
81
- transition: d
96
+ layoutId: "segment-thumb",
97
+ initial: !1,
98
+ className: t("absolute inset-0 rounded-control pointer-events-none", d[k]),
99
+ transition: I
82
100
  }),
83
101
  r.icon && /* @__PURE__ */ i("span", {
84
- className: t("relative z-[1] shrink-0 inline-flex items-center justify-center", E),
102
+ className: t("relative shrink-0 inline-flex items-center justify-center", B),
85
103
  children: /* @__PURE__ */ i(e, {
86
- size: f === "lg" ? "sm" : "xs",
104
+ size: p === "lg" ? "sm" : "xs",
87
105
  children: n(r.icon)
88
106
  })
89
107
  }),
90
- /* @__PURE__ */ i("span", {
91
- className: "relative z-[1]",
108
+ r.text != null && /* @__PURE__ */ i("span", {
109
+ className: "relative",
92
110
  children: r.text
93
111
  })
94
112
  ]
@@ -97,6 +115,6 @@ var c = {
97
115
  })
98
116
  });
99
117
  });
100
- f.displayName = "SegmentedControl";
118
+ p.displayName = "SegmentedControl";
101
119
  //#endregion
102
- export { f as SegmentedControl };
120
+ export { p as SegmentedControl };