@spear-ai/spectral 1.21.1 → 1.21.3

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 (48) hide show
  1. package/dist/ButtonGroup/ButtonGroupButton.d.ts.map +1 -1
  2. package/dist/ButtonGroup/ButtonGroupButton.js +6 -5
  3. package/dist/ButtonGroup/ButtonGroupButton.js.map +1 -1
  4. package/dist/ButtonGroup.js +1 -1
  5. package/dist/ButtonGroup.js.map +1 -1
  6. package/dist/ButtonIcon.js +2 -2
  7. package/dist/ButtonIcon.js.map +1 -1
  8. package/dist/Checkbox.d.ts +1 -0
  9. package/dist/Checkbox.d.ts.map +1 -1
  10. package/dist/Checkbox.js.map +1 -1
  11. package/dist/DateTimePicker/DateTimeDisplayInput.d.ts +1 -1
  12. package/dist/DateTimePicker/DateTimeDisplayInput.js.map +1 -1
  13. package/dist/DateTimePicker.d.ts +6 -4
  14. package/dist/DateTimePicker.d.ts.map +1 -1
  15. package/dist/DateTimePicker.js +41 -30
  16. package/dist/DateTimePicker.js.map +1 -1
  17. package/dist/Drawer.d.ts +5 -0
  18. package/dist/Drawer.d.ts.map +1 -1
  19. package/dist/Drawer.js +3 -1
  20. package/dist/Drawer.js.map +1 -1
  21. package/dist/InputSearch.d.ts +2 -2
  22. package/dist/InputSearch.d.ts.map +1 -1
  23. package/dist/InputSearch.js +3 -3
  24. package/dist/InputSearch.js.map +1 -1
  25. package/dist/Kbd.d.ts.map +1 -1
  26. package/dist/Kbd.js.map +1 -1
  27. package/dist/MultiSelect/MultiSelectBase.d.ts.map +1 -1
  28. package/dist/MultiSelect/MultiSelectBase.js +1 -1
  29. package/dist/MultiSelect/MultiSelectBase.js.map +1 -1
  30. package/dist/RadialMenu.d.ts.map +1 -1
  31. package/dist/RadialMenu.js +3 -4
  32. package/dist/RadialMenu.js.map +1 -1
  33. package/dist/RadioButton.js +1 -1
  34. package/dist/RadioButton.js.map +1 -1
  35. package/dist/RadioGroup.d.ts +5 -4
  36. package/dist/RadioGroup.d.ts.map +1 -1
  37. package/dist/RadioGroup.js +15 -11
  38. package/dist/RadioGroup.js.map +1 -1
  39. package/dist/Select.d.ts +1 -0
  40. package/dist/Select.d.ts.map +1 -1
  41. package/dist/Select.js.map +1 -1
  42. package/dist/Switch.d.ts +1 -1
  43. package/dist/Switch.d.ts.map +1 -1
  44. package/dist/Switch.js +1 -1
  45. package/dist/Switch.js.map +1 -1
  46. package/dist/styles/horizon/colors.css +5 -3
  47. package/dist/styles/spectral.css +1 -1
  48. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"RadialMenu.js","names":[],"sources":["../src/components/RadialMenu/RadialMenu.tsx"],"sourcesContent":["import { ChevronDownIcon } from '@components/Icons'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@components/Tooltip/Tooltip'\nimport { cn } from '@utils/twUtils'\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState, type ReactNode, type Ref } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport interface RadialMenuItem {\n /** Optional longer description shown in a tooltip on hover, since labels are kept short. */\n description?: string\n destructive?: boolean\n disabled?: boolean\n icon: ReactNode\n id: string\n items?: RadialMenuItem[]\n label: string\n onSelect?: () => void\n}\n\nexport interface RadialMenuProps {\n ariaLabel: string\n backLabel?: string\n className?: string\n dataTestId?: string\n items: RadialMenuItem[]\n onClose: () => void\n position: { x: number; y: number } | null\n}\n\nconst BOUNCE_EASE = 'motion-safe:ease-[cubic-bezier(0.34,1.56,0.64,1)]'\n// Stacked soft shadows form a dense-but-soft halo so labels stay legible over any background.\nconst LABEL_SHADOW = '[text-shadow:0_0_5px_var(--color-text-inverted),0_0_5px_var(--color-text-inverted),0_0_10px_var(--color-text-inverted),0_0_10px_var(--color-text-inverted),0_0_16px_var(--color-text-inverted),0_0_16px_var(--color-text-inverted),0_0_22px_var(--color-text-inverted)]'\nconst CLOSE_MS = 260\nconst LEVEL_MS = 200\nconst LABEL_OFFSET = 30\nconst PRIMARY_RADIUS = 70\nconst SUB_RADIUS = 96\nconst ITEM_REACH = 44\nconst VIEWPORT_MARGIN = 16\n\n// When the menu is larger than the viewport (min > max), center it; otherwise clamp normally.\nconst clamp = (value: number, min: number, max: number) => (min > max ? (min + max) / 2 : Math.min(Math.max(value, min), max))\n\n// Double rAF so the collapsed \"from\" state paints before the transition target is applied.\nconst runAfterPaint = (callback: () => void) => {\n let inner = 0\n const outer = requestAnimationFrame(() => {\n inner = requestAnimationFrame(callback)\n })\n return () => {\n cancelAnimationFrame(outer)\n cancelAnimationFrame(inner)\n }\n}\n\nconst positionForIndex = (index: number, count: number, radius: number) => {\n const angle = -Math.PI / 2 + (index * 2 * Math.PI) / count\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius }\n}\n\n// Fanned across an arc centered straight up, so a sub-ring arches above its parent.\nconst SUB_ARC_CENTER = -Math.PI / 2\nconst SUB_ARC_SPAN = Math.PI * 0.7\nconst positionOnArc = (index: number, count: number, radius: number) => {\n const angle = count <= 1 ? SUB_ARC_CENTER : SUB_ARC_CENTER - SUB_ARC_SPAN / 2 + (index / (count - 1)) * SUB_ARC_SPAN\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius }\n}\n\nexport const RadialMenu = ({ ariaLabel, backLabel = 'Back', className, dataTestId, items, onClose, position, ref }: RadialMenuProps & { ref?: Ref<HTMLDivElement> }) => {\n const resolvedTestId = dataTestId ?? 'spectral-radial-menu'\n const containerRef = useRef<HTMLDivElement>(null)\n const levelTimersRef = useRef<number[]>([])\n const [activeSubmenuId, setActiveSubmenuId] = useState<string | null>(null)\n const [highlightIndex, setHighlightIndex] = useState(-1)\n // Items animate between `spreadOrigin` (collapsed) and `base + ring offset` (expanded).\n const [renderCenter, setRenderCenter] = useState(position)\n const [expanded, setExpanded] = useState(false)\n const [spreadOrigin, setSpreadOrigin] = useState<{ x: number; y: number }>({ x: 0, y: 0 })\n // Parent re-skinned as a back control, kept mounted so it swaps cleanly with the real item.\n const [backSkin, setBackSkin] = useState<{ item: RadialMenuItem; offset: { x: number; y: number } } | null>(null)\n // The returned item appears instantly at its spot (swapping in for the chevron) rather than fading.\n const [returningId, setReturningId] = useState<string | null>(null)\n\n const clearLevelTimers = useCallback(() => {\n levelTimersRef.current.forEach((id) => clearTimeout(id))\n levelTimersRef.current = []\n }, [])\n\n useEffect(() => {\n // Cancel any in-flight level-change timers so a stale callback can't mutate a new cycle.\n clearLevelTimers()\n if (position !== null) {\n setRenderCenter(position)\n setActiveSubmenuId(null)\n setHighlightIndex(-1)\n setSpreadOrigin({ x: 0, y: 0 })\n setBackSkin(null)\n setReturningId(null)\n setExpanded(false)\n return runAfterPaint(() => {\n setExpanded(true)\n containerRef.current?.focus()\n })\n }\n setExpanded(false)\n // Collapse to the center, not the lingering sub-ring origin, so close mirrors open.\n setSpreadOrigin({ x: 0, y: 0 })\n const timer = setTimeout(() => {\n setRenderCenter(null)\n setBackSkin(null)\n }, CLOSE_MS)\n return () => {\n clearTimeout(timer)\n }\n }, [clearLevelTimers, position])\n\n useEffect(() => {\n if (position === null) {\n return () => {}\n }\n window.addEventListener('scroll', onClose, true)\n window.addEventListener('resize', onClose)\n return () => {\n window.removeEventListener('scroll', onClose, true)\n window.removeEventListener('resize', onClose)\n }\n }, [position, onClose])\n\n const renderCenterRef = useRef(renderCenter)\n renderCenterRef.current = renderCenter\n useEffect(() => {\n if (renderCenterRef.current === null) {\n return () => {}\n }\n return runAfterPaint(() => {\n setExpanded(true)\n })\n }, [activeSubmenuId])\n\n useEffect(() => clearLevelTimers, [clearLevelTimers])\n\n const activeItems = useMemo(() => {\n if (activeSubmenuId === null) return items\n return items.find((item) => item.id === activeSubmenuId)?.items ?? items\n }, [activeSubmenuId, items])\n\n const activate = useCallback(\n (item: RadialMenuItem) => {\n if (item.disabled === true) return\n if (item.items && item.items.length > 0) {\n clearLevelTimers()\n const index = items.findIndex((candidate) => candidate.id === item.id)\n const offset = positionForIndex(index, items.length, PRIMARY_RADIUS)\n setSpreadOrigin(offset)\n setReturningId(null)\n setBackSkin({\n item: {\n ...item,\n description: undefined,\n icon: (\n <ChevronDownIcon\n className='rotate-90'\n size={20}\n />\n ),\n id: `${item.id}__back`,\n items: undefined,\n label: backLabel,\n },\n offset,\n })\n setActiveSubmenuId(item.id)\n setHighlightIndex(-1)\n setExpanded(false)\n return\n }\n item.onSelect?.()\n onClose()\n },\n [backLabel, clearLevelTimers, items, onClose],\n )\n\n const goBack = useCallback(() => {\n const returningSubmenuId = activeSubmenuId\n const index = items.findIndex((candidate) => candidate.id === returningSubmenuId)\n setSpreadOrigin(index >= 0 ? positionForIndex(index, items.length, PRIMARY_RADIUS) : { x: 0, y: 0 })\n setExpanded(false)\n setHighlightIndex(-1)\n clearLevelTimers()\n levelTimersRef.current = [\n window.setTimeout(() => {\n if (renderCenterRef.current === null) return\n setActiveSubmenuId(null)\n setBackSkin(null)\n setReturningId(returningSubmenuId)\n }, LEVEL_MS),\n window.setTimeout(() => {\n if (renderCenterRef.current !== null) {\n setReturningId(null)\n }\n }, LEVEL_MS + 350),\n ]\n }, [activeSubmenuId, clearLevelTimers, items])\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n const count = activeItems.length\n if (count === 0) return\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown': {\n event.preventDefault()\n setHighlightIndex((index) => (index < 0 ? 0 : (index + 1) % count))\n break\n }\n case 'ArrowLeft':\n case 'ArrowUp': {\n event.preventDefault()\n setHighlightIndex((index) => (index < 0 ? count - 1 : (index - 1 + count) % count))\n break\n }\n case 'Enter':\n case ' ': {\n event.preventDefault()\n const item = activeItems[highlightIndex]\n if (item) activate(item)\n break\n }\n case 'Escape': {\n event.preventDefault()\n if (activeSubmenuId === null) {\n onClose()\n } else {\n goBack()\n }\n break\n }\n case 'Tab': {\n event.preventDefault()\n break\n }\n default: {\n break\n }\n }\n },\n [activate, activeItems, activeSubmenuId, goBack, highlightIndex, onClose],\n )\n\n if (renderCenter === null || typeof document === 'undefined') {\n return null\n }\n\n const inSubmenu = activeSubmenuId !== null\n const parentIndex = inSubmenu ? items.findIndex((item) => item.id === activeSubmenuId) : -1\n const parentItem = parentIndex >= 0 ? (items[parentIndex] ?? null) : null\n const base = parentItem === null ? { x: 0, y: 0 } : positionForIndex(parentIndex, items.length, PRIMARY_RADIUS)\n const ringRadius = inSubmenu ? SUB_RADIUS : PRIMARY_RADIUS\n\n const reach = (inSubmenu ? PRIMARY_RADIUS + SUB_RADIUS : PRIMARY_RADIUS) + ITEM_REACH\n const cx = clamp(renderCenter.x, reach + VIEWPORT_MARGIN, window.innerWidth - reach - VIEWPORT_MARGIN)\n const cy = clamp(renderCenter.y, reach + VIEWPORT_MARGIN, window.innerHeight - reach - VIEWPORT_MARGIN)\n\n const renderSpoke = (options: { delay?: number; isActive?: boolean; isHighlighted?: boolean; item: RadialMenuItem; nonInteractive?: boolean; onClick: () => void; opacity: number; testId: string; x: number; y: number }) => {\n const { delay = 0, isActive = false, isHighlighted = false, item, nonInteractive = false, onClick, opacity, testId, x, y } = options\n const hasSubmenu = Boolean(item.items && item.items.length > 0)\n const circle = (\n <button\n aria-disabled={item.disabled}\n aria-haspopup={hasSubmenu ? 'menu' : undefined}\n aria-label={item.label}\n className={cn(\n 'size-12 absolute flex -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full',\n 'border border-border-primary bg-popover-bg text-popover-text shadow-elevation-3',\n 'hover:scale-110 hover:cursor-pointer hover:border-toggle-border hover:bg-level-three',\n 'motion-safe:transition-[left,top,opacity,transform] motion-safe:duration-300',\n BOUNCE_EASE,\n (isHighlighted || isActive) && 'border-toggle-border bg-level-three',\n isHighlighted && 'scale-110',\n item.destructive === true && 'text-danger-300',\n item.disabled === true && 'pointer-events-none hover:scale-100',\n nonInteractive && 'pointer-events-none',\n )}\n data-testid={testId}\n disabled={item.disabled}\n onClick={onClick}\n role='menuitem'\n style={{ left: x, opacity, top: y, transitionDelay: `${delay}ms` }}\n type='button'\n >\n {item.icon}\n {hasSubmenu && (\n <span\n aria-hidden\n className='size-4 -right-0.5 -top-0.5 font-semibold absolute flex items-center justify-center rounded-full border border-border-primary bg-level-four text-[9px] leading-none text-text-primary tabular-nums'\n data-testid={`${testId}-submenu-count`}\n >\n {item.items?.length}\n </span>\n )}\n </button>\n )\n return (\n <Fragment key={item.id}>\n {item.description ? (\n <Tooltip delayDuration={400}>\n <TooltipTrigger asChild>{circle}</TooltipTrigger>\n <TooltipContent>{item.description}</TooltipContent>\n </Tooltip>\n ) : (\n circle\n )}\n <span\n aria-hidden\n className={cn(\n 'w-20 font-medium leading-tight pointer-events-none absolute -translate-x-1/2 text-center text-[10px]',\n LABEL_SHADOW,\n 'motion-safe:transition-[left,top,opacity] motion-safe:duration-300',\n BOUNCE_EASE,\n item.destructive === true ? 'text-danger-300' : 'text-text-primary',\n )}\n data-testid={`${testId}-label`}\n style={{ left: x, opacity, top: y + LABEL_OFFSET, transitionDelay: `${delay}ms` }}\n >\n {item.label}\n </span>\n </Fragment>\n )\n }\n\n return createPortal(\n <div\n className='inset-0 fixed z-50 motion-safe:duration-150 motion-safe:animate-in motion-safe:fade-in-0'\n data-testid={resolvedTestId}\n onContextMenu={(event) => {\n event.preventDefault()\n }}\n onPointerDown={(event) => {\n if (event.target === event.currentTarget) {\n onClose()\n }\n }}\n ref={ref}\n >\n <div\n aria-label={ariaLabel}\n className={cn('focus-visible:ring-ring absolute origin-center focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none', className)}\n data-testid={`${resolvedTestId}-menu`}\n onKeyDown={handleKeyDown}\n ref={containerRef}\n role='menu'\n style={{ left: cx, top: cy }}\n tabIndex={-1}\n >\n {!inSubmenu && (\n <span\n aria-hidden\n className='size-1.5 absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-text-secondary opacity-60'\n data-testid={`${resolvedTestId}-center`}\n />\n )}\n {backSkin !== null &&\n renderSpoke({\n isActive: true,\n item: backSkin.item,\n nonInteractive: !inSubmenu,\n onClick: goBack,\n opacity: inSubmenu ? 1 : 0,\n testId: `${resolvedTestId}-back`,\n x: backSkin.offset.x,\n y: backSkin.offset.y,\n })}\n {activeItems.map((item, index) => {\n const offset = inSubmenu ? positionOnArc(index, activeItems.length, ringRadius) : positionForIndex(index, activeItems.length, ringRadius)\n const settled = expanded || item.id === returningId\n return renderSpoke({\n delay: item.id === returningId ? 0 : expanded ? index * 25 : 0,\n isHighlighted: index === highlightIndex,\n item,\n onClick: () => {\n activate(item)\n },\n opacity: settled ? (item.disabled === true ? 0.4 : 1) : 0,\n testId: `${resolvedTestId}-item`,\n x: settled ? base.x + offset.x : spreadOrigin.x,\n y: settled ? base.y + offset.y : spreadOrigin.y,\n })\n })}\n </div>\n </div>,\n document.body,\n )\n}\n\nRadialMenu.displayName = 'RadialMenu'\n"],"mappings":"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"}
1
+ {"version":3,"file":"RadialMenu.js","names":[],"sources":["../src/components/RadialMenu/RadialMenu.tsx"],"sourcesContent":["import { ChevronDownIcon } from '@components/Icons'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@components/Tooltip/Tooltip'\nimport { cn } from '@utils/twUtils'\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState, type ReactNode, type Ref } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport interface RadialMenuItem {\n /** Optional longer description shown in a tooltip on hover, since labels are kept short. */\n description?: string\n destructive?: boolean\n disabled?: boolean\n icon: ReactNode\n id: string\n items?: RadialMenuItem[]\n label: string\n onSelect?: () => void\n}\n\nexport interface RadialMenuProps {\n ariaLabel: string\n backLabel?: string\n className?: string\n dataTestId?: string\n items: RadialMenuItem[]\n onClose: () => void\n position: { x: number; y: number } | null\n}\n\nconst BOUNCE_EASE = 'motion-safe:ease-[cubic-bezier(0.34,1.56,0.64,1)]'\n// Stacked soft shadows form a dense-but-soft halo so labels stay legible over any background.\nconst LABEL_SHADOW = '[text-shadow:0_0_5px_var(--color-text-inverted),0_0_5px_var(--color-text-inverted),0_0_10px_var(--color-text-inverted),0_0_10px_var(--color-text-inverted),0_0_16px_var(--color-text-inverted),0_0_16px_var(--color-text-inverted),0_0_22px_var(--color-text-inverted)]'\nconst CLOSE_MS = 260\nconst LEVEL_MS = 200\nconst LABEL_OFFSET = 30\nconst PRIMARY_RADIUS = 70\nconst SUB_RADIUS = 96\nconst ITEM_REACH = 44\nconst VIEWPORT_MARGIN = 16\n\n// When the menu is larger than the viewport (min > max), center it; otherwise clamp normally.\nconst clamp = (value: number, min: number, max: number) => (min > max ? (min + max) / 2 : Math.min(Math.max(value, min), max))\n\n// Double rAF so the collapsed \"from\" state paints before the transition target is applied.\nconst runAfterPaint = (callback: () => void) => {\n let inner = 0\n const outer = requestAnimationFrame(() => {\n inner = requestAnimationFrame(callback)\n })\n return () => {\n cancelAnimationFrame(outer)\n cancelAnimationFrame(inner)\n }\n}\n\nconst positionForIndex = (index: number, count: number, radius: number) => {\n const angle = -Math.PI / 2 + (index * 2 * Math.PI) / count\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius }\n}\n\n// Fanned across an arc centered straight up, so a sub-ring arches above its parent.\nconst SUB_ARC_CENTER = -Math.PI / 2\nconst SUB_ARC_SPAN = Math.PI * 0.7\nconst positionOnArc = (index: number, count: number, radius: number) => {\n const angle = count <= 1 ? SUB_ARC_CENTER : SUB_ARC_CENTER - SUB_ARC_SPAN / 2 + (index / (count - 1)) * SUB_ARC_SPAN\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius }\n}\n\nexport const RadialMenu = ({ ariaLabel, backLabel = 'Back', className, dataTestId = 'spectral-radial-menu', items, onClose, position, ref }: RadialMenuProps & { ref?: Ref<HTMLDivElement> }) => {\n const containerRef = useRef<HTMLDivElement>(null)\n const levelTimersRef = useRef<number[]>([])\n const [activeSubmenuId, setActiveSubmenuId] = useState<string | null>(null)\n const [highlightIndex, setHighlightIndex] = useState(-1)\n // Items animate between `spreadOrigin` (collapsed) and `base + ring offset` (expanded).\n const [renderCenter, setRenderCenter] = useState(position)\n const [expanded, setExpanded] = useState(false)\n const [spreadOrigin, setSpreadOrigin] = useState<{ x: number; y: number }>({ x: 0, y: 0 })\n // Parent re-skinned as a back control, kept mounted so it swaps cleanly with the real item.\n const [backSkin, setBackSkin] = useState<{ item: RadialMenuItem; offset: { x: number; y: number } } | null>(null)\n // The returned item appears instantly at its spot (swapping in for the chevron) rather than fading.\n const [returningId, setReturningId] = useState<string | null>(null)\n\n const clearLevelTimers = useCallback(() => {\n levelTimersRef.current.forEach((id) => clearTimeout(id))\n levelTimersRef.current = []\n }, [])\n\n useEffect(() => {\n // Cancel any in-flight level-change timers so a stale callback can't mutate a new cycle.\n clearLevelTimers()\n if (position !== null) {\n setRenderCenter(position)\n setActiveSubmenuId(null)\n setHighlightIndex(-1)\n setSpreadOrigin({ x: 0, y: 0 })\n setBackSkin(null)\n setReturningId(null)\n setExpanded(false)\n return runAfterPaint(() => {\n setExpanded(true)\n containerRef.current?.focus()\n })\n }\n setExpanded(false)\n // Collapse to the center, not the lingering sub-ring origin, so close mirrors open.\n setSpreadOrigin({ x: 0, y: 0 })\n const timer = setTimeout(() => {\n setRenderCenter(null)\n setBackSkin(null)\n }, CLOSE_MS)\n return () => {\n clearTimeout(timer)\n }\n }, [clearLevelTimers, position])\n\n useEffect(() => {\n if (position === null) {\n return () => {}\n }\n window.addEventListener('scroll', onClose, true)\n window.addEventListener('resize', onClose)\n return () => {\n window.removeEventListener('scroll', onClose, true)\n window.removeEventListener('resize', onClose)\n }\n }, [position, onClose])\n\n const renderCenterRef = useRef(renderCenter)\n renderCenterRef.current = renderCenter\n useEffect(() => {\n if (renderCenterRef.current === null) {\n return () => {}\n }\n return runAfterPaint(() => {\n setExpanded(true)\n })\n }, [activeSubmenuId])\n\n useEffect(() => clearLevelTimers, [clearLevelTimers])\n\n const activeItems = useMemo(() => {\n if (activeSubmenuId === null) return items\n return items.find((item) => item.id === activeSubmenuId)?.items ?? items\n }, [activeSubmenuId, items])\n\n const activate = useCallback(\n (item: RadialMenuItem) => {\n if (item.disabled === true) return\n if (item.items && item.items.length > 0) {\n clearLevelTimers()\n const index = items.findIndex((candidate) => candidate.id === item.id)\n const offset = positionForIndex(index, items.length, PRIMARY_RADIUS)\n setSpreadOrigin(offset)\n setReturningId(null)\n setBackSkin({\n item: {\n ...item,\n description: undefined,\n icon: (\n <ChevronDownIcon\n className='rotate-90'\n size={20}\n />\n ),\n id: `${item.id}__back`,\n items: undefined,\n label: backLabel,\n },\n offset,\n })\n setActiveSubmenuId(item.id)\n setHighlightIndex(-1)\n setExpanded(false)\n return\n }\n item.onSelect?.()\n onClose()\n },\n [backLabel, clearLevelTimers, items, onClose],\n )\n\n const goBack = useCallback(() => {\n const returningSubmenuId = activeSubmenuId\n const index = items.findIndex((candidate) => candidate.id === returningSubmenuId)\n setSpreadOrigin(index >= 0 ? positionForIndex(index, items.length, PRIMARY_RADIUS) : { x: 0, y: 0 })\n setExpanded(false)\n setHighlightIndex(-1)\n clearLevelTimers()\n levelTimersRef.current = [\n window.setTimeout(() => {\n if (renderCenterRef.current === null) return\n setActiveSubmenuId(null)\n setBackSkin(null)\n setReturningId(returningSubmenuId)\n }, LEVEL_MS),\n window.setTimeout(() => {\n if (renderCenterRef.current !== null) {\n setReturningId(null)\n }\n }, LEVEL_MS + 350),\n ]\n }, [activeSubmenuId, clearLevelTimers, items])\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n const count = activeItems.length\n if (count === 0) return\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown': {\n event.preventDefault()\n setHighlightIndex((index) => (index < 0 ? 0 : (index + 1) % count))\n break\n }\n case 'ArrowLeft':\n case 'ArrowUp': {\n event.preventDefault()\n setHighlightIndex((index) => (index < 0 ? count - 1 : (index - 1 + count) % count))\n break\n }\n case 'Enter':\n case ' ': {\n event.preventDefault()\n const item = activeItems[highlightIndex]\n if (item) activate(item)\n break\n }\n case 'Escape': {\n event.preventDefault()\n if (activeSubmenuId === null) {\n onClose()\n } else {\n goBack()\n }\n break\n }\n case 'Tab': {\n event.preventDefault()\n break\n }\n default: {\n break\n }\n }\n },\n [activate, activeItems, activeSubmenuId, goBack, highlightIndex, onClose],\n )\n\n if (renderCenter === null || typeof document === 'undefined') {\n return null\n }\n\n const inSubmenu = activeSubmenuId !== null\n const parentIndex = inSubmenu ? items.findIndex((item) => item.id === activeSubmenuId) : -1\n const parentItem = parentIndex >= 0 ? (items[parentIndex] ?? null) : null\n const base = parentItem === null ? { x: 0, y: 0 } : positionForIndex(parentIndex, items.length, PRIMARY_RADIUS)\n const ringRadius = inSubmenu ? SUB_RADIUS : PRIMARY_RADIUS\n\n const reach = (inSubmenu ? PRIMARY_RADIUS + SUB_RADIUS : PRIMARY_RADIUS) + ITEM_REACH\n const cx = clamp(renderCenter.x, reach + VIEWPORT_MARGIN, window.innerWidth - reach - VIEWPORT_MARGIN)\n const cy = clamp(renderCenter.y, reach + VIEWPORT_MARGIN, window.innerHeight - reach - VIEWPORT_MARGIN)\n\n const renderSpoke = (options: { delay?: number; isActive?: boolean; isHighlighted?: boolean; item: RadialMenuItem; nonInteractive?: boolean; onClick: () => void; opacity: number; testId: string; x: number; y: number }) => {\n const { delay = 0, isActive = false, isHighlighted = false, item, nonInteractive = false, onClick, opacity, testId, x, y } = options\n const hasSubmenu = Boolean(item.items && item.items.length > 0)\n const circle = (\n <button\n aria-disabled={item.disabled}\n aria-haspopup={hasSubmenu ? 'menu' : undefined}\n aria-label={item.label}\n className={cn(\n 'size-12 absolute flex -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full',\n 'border border-border-primary bg-popover-bg text-popover-text shadow-elevation-3',\n 'hover:scale-110 hover:cursor-pointer hover:border-toggle-border hover:bg-level-three',\n 'motion-safe:transition-[left,top,opacity,transform] motion-safe:duration-300',\n BOUNCE_EASE,\n (isHighlighted || isActive) && 'border-toggle-border bg-level-three',\n isHighlighted && 'scale-110',\n item.destructive === true && 'text-danger-300',\n item.disabled === true && 'pointer-events-none hover:scale-100',\n nonInteractive && 'pointer-events-none',\n )}\n data-testid={testId}\n disabled={item.disabled}\n onClick={onClick}\n role='menuitem'\n style={{ left: x, opacity, top: y, transitionDelay: `${delay}ms` }}\n type='button'\n >\n {item.icon}\n {hasSubmenu && (\n <span\n aria-hidden\n className='size-4 -right-0.5 -top-0.5 font-semibold absolute flex items-center justify-center rounded-full border border-border-primary bg-level-four text-[9px] leading-none text-text-primary tabular-nums'\n data-testid={`${testId}-submenu-count`}\n >\n {item.items?.length}\n </span>\n )}\n </button>\n )\n return (\n <Fragment key={item.id}>\n {item.description ? (\n <Tooltip delayDuration={400}>\n <TooltipTrigger asChild>{circle}</TooltipTrigger>\n <TooltipContent>{item.description}</TooltipContent>\n </Tooltip>\n ) : (\n circle\n )}\n <span\n aria-hidden\n className={cn(\n 'w-20 font-medium leading-tight pointer-events-none absolute -translate-x-1/2 text-center text-[10px]',\n LABEL_SHADOW,\n 'motion-safe:transition-[left,top,opacity] motion-safe:duration-300',\n BOUNCE_EASE,\n item.destructive === true ? 'text-danger-300' : 'text-text-primary',\n )}\n data-testid={`${testId}-label`}\n style={{ left: x, opacity, top: y + LABEL_OFFSET, transitionDelay: `${delay}ms` }}\n >\n {item.label}\n </span>\n </Fragment>\n )\n }\n\n return createPortal(\n <div\n className='inset-0 fixed z-50 motion-safe:duration-150 motion-safe:animate-in motion-safe:fade-in-0'\n data-testid={dataTestId}\n onContextMenu={(event) => {\n event.preventDefault()\n }}\n onPointerDown={(event) => {\n if (event.target === event.currentTarget) {\n onClose()\n }\n }}\n ref={ref}\n >\n <div\n aria-label={ariaLabel}\n className={cn('focus-visible:ring-ring absolute origin-center focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none', className)}\n data-testid={`${dataTestId}-menu`}\n onKeyDown={handleKeyDown}\n ref={containerRef}\n role='menu'\n style={{ left: cx, top: cy }}\n tabIndex={-1}\n >\n {!inSubmenu && (\n <span\n aria-hidden\n className='size-1.5 absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-text-secondary opacity-60'\n data-testid={`${dataTestId}-center`}\n />\n )}\n {backSkin !== null &&\n renderSpoke({\n isActive: true,\n item: backSkin.item,\n nonInteractive: !inSubmenu,\n onClick: goBack,\n opacity: inSubmenu ? 1 : 0,\n testId: `${dataTestId}-back`,\n x: backSkin.offset.x,\n y: backSkin.offset.y,\n })}\n {activeItems.map((item, index) => {\n const offset = inSubmenu ? positionOnArc(index, activeItems.length, ringRadius) : positionForIndex(index, activeItems.length, ringRadius)\n const settled = expanded || item.id === returningId\n return renderSpoke({\n delay: item.id === returningId ? 0 : expanded ? index * 25 : 0,\n isHighlighted: index === highlightIndex,\n item,\n onClick: () => {\n activate(item)\n },\n opacity: settled ? (item.disabled === true ? 0.4 : 1) : 0,\n testId: `${dataTestId}-item`,\n x: settled ? base.x + offset.x : spreadOrigin.x,\n y: settled ? base.y + offset.y : spreadOrigin.y,\n })\n })}\n </div>\n </div>,\n document.body,\n )\n}\n\nRadialMenu.displayName = 'RadialMenu'\n"],"mappings":"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"}
@@ -18,7 +18,7 @@ const RadioButton = ({ asChild = false, activeColor = "default", activeTextColor
18
18
  className: cn(`gap-2 rounded-md text-sm font-medium h-9 px-3 min-w-9 [&_svg:not([class*='size-']):not([width]):not([height])]:size-4 inline-flex items-center justify-center border border-toggle-border
19
19
  bg-toggle-bg text-toggle-text shadow-none transition-colors hover:cursor-pointer hover:border-toggle-border--hover hover:bg-toggle-bg--hover hover:text-toggle-text--hover focus-visible:z-10
20
20
  focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent active:border-toggle-border--active active:bg-toggle-bg--active active:text-toggle-text--active
21
- disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=outline]:[--color-toggle-border:var(--color-toggle-outline-border)`, expanded && "w-full", isKeptActive && "data-[state=on]:border-toggle-border--active data-[state=on]:bg-toggle-bg--active data-[state=on]:text-toggle-text--active", className),
21
+ disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=outline]:[--color-toggle-border:var(--color-toggle-outline-border)`, expanded && "w-full", variant === "outline" && "border-radio-button-outline-border", isKeptActive && "data-[state=on]:border-toggle-border--active data-[state=on]:bg-toggle-bg--active data-[state=on]:text-toggle-text--active", className),
22
22
  style: {
23
23
  ...getActiveColorStyle(activeColor, activeTextColor),
24
24
  ...style
@@ -1 +1 @@
1
- {"version":3,"file":"RadioButton.js","names":[],"sources":["../src/components/RadioButton/RadioButton.tsx"],"sourcesContent":["import { Slot, type AsChildProp } from '@primitives/slot'\nimport { getActiveColorStyle, type ActiveColor, type ActiveTextColor } from '@utils/activeColorStyle'\nimport { cn } from '@utils/twUtils'\nimport { type ButtonHTMLAttributes, type MouseEvent, type ReactNode, type Ref } from 'react'\n\nexport interface RadioButtonProps extends AsChildProp, Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onSelect' | 'type'> {\n activeColor?: ActiveColor\n activeTextColor?: ActiveTextColor\n checked?: boolean\n children: ReactNode\n expanded?: boolean\n isKeptActive?: boolean\n onCheckedChange?: (checked: boolean) => void\n onSelect?: (checked: boolean) => void\n variant?: 'default' | 'outline'\n}\n\nexport const RadioButton = ({\n asChild = false,\n activeColor = 'default',\n activeTextColor,\n checked = false,\n children,\n className,\n disabled = false,\n expanded = false,\n isKeptActive = false,\n onCheckedChange,\n onClick,\n onSelect,\n ref,\n variant = 'default',\n style,\n ...rest\n}: RadioButtonProps & {\n ref?: Ref<HTMLButtonElement>\n}) => {\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n onClick?.(event)\n if (event.defaultPrevented || disabled) return\n\n onCheckedChange?.(true)\n onSelect?.(true)\n }\n\n const baseProps = {\n ...rest,\n className: cn(\n `gap-2 rounded-md text-sm font-medium h-9 px-3 min-w-9 [&_svg:not([class*='size-']):not([width]):not([height])]:size-4 inline-flex items-center justify-center border border-toggle-border\n bg-toggle-bg text-toggle-text shadow-none transition-colors hover:cursor-pointer hover:border-toggle-border--hover hover:bg-toggle-bg--hover hover:text-toggle-text--hover focus-visible:z-10\n focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent active:border-toggle-border--active active:bg-toggle-bg--active active:text-toggle-text--active\n disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=outline]:[--color-toggle-border:var(--color-toggle-outline-border)`,\n expanded && 'w-full',\n isKeptActive && 'data-[state=on]:border-toggle-border--active data-[state=on]:bg-toggle-bg--active data-[state=on]:text-toggle-text--active',\n className,\n ),\n style: { ...getActiveColorStyle(activeColor, activeTextColor), ...style },\n disabled,\n onClick: handleClick,\n role: 'radio',\n 'aria-checked': checked,\n 'data-state': checked ? 'on' : 'off',\n 'data-testid': 'spectral-radio-button',\n 'data-variant': variant,\n }\n\n if (asChild) {\n return (\n <Slot ref={ref as Ref<HTMLElement>} {...baseProps}>\n {children}\n </Slot>\n )\n }\n\n return (\n <button ref={ref} {...baseProps} type='button'>\n {children}\n </button>\n )\n}\n\nRadioButton.displayName = 'RadioButton'\n"],"mappings":";;;;;;;;AAiBA,MAAa,eAAe,EAC1B,UAAU,OACV,cAAc,WACd,iBACA,UAAU,OACV,UACA,WACA,WAAW,OACX,WAAW,OACX,eAAe,OACf,iBACA,SACA,UACA,KACA,UAAU,WACV,OACA,GAAG,WAGC;CACJ,MAAM,eAAe,UAAyC;AAC5D,YAAU,MAAM;AAChB,MAAI,MAAM,oBAAoB,SAAU;AAExC,oBAAkB,KAAK;AACvB,aAAW,KAAK;;CAGlB,MAAM,YAAY;EAChB,GAAG;EACH,WAAW,GACT;;;mNAIA,YAAY,UACZ,gBAAgB,8HAChB,UACD;EACD,OAAO;GAAE,GAAG,oBAAoB,aAAa,gBAAgB;GAAE,GAAG;GAAO;EACzE;EACA,SAAS;EACT,MAAM;EACN,gBAAgB;EAChB,cAAc,UAAU,OAAO;EAC/B,eAAe;EACf,gBAAgB;EACjB;AAED,KAAI,QACF,QACE,oBAAC,MAAD;EAAW;EAAyB,GAAI;EACrC;EACI;AAIX,QACE,oBAAC,UAAD;EAAa;EAAK,GAAI;EAAW,MAAK;EACnC;EACM;;AAIb,YAAY,cAAc"}
1
+ {"version":3,"file":"RadioButton.js","names":[],"sources":["../src/components/RadioButton/RadioButton.tsx"],"sourcesContent":["import { Slot, type AsChildProp } from '@primitives/slot'\nimport { getActiveColorStyle, type ActiveColor, type ActiveTextColor } from '@utils/activeColorStyle'\nimport { cn } from '@utils/twUtils'\nimport { type ButtonHTMLAttributes, type MouseEvent, type ReactNode, type Ref } from 'react'\n\nexport interface RadioButtonProps extends AsChildProp, Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onSelect' | 'type'> {\n activeColor?: ActiveColor\n activeTextColor?: ActiveTextColor\n checked?: boolean\n children: ReactNode\n expanded?: boolean\n isKeptActive?: boolean\n onCheckedChange?: (checked: boolean) => void\n onSelect?: (checked: boolean) => void\n variant?: 'default' | 'outline'\n}\n\nexport const RadioButton = ({\n asChild = false,\n activeColor = 'default',\n activeTextColor,\n checked = false,\n children,\n className,\n disabled = false,\n expanded = false,\n isKeptActive = false,\n onCheckedChange,\n onClick,\n onSelect,\n ref,\n variant = 'default',\n style,\n ...rest\n}: RadioButtonProps & {\n ref?: Ref<HTMLButtonElement>\n}) => {\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n onClick?.(event)\n if (event.defaultPrevented || disabled) return\n\n onCheckedChange?.(true)\n onSelect?.(true)\n }\n\n const baseProps = {\n ...rest,\n className: cn(\n `gap-2 rounded-md text-sm font-medium h-9 px-3 min-w-9 [&_svg:not([class*='size-']):not([width]):not([height])]:size-4 inline-flex items-center justify-center border border-toggle-border\n bg-toggle-bg text-toggle-text shadow-none transition-colors hover:cursor-pointer hover:border-toggle-border--hover hover:bg-toggle-bg--hover hover:text-toggle-text--hover focus-visible:z-10\n focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent active:border-toggle-border--active active:bg-toggle-bg--active active:text-toggle-text--active\n disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=outline]:[--color-toggle-border:var(--color-toggle-outline-border)`,\n expanded && 'w-full',\n variant === 'outline' && 'border-radio-button-outline-border',\n isKeptActive && 'data-[state=on]:border-toggle-border--active data-[state=on]:bg-toggle-bg--active data-[state=on]:text-toggle-text--active',\n className,\n ),\n style: { ...getActiveColorStyle(activeColor, activeTextColor), ...style },\n disabled,\n onClick: handleClick,\n role: 'radio',\n 'aria-checked': checked,\n 'data-state': checked ? 'on' : 'off',\n 'data-testid': 'spectral-radio-button',\n 'data-variant': variant,\n }\n\n if (asChild) {\n return (\n <Slot ref={ref as Ref<HTMLElement>} {...baseProps}>\n {children}\n </Slot>\n )\n }\n\n return (\n <button ref={ref} {...baseProps} type='button'>\n {children}\n </button>\n )\n}\n\nRadioButton.displayName = 'RadioButton'\n"],"mappings":";;;;;;;;AAiBA,MAAa,eAAe,EAC1B,UAAU,OACV,cAAc,WACd,iBACA,UAAU,OACV,UACA,WACA,WAAW,OACX,WAAW,OACX,eAAe,OACf,iBACA,SACA,UACA,KACA,UAAU,WACV,OACA,GAAG,WAGC;CACJ,MAAM,eAAe,UAAyC;AAC5D,YAAU,MAAM;AAChB,MAAI,MAAM,oBAAoB,SAAU;AAExC,oBAAkB,KAAK;AACvB,aAAW,KAAK;;CAGlB,MAAM,YAAY;EAChB,GAAG;EACH,WAAW,GACT;;;mNAIA,YAAY,UACZ,YAAY,aAAa,sCACzB,gBAAgB,8HAChB,UACD;EACD,OAAO;GAAE,GAAG,oBAAoB,aAAa,gBAAgB;GAAE,GAAG;GAAO;EACzE;EACA,SAAS;EACT,MAAM;EACN,gBAAgB;EAChB,cAAc,UAAU,OAAO;EAC/B,eAAe;EACf,gBAAgB;EACjB;AAED,KAAI,QACF,QACE,oBAAC,MAAD;EAAW;EAAyB,GAAI;EACrC;EACI;AAIX,QACE,oBAAC,UAAD;EAAa;EAAK,GAAI;EAAW,MAAK;EACnC;EACM;;AAIb,YAAY,cAAc"}
@@ -7,8 +7,6 @@ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
7
7
  //#region src/components/RadioGroup/RadioGroup.d.ts
8
8
  type RadioGroupVariant = 'default' | 'unstyled';
9
9
  interface RadioGroupProps extends Omit<ComponentProps<typeof RadioGroupPrimitive.Root>, 'onChange' | 'disabled'> {
10
- 'aria-describedby'?: string;
11
- 'aria-label'?: string;
12
10
  className?: string;
13
11
  disabled?: boolean | string[];
14
12
  errorMessage?: BaseFormFieldProps['errorMessage'];
@@ -16,14 +14,17 @@ interface RadioGroupProps extends Omit<ComponentProps<typeof RadioGroupPrimitive
16
14
  messageReserveLines?: number;
17
15
  messageReserveSpace?: boolean;
18
16
  name: string;
19
- onChange?: (selected: string) => void;
20
- onValueChange: (selected: string) => void;
17
+ onChange: (selected: string) => void;
18
+ /** @deprecated Use `onChange` instead. `onValueChange` will be removed in a future release. */
19
+ onValueChange?: (selected: string) => void;
21
20
  orientation?: 'horizontal' | 'vertical';
22
21
  required?: boolean;
23
22
  selected?: string;
24
23
  state?: FormFieldState;
25
24
  variant?: RadioGroupVariant;
26
25
  warningMessage?: BaseFormFieldProps['errorMessage'];
26
+ 'aria-describedby'?: string;
27
+ 'aria-label'?: string;
27
28
  }
28
29
  interface RadioGroupItemProps extends ComponentProps<typeof RadioGroupPrimitive.Item> {
29
30
  children?: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"RadioGroup.d.ts","names":[],"sources":["../src/components/RadioGroup/RadioGroup.tsx"],"mappings":";;;;;;;KAeK,iBAAA;AAAA,UAEY,eAAA,SAAwB,IAAA,CAAK,cAAA,QAAsB,mBAAA,CAAoB,IAAA;EACtF,kBAAA;EACA,YAAA;EACA,SAAA;EACA,QAAA;EACA,YAAA,GAAe,kBAAA;EACf,aAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,QAAA,IAAY,QAAA;EACZ,aAAA,GAAgB,QAAA;EAChB,WAAA;EACA,QAAA;EACA,QAAA;EACA,KAAA,GAAQ,cAAA;EACR,OAAA,GAAU,iBAAA;EACV,cAAA,GAAiB,kBAAA;AAAA;AAAA,UAGF,mBAAA,SAA4B,cAAA,QAAsB,mBAAA,CAAoB,IAAA;EACrF,QAAA,GAAW,SAAA;EACX,SAAA;EACA,WAAA,YAAuB,SAAA;EACvB,EAAA;EACA,KAAA;AAAA;AAAA,iBACD,UAAA,CAqBC,QAAA,EAAU,eAAA;EACR,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,mBAAA,CAAoB,IAAA;AAAA,IAEnD,YAAA;AAAA,kBAAY,UAAA;EAAA;;;EAqJb,QAAA;EACA,SAAA;EACA,QAAA;EACA,GAAA;EACA,KAAA;EAAA,GACG;AAAA,GACF,mBAAA;EACD,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,mBAAA,CAAoB,IAAA;AAAA,IAChD,YAAA;AAAA,kBAAY,cAAA;EAAA;;;EAgCd,GAAA;EACA,SAAA;EAAA,GACG;AAAA,GACF,cAAA,QAAsB,KAAA;EACvB,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,KAAA;AAAA,IAC5B,YAAA;AAAA,kBAAY,eAAA;EAAA"}
1
+ {"version":3,"file":"RadioGroup.d.ts","names":[],"sources":["../src/components/RadioGroup/RadioGroup.tsx"],"mappings":";;;;;;;KAeK,iBAAA;AAAA,UAEY,eAAA,SAAwB,IAAA,CAAK,cAAA,QAAsB,mBAAA,CAAoB,IAAA;EACtF,SAAA;EACA,QAAA;EACA,YAAA,GAAe,kBAAA;EACf,aAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,QAAA,GAAW,QAAA;EARuD;EAUlE,aAAA,IAAiB,QAAA;EACjB,WAAA;EACA,QAAA;EACA,QAAA;EACA,KAAA,GAAQ,cAAA;EACR,OAAA,GAAU,iBAAA;EACV,cAAA,GAAiB,kBAAA;EACjB,kBAAA;EACA,YAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,cAAA,QAAsB,mBAAA,CAAoB,IAAA;EACrF,QAAA,GAAW,SAAA;EACX,SAAA;EACA,WAAA,YAAuB,SAAA;EACvB,EAAA;EACA,KAAA;AAAA;AAAA,iBACD,UAAA,CAqBC,QAAA,EAAU,eAAA;EACR,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,mBAAA,CAAoB,IAAA;AAAA,IAEnD,YAAA;AAAA,kBAAY,UAAA;EAAA;;;EAmIb,QAAA;EACA,SAAA;EACA,QAAA;EACA,GAAA;EACA,KAAA;EAAA,GACG;AAAA,GACF,mBAAA;EACD,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,mBAAA,CAAoB,IAAA;AAAA,IAChD,YAAA;AAAA,kBAAY,cAAA;EAAA;;;EAiCd,GAAA;EACA,SAAA;EAAA,GACG;AAAA,GACF,cAAA,QAAsB,KAAA;EACvB,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,KAAA;AAAA,IAC5B,YAAA;AAAA,kBAAY,eAAA;EAAA"}
@@ -3,7 +3,7 @@ import { cn } from "./utils/twUtils.js";
3
3
  import { ErrorMessage, WarningMessage } from "./FormFieldMessage.js";
4
4
  import { getErrorMessageId, getWarningMessageId, useFormFieldId } from "./utils/formFieldUtils.js";
5
5
  import { Label } from "./Label.js";
6
- import { createContext, memo, useContext, useId, useMemo } from "react";
6
+ import { Children, createContext, memo, useContext, useId, useMemo } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
9
9
  import { AnimatePresence, motion, useReducedMotion } from "motion/react";
@@ -25,8 +25,8 @@ const RadioGroup = (allProps) => {
25
25
  const warningMessageId = getWarningMessageId(groupId);
26
26
  const messageId = state === "error" && errorMessage ? errorMessageId : state === "warning" && warningMessage ? warningMessageId : void 0;
27
27
  const handleValueChange = (nextValue) => {
28
- onValueChange(nextValue);
29
- onChange?.(nextValue);
28
+ onChange(nextValue);
29
+ onValueChange?.(nextValue);
30
30
  };
31
31
  const contextValue = useMemo(() => ({
32
32
  disabledValues: Array.isArray(disabled) ? disabled : [],
@@ -49,12 +49,12 @@ const RadioGroup = (allProps) => {
49
49
  className: "flex w-full flex-col gap-1.5",
50
50
  children: [
51
51
  /* @__PURE__ */ jsx(RadioGroupPrimitive.Root, {
52
- className: cn("flex w-full text-text-primary", orientation === "vertical" ? "gap-4 flex-col" : "gap-5 flex-row", variant === "unstyled" && "gap-2.5 w-fit", className),
53
- "data-state": state,
54
- id: groupId,
55
52
  "aria-invalid": state === "error" ? true : void 0,
56
53
  "aria-describedby": [messageId, ariaDescribedBy].filter(Boolean).join(" ") || void 0,
54
+ className: cn("group/radio-field flex w-full text-text-primary", orientation === "vertical" ? "gap-4 flex-col" : "gap-5 flex-row", variant === "unstyled" && "gap-2.5 w-fit", className),
55
+ "data-state": state,
57
56
  disabled: contextValue.groupDisabled,
57
+ id: groupId,
58
58
  name,
59
59
  onValueChange: handleValueChange,
60
60
  ref,
@@ -85,9 +85,12 @@ const DEFAULT_TRANSITION = {
85
85
  stiffness: 200,
86
86
  damping: 16
87
87
  };
88
+ const RADIO_BUTTON_CLASSES = cn("size-4.5 border-border-subtle relative aspect-square cursor-pointer rounded-full border-2 bg-radio-bg transition-colors hover:border-radio-border--hover", "focus:outline-none focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent data-[state=checked]:border-radio-border--selected data-[state=checked]:bg-radio-bg", "group-data-[state=error]/radio-field:data-[state=unchecked]:border-danger-400 group-data-[state=error]/radio-field:data-[state=unchecked]:hover:border-danger-500 group-data-[state=error]/radio-field:focus-visible:outline-danger-400", "group-data-[state=warning]/radio-field:data-[state=unchecked]:border-warning-400 group-data-[state=warning]/radio-field:data-[state=unchecked]:hover:border-warning-500 group-data-[state=warning]/radio-field:focus-visible:outline-warning-400");
88
89
  const RadioButton = memo(({ className, id, isDisabled, ref, transition = DEFAULT_TRANSITION, value, ...props }) => {
89
- if (useReducedMotion()) return /* @__PURE__ */ jsx(RadioGroupPrimitive.Item, {
90
- className: cn("h-4.5 w-4.5 border-border-subtle ring-black relative aspect-square cursor-pointer rounded-full border-2 bg-radio-bg transition-colors", "hover:border-radio-border--hover focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent", "data-[state=checked]:border-radio-border--selected data-[state=checked]:bg-radio-bg", isDisabled && DISABLED_STYLES, className),
90
+ const prefersReducedMotion = useReducedMotion();
91
+ const buttonClassName = cn(RADIO_BUTTON_CLASSES, isDisabled && DISABLED_STYLES, className);
92
+ if (prefersReducedMotion) return /* @__PURE__ */ jsx(RadioGroupPrimitive.Item, {
93
+ className: buttonClassName,
91
94
  disabled: isDisabled,
92
95
  id,
93
96
  ref,
@@ -102,7 +105,7 @@ const RadioButton = memo(({ className, id, isDisabled, ref, transition = DEFAULT
102
105
  asChild: true,
103
106
  ...props,
104
107
  children: /* @__PURE__ */ jsx(motion.button, {
105
- className: cn("h-4.5 w-4.5 border-border-subtle ring-black relative aspect-square cursor-pointer rounded-full border-2 bg-radio-bg transition-colors", "hover:border-radio-border--hover focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent", "data-[state=checked]:border-radio-border--selected data-[state=checked]:bg-radio-bg", isDisabled && DISABLED_STYLES, className),
108
+ className: buttonClassName,
106
109
  ref,
107
110
  whileHover: { scale: 1.05 },
108
111
  whileTap: { scale: .95 },
@@ -135,6 +138,7 @@ const RadioGroupItem = ({ children, className, disabled, ref, value, ...props })
135
138
  const stringValue = value.toString();
136
139
  const id = props.id?.toString() ?? `${stringValue}-${generatedId}`;
137
140
  const isDisabled = groupDisabled || disabledValues.includes(stringValue) || Boolean(disabled);
141
+ const isTextContent = Children.toArray(children).every((child) => typeof child === "string" || typeof child === "number");
138
142
  if (variant === "unstyled") return /* @__PURE__ */ jsx(RadioGroupPrimitive.Item, {
139
143
  asChild: true,
140
144
  disabled: isDisabled,
@@ -143,7 +147,7 @@ const RadioGroupItem = ({ children, className, disabled, ref, value, ...props })
143
147
  value: stringValue,
144
148
  ...props,
145
149
  children: /* @__PURE__ */ jsx(Label, {
146
- className: cn("rounded flex h-fit w-fit border-2 border-transparent data-[state=checked]:border-radio-border--selected", isDisabled && DISABLED_STYLES, itemClassName, className),
150
+ className: cn("rounded cursor-pointer flex h-fit w-fit border-2 border-transparent data-[state=checked]:border-radio-border--selected [&_*]:cursor-[inherit]", isTextContent && "p-1.5", isDisabled && DISABLED_STYLES, itemClassName, className),
147
151
  htmlFor: id,
148
152
  children
149
153
  })
@@ -157,7 +161,7 @@ const RadioGroupItem = ({ children, className, disabled, ref, value, ...props })
157
161
  isDisabled,
158
162
  ...props
159
163
  }), children && /* @__PURE__ */ jsx(Label, {
160
- className: cn("text-md font-normal cursor-pointer", orientation === "vertical" ? "ml-2" : "ml-1"),
164
+ className: cn("text-md font-normal cursor-pointer", orientation === "vertical" ? "ml-2" : "ml-1.5"),
161
165
  htmlFor: id,
162
166
  children
163
167
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"RadioGroup.js","names":[],"sources":["../src/components/RadioGroup/RadioGroup.tsx"],"sourcesContent":["import { Label } from '@components/Label/Label'\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\nimport {\n ErrorMessage,\n getErrorMessageId,\n getWarningMessageId,\n useFormFieldId,\n WarningMessage,\n type BaseFormFieldProps,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { AnimatePresence, motion, useReducedMotion, type Transition } from 'motion/react'\nimport { createContext, memo, useContext, useId, useMemo, type ComponentProps, type ComponentRef, type ReactElement, type ReactNode, type Ref } from 'react'\n\ntype RadioGroupVariant = 'default' | 'unstyled'\n\nexport interface RadioGroupProps extends Omit<ComponentProps<typeof RadioGroupPrimitive.Root>, 'onChange' | 'disabled'> {\n 'aria-describedby'?: string\n 'aria-label'?: string\n className?: string\n disabled?: boolean | string[]\n errorMessage?: BaseFormFieldProps['errorMessage']\n itemClassName?: string\n messageReserveLines?: number\n messageReserveSpace?: boolean\n name: string\n onChange?: (selected: string) => void\n onValueChange: (selected: string) => void\n orientation?: 'horizontal' | 'vertical'\n required?: boolean\n selected?: string\n state?: FormFieldState\n variant?: RadioGroupVariant\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport interface RadioGroupItemProps extends ComponentProps<typeof RadioGroupPrimitive.Item> {\n children?: ReactNode\n className?: string\n description?: string | ReactNode\n id?: string\n value: string\n}\n\ninterface RadioGroupContextType {\n disabledValues: string[]\n groupDisabled: boolean\n itemClassName?: string\n orientation: 'horizontal' | 'vertical'\n selected?: string\n variant: RadioGroupVariant\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextType>({\n disabledValues: [],\n groupDisabled: false,\n orientation: 'vertical',\n variant: 'default',\n})\n\nconst DISABLED_STYLES = 'pointer-events-none opacity-60'\n\nconst RadioGroup = (\n allProps: RadioGroupProps & {\n ref?: Ref<ComponentRef<typeof RadioGroupPrimitive.Root>>\n },\n): ReactElement => {\n const isControlled = 'selected' in allProps\n const {\n className,\n disabled,\n errorMessage,\n itemClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onValueChange,\n orientation = 'vertical',\n ref,\n selected: selectedProp,\n state = 'default',\n variant = 'default',\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n ...props\n } = allProps\n const selected = isControlled ? (selectedProp ?? '') : selectedProp\n const groupId = useFormFieldId(props.id, name)\n const errorMessageId = getErrorMessageId(groupId)\n const warningMessageId = getWarningMessageId(groupId)\n const messageId = state === 'error' && errorMessage ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const handleValueChange = (nextValue: string) => {\n onValueChange(nextValue)\n onChange?.(nextValue)\n }\n\n const contextValue = useMemo(\n () => ({\n disabledValues: Array.isArray(disabled) ? disabled : [],\n groupDisabled: typeof disabled === 'boolean' ? disabled : false,\n itemClassName,\n orientation,\n selected,\n variant,\n }),\n [orientation, variant, disabled, itemClassName, selected],\n )\n\n return (\n <RadioGroupContext.Provider value={contextValue}>\n <div data-slot='radio-group-field' className='flex w-full flex-col gap-1.5'>\n <RadioGroupPrimitive.Root\n className={cn('flex w-full text-text-primary', orientation === 'vertical' ? 'gap-4 flex-col' : 'gap-5 flex-row', variant === 'unstyled' && 'gap-2.5 w-fit', className)}\n data-state={state}\n data-testid='spectral-radio-group'\n id={groupId}\n aria-invalid={state === 'error' ? true : undefined}\n aria-describedby={[messageId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n disabled={contextValue.groupDisabled}\n name={name}\n onValueChange={handleValueChange}\n ref={ref}\n value={selected}\n {...props}\n />\n <ErrorMessage\n dataTestId='spectral-radio-group-error-message'\n id={errorMessageId}\n message={state === 'error' ? errorMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-radio-group-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? warningMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n </RadioGroupContext.Provider>\n )\n}\nRadioGroup.displayName = 'RadioGroup'\n\nconst DEFAULT_TRANSITION: Transition = { type: 'spring', stiffness: 200, damping: 16 }\n\nconst RadioButton = memo(\n ({\n className,\n id,\n isDisabled,\n ref,\n transition = DEFAULT_TRANSITION,\n value,\n ...props\n }: Omit<ComponentProps<typeof RadioGroupPrimitive.Item>, 'asChild'> & {\n isDisabled?: boolean\n ref?: Ref<HTMLButtonElement>\n transition?: Transition\n }) => {\n const prefersReducedMotion = useReducedMotion()\n\n // When reduced motion is preferred, fall back to original non-animated behavior\n if (prefersReducedMotion) {\n return (\n <RadioGroupPrimitive.Item\n className={cn(\n 'h-4.5 w-4.5 border-border-subtle ring-black relative aspect-square cursor-pointer rounded-full border-2 bg-radio-bg transition-colors',\n 'hover:border-radio-border--hover focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent',\n 'data-[state=checked]:border-radio-border--selected data-[state=checked]:bg-radio-bg',\n isDisabled && DISABLED_STYLES,\n className,\n )}\n data-testid='spectral-radio-group-item'\n disabled={isDisabled}\n id={id}\n ref={ref as Ref<ComponentRef<typeof RadioGroupPrimitive.Item>>}\n value={value}\n {...props}\n >\n <RadioGroupPrimitive.Indicator className={cn(`after:inset-0 after:h-2.5 after:w-2.5 after:absolute after:m-auto after:rounded-full after:bg-radio-bg--selected after:content-['']`, isDisabled && DISABLED_STYLES)} />\n </RadioGroupPrimitive.Item>\n )\n }\n\n return (\n <RadioGroupPrimitive.Item value={value} id={id} disabled={isDisabled} asChild {...props}>\n <motion.button\n className={cn(\n 'h-4.5 w-4.5 border-border-subtle ring-black relative aspect-square cursor-pointer rounded-full border-2 bg-radio-bg transition-colors',\n 'hover:border-radio-border--hover focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent',\n 'data-[state=checked]:border-radio-border--selected data-[state=checked]:bg-radio-bg',\n isDisabled && DISABLED_STYLES,\n className,\n )}\n data-testid='spectral-radio-group-item'\n ref={ref}\n whileHover={{ scale: 1.05 }}\n whileTap={{ scale: 0.95 }}\n >\n <RadioGroupPrimitive.Indicator className='relative flex items-center justify-center'>\n <AnimatePresence>\n <motion.div animate={{ opacity: 1, scale: 1 }} className='h-2.5 w-2.5 absolute rounded-full bg-radio-bg--selected' exit={{ opacity: 0, scale: 0 }} initial={{ opacity: 0, scale: 0 }} key='radio-indicator' transition={transition} />\n </AnimatePresence>\n </RadioGroupPrimitive.Indicator>\n </motion.button>\n </RadioGroupPrimitive.Item>\n )\n },\n)\nRadioButton.displayName = 'RadioButton'\n\nconst RadioGroupItem = ({\n children,\n className,\n disabled,\n ref,\n value,\n ...props\n}: RadioGroupItemProps & {\n ref?: Ref<ComponentRef<typeof RadioGroupPrimitive.Item>>\n}): ReactElement => {\n const { disabledValues, groupDisabled, itemClassName, variant, orientation } = useContext(RadioGroupContext)\n const generatedId = useId()\n\n const stringValue = value.toString()\n const id = props.id?.toString() ?? `${stringValue}-${generatedId}`\n const isDisabled = groupDisabled || disabledValues.includes(stringValue) || Boolean(disabled)\n\n if (variant === 'unstyled') {\n return (\n <RadioGroupPrimitive.Item asChild data-testid='spectral-radio-group-item' disabled={isDisabled} id={id} ref={ref} value={stringValue} {...props}>\n <Label className={cn('rounded flex h-fit w-fit border-2 border-transparent data-[state=checked]:border-radio-border--selected', isDisabled && DISABLED_STYLES, itemClassName, className)} data-testid='spectral-radio-group-item-label' htmlFor={id}>\n {children}\n </Label>\n </RadioGroupPrimitive.Item>\n )\n }\n\n return (\n <div className={cn('flex items-center', isDisabled && DISABLED_STYLES, itemClassName, className, orientation)}>\n <RadioButton ref={ref} value={stringValue} id={id} isDisabled={isDisabled} {...props} />\n {children && (\n <Label className={cn('text-md font-normal cursor-pointer', orientation === 'vertical' ? 'ml-2' : 'ml-1')} htmlFor={id}>\n {children}\n </Label>\n )}\n </div>\n )\n}\nRadioGroupItem.displayName = 'RadioGroup.Item'\n\nconst RadioGroupLabel = ({\n ref,\n className,\n ...props\n}: ComponentProps<typeof Label> & {\n ref?: Ref<ComponentRef<typeof Label>>\n}): ReactElement => {\n return <Label ref={ref} data-testid='spectral-radio-group-label' className={cn('text-md font-medium block', className)} {...props} />\n}\nRadioGroupLabel.displayName = 'RadioGroup.Label'\n\nexport { RadioGroup, RadioGroupItem, RadioGroupLabel }\n"],"mappings":"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"}
1
+ {"version":3,"file":"RadioGroup.js","names":[],"sources":["../src/components/RadioGroup/RadioGroup.tsx"],"sourcesContent":["import { Label } from '@components/Label/Label'\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\nimport {\n ErrorMessage,\n getErrorMessageId,\n getWarningMessageId,\n useFormFieldId,\n WarningMessage,\n type BaseFormFieldProps,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { AnimatePresence, motion, useReducedMotion, type Transition } from 'motion/react'\nimport { Children, createContext, memo, useContext, useId, useMemo, type ComponentProps, type ComponentRef, type ReactElement, type ReactNode, type Ref } from 'react'\n\ntype RadioGroupVariant = 'default' | 'unstyled'\n\nexport interface RadioGroupProps extends Omit<ComponentProps<typeof RadioGroupPrimitive.Root>, 'onChange' | 'disabled'> {\n className?: string\n disabled?: boolean | string[]\n errorMessage?: BaseFormFieldProps['errorMessage']\n itemClassName?: string\n messageReserveLines?: number\n messageReserveSpace?: boolean\n name: string\n onChange: (selected: string) => void\n /** @deprecated Use `onChange` instead. `onValueChange` will be removed in a future release. */\n onValueChange?: (selected: string) => void\n orientation?: 'horizontal' | 'vertical'\n required?: boolean\n selected?: string\n state?: FormFieldState\n variant?: RadioGroupVariant\n warningMessage?: BaseFormFieldProps['errorMessage']\n 'aria-describedby'?: string\n 'aria-label'?: string\n}\n\nexport interface RadioGroupItemProps extends ComponentProps<typeof RadioGroupPrimitive.Item> {\n children?: ReactNode\n className?: string\n description?: string | ReactNode\n id?: string\n value: string\n}\n\ninterface RadioGroupContextType {\n disabledValues: string[]\n groupDisabled: boolean\n itemClassName?: string\n orientation: 'horizontal' | 'vertical'\n selected?: string\n variant: RadioGroupVariant\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextType>({\n disabledValues: [],\n groupDisabled: false,\n orientation: 'vertical',\n variant: 'default',\n})\n\nconst DISABLED_STYLES = 'pointer-events-none opacity-60'\n\nconst RadioGroup = (\n allProps: RadioGroupProps & {\n ref?: Ref<ComponentRef<typeof RadioGroupPrimitive.Root>>\n },\n): ReactElement => {\n const isControlled = 'selected' in allProps\n const {\n className,\n disabled,\n errorMessage,\n itemClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onValueChange,\n orientation = 'vertical',\n ref,\n selected: selectedProp,\n state = 'default',\n variant = 'default',\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n ...props\n } = allProps\n const selected = isControlled ? (selectedProp ?? '') : selectedProp\n const groupId = useFormFieldId(props.id, name)\n const errorMessageId = getErrorMessageId(groupId)\n const warningMessageId = getWarningMessageId(groupId)\n const messageId = state === 'error' && errorMessage ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const handleValueChange = (nextValue: string) => {\n onChange(nextValue)\n onValueChange?.(nextValue)\n }\n\n const contextValue = useMemo(\n () => ({\n disabledValues: Array.isArray(disabled) ? disabled : [],\n groupDisabled: typeof disabled === 'boolean' ? disabled : false,\n itemClassName,\n orientation,\n selected,\n variant,\n }),\n [orientation, variant, disabled, itemClassName, selected],\n )\n\n return (\n <RadioGroupContext.Provider value={contextValue}>\n <div data-slot='radio-group-field' className='flex w-full flex-col gap-1.5'>\n <RadioGroupPrimitive.Root\n aria-invalid={state === 'error' ? true : undefined}\n aria-describedby={[messageId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n className={cn('group/radio-field flex w-full text-text-primary', orientation === 'vertical' ? 'gap-4 flex-col' : 'gap-5 flex-row', variant === 'unstyled' && 'gap-2.5 w-fit', className)}\n data-state={state}\n data-testid='spectral-radio-group'\n disabled={contextValue.groupDisabled}\n id={groupId}\n name={name}\n onValueChange={handleValueChange}\n ref={ref}\n value={selected}\n {...props}\n />\n <ErrorMessage\n dataTestId='spectral-radio-group-error-message'\n id={errorMessageId}\n message={state === 'error' ? errorMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-radio-group-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? warningMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n </RadioGroupContext.Provider>\n )\n}\nRadioGroup.displayName = 'RadioGroup'\n\nconst DEFAULT_TRANSITION: Transition = { type: 'spring', stiffness: 200, damping: 16 }\n\nconst RADIO_BUTTON_CLASSES = cn(\n 'size-4.5 border-border-subtle relative aspect-square cursor-pointer rounded-full border-2 bg-radio-bg transition-colors hover:border-radio-border--hover',\n 'focus:outline-none focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-accent data-[state=checked]:border-radio-border--selected data-[state=checked]:bg-radio-bg',\n 'group-data-[state=error]/radio-field:data-[state=unchecked]:border-danger-400 group-data-[state=error]/radio-field:data-[state=unchecked]:hover:border-danger-500 group-data-[state=error]/radio-field:focus-visible:outline-danger-400',\n 'group-data-[state=warning]/radio-field:data-[state=unchecked]:border-warning-400 group-data-[state=warning]/radio-field:data-[state=unchecked]:hover:border-warning-500 group-data-[state=warning]/radio-field:focus-visible:outline-warning-400',\n)\n\nconst RadioButton = memo(\n ({\n className,\n id,\n isDisabled,\n ref,\n transition = DEFAULT_TRANSITION,\n value,\n ...props\n }: Omit<ComponentProps<typeof RadioGroupPrimitive.Item>, 'asChild'> & {\n isDisabled?: boolean\n ref?: Ref<HTMLButtonElement>\n transition?: Transition\n }) => {\n const prefersReducedMotion = useReducedMotion()\n const buttonClassName = cn(RADIO_BUTTON_CLASSES, isDisabled && DISABLED_STYLES, className)\n\n // When reduced motion is preferred, fall back to original non-animated behavior\n if (prefersReducedMotion) {\n return (\n <RadioGroupPrimitive.Item className={buttonClassName} data-testid='spectral-radio-group-item' disabled={isDisabled} id={id} ref={ref as Ref<ComponentRef<typeof RadioGroupPrimitive.Item>>} value={value} {...props}>\n <RadioGroupPrimitive.Indicator className={cn(`after:inset-0 after:h-2.5 after:w-2.5 after:absolute after:m-auto after:rounded-full after:bg-radio-bg--selected after:content-['']`, isDisabled && DISABLED_STYLES)} />\n </RadioGroupPrimitive.Item>\n )\n }\n\n return (\n <RadioGroupPrimitive.Item value={value} id={id} disabled={isDisabled} asChild {...props}>\n <motion.button className={buttonClassName} data-testid='spectral-radio-group-item' ref={ref} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>\n <RadioGroupPrimitive.Indicator className='relative flex items-center justify-center'>\n <AnimatePresence>\n <motion.div animate={{ opacity: 1, scale: 1 }} className='h-2.5 w-2.5 absolute rounded-full bg-radio-bg--selected' exit={{ opacity: 0, scale: 0 }} initial={{ opacity: 0, scale: 0 }} key='radio-indicator' transition={transition} />\n </AnimatePresence>\n </RadioGroupPrimitive.Indicator>\n </motion.button>\n </RadioGroupPrimitive.Item>\n )\n },\n)\nRadioButton.displayName = 'RadioButton'\n\nconst RadioGroupItem = ({\n children,\n className,\n disabled,\n ref,\n value,\n ...props\n}: RadioGroupItemProps & {\n ref?: Ref<ComponentRef<typeof RadioGroupPrimitive.Item>>\n}): ReactElement => {\n const { disabledValues, groupDisabled, itemClassName, variant, orientation } = useContext(RadioGroupContext)\n const generatedId = useId()\n\n const stringValue = value.toString()\n const id = props.id?.toString() ?? `${stringValue}-${generatedId}`\n const isDisabled = groupDisabled || disabledValues.includes(stringValue) || Boolean(disabled)\n const isTextContent = Children.toArray(children).every((child) => typeof child === 'string' || typeof child === 'number')\n\n if (variant === 'unstyled') {\n return (\n <RadioGroupPrimitive.Item asChild data-testid='spectral-radio-group-item' disabled={isDisabled} id={id} ref={ref} value={stringValue} {...props}>\n <Label className={cn('rounded cursor-pointer flex h-fit w-fit border-2 border-transparent data-[state=checked]:border-radio-border--selected [&_*]:cursor-[inherit]', isTextContent && 'p-1.5', isDisabled && DISABLED_STYLES, itemClassName, className)} data-testid='spectral-radio-group-item-label' htmlFor={id}>\n {children}\n </Label>\n </RadioGroupPrimitive.Item>\n )\n }\n\n return (\n <div className={cn('flex items-center', isDisabled && DISABLED_STYLES, itemClassName, className, orientation)}>\n <RadioButton ref={ref} value={stringValue} id={id} isDisabled={isDisabled} {...props} />\n {children && (\n <Label className={cn('text-md font-normal cursor-pointer', orientation === 'vertical' ? 'ml-2' : 'ml-1.5')} htmlFor={id}>\n {children}\n </Label>\n )}\n </div>\n )\n}\nRadioGroupItem.displayName = 'RadioGroup.Item'\n\nconst RadioGroupLabel = ({\n ref,\n className,\n ...props\n}: ComponentProps<typeof Label> & {\n ref?: Ref<ComponentRef<typeof Label>>\n}): ReactElement => {\n return <Label ref={ref} data-testid='spectral-radio-group-label' className={cn('text-md font-medium block', className)} {...props} />\n}\nRadioGroupLabel.displayName = 'RadioGroup.Label'\n\nexport { RadioGroup, RadioGroupItem, RadioGroupLabel }\n"],"mappings":"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"}
package/dist/Select.d.ts CHANGED
@@ -16,6 +16,7 @@ interface SelectProps extends Omit<ComponentPropsWithoutRef<'button'>, 'value' |
16
16
  labelClassName?: string;
17
17
  loadingMessage?: string;
18
18
  onChange?: (value: string) => void;
19
+ /** @deprecated Use `onChange` instead. `onValueChange` will be removed in a future release. */
19
20
  onValueChange?: (value: string) => void;
20
21
  options: SelectOption[];
21
22
  placeholder?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"Select.d.ts","names":[],"sources":["../src/components/Select/Select.tsx"],"mappings":";;;;;;KA4BK,YAAA,GAAe,UAAA;AAAA,KACf,KAAA;AAAA,KACA,IAAA;AAAA,UAEY,WAAA,SAAoB,IAAA,CAAK,wBAAA,4HAAoJ,IAAA,CAAK,kBAAA;EACjM,YAAA;EACA,aAAA,GAAgB,aAAA;EAChB,YAAA,GAAe,SAAA;EACf,EAAA;EACA,KAAA;EACA,cAAA;EACA,cAAA;EACA,QAAA,IAAY,KAAA;EACZ,aAAA,IAAiB,KAAA;EACjB,OAAA,EAAS,YAAA;EACT,WAAA;EACA,KAAA,GAAQ,OAAA,CAAQ,cAAA;EAChB,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA,UAGF,mBAAA,SAA4B,WAAA;EAC3C,KAAA,GAAQ,KAAA;EACR,WAAA;EACA,eAAA;EACA,iBAAA,GAAoB,OAAA,GAAU,OAAA;EAC9B,gBAAA,YAA4B,OAAA,CAAQ,MAAA,CAAO,IAAA;EAC3C,QAAA;EACA,IAAA,GAAO,IAAA;EACP,UAAA;AAAA;AAAA,wBAIA,QAAA,EAAU,mBAAA;EACR,GAAA,GAAM,GAAA,CAAI,iBAAA;AAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"Select.d.ts","names":[],"sources":["../src/components/Select/Select.tsx"],"mappings":";;;;;;KA4BK,YAAA,GAAe,UAAA;AAAA,KACf,KAAA;AAAA,KACA,IAAA;AAAA,UAEY,WAAA,SAAoB,IAAA,CAAK,wBAAA,4HAAoJ,IAAA,CAAK,kBAAA;EACjM,YAAA;EACA,aAAA,GAAgB,aAAA;EAChB,YAAA,GAAe,SAAA;EACf,EAAA;EACA,KAAA;EACA,cAAA;EACA,cAAA;EACA,QAAA,IAAY,KAAA;EAXJ;EAaR,aAAA,IAAiB,KAAA;EACjB,OAAA,EAAS,YAAA;EACT,WAAA;EACA,KAAA,GAAQ,OAAA,CAAQ,cAAA;EAChB,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA,UAGF,mBAAA,SAA4B,WAAA;EAC3C,KAAA,GAAQ,KAAA;EACR,WAAA;EACA,eAAA;EACA,iBAAA,GAAoB,OAAA,GAAU,OAAA;EAC9B,gBAAA,YAA4B,OAAA,CAAQ,MAAA,CAAO,IAAA;EAC3C,QAAA;EACA,IAAA,GAAO,IAAA;EACP,UAAA;AAAA;AAAA,wBAIA,QAAA,EAAU,mBAAA;EACR,GAAA,GAAM,GAAA,CAAI,iBAAA;AAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","names":[],"sources":["../src/components/Select/Select.tsx"],"sourcesContent":["import { CheckmarkIcon, ChevronDownIcon, LoaderIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { SelectValue } from '@primitives/select'\nimport * as SelectPrimitive from '@radix-ui/react-select'\nimport { useAutoDropdownHorizontalShift } from '@utils/dropdownPositioning'\nimport {\n EmptyState,\n ErrorMessage,\n getAriaProps,\n getDropdownSurfaceClasses,\n getDropdownWidthStyles,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getOptionClasses,\n getTriggerClasses,\n getWarningMessageId,\n groupOptions,\n LoadingState,\n useFormFieldId,\n WarningMessage,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useState, type ComponentPropsWithoutRef, type CSSProperties, type ReactNode, type Ref } from 'react'\n\ntype SelectOption = BaseOption\ntype Align = 'start' | 'center' | 'end'\ntype Side = 'top' | 'bottom' | 'left' | 'right'\n\nexport interface SelectProps extends Omit<ComponentPropsWithoutRef<'button'>, 'value' | 'onChange' | 'aria-disabled' | 'aria-invalid' | 'aria-required' | 'aria-describedby' | 'aria-label'>, Omit<BaseFormFieldProps, 'state'> {\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: ReactNode\n id?: string\n label?: string\n labelClassName?: string\n loadingMessage?: string\n onChange?: (value: string) => void\n onValueChange?: (value: string) => void\n options: SelectOption[]\n placeholder?: string\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport interface SelectExtendedProps extends SelectProps {\n align?: Align\n alignOffset?: number\n avoidCollisions?: boolean\n collisionBoundary?: Element | Element[] | null\n collisionPadding?: number | Partial<Record<Side, number>>\n position?: 'popper' | 'item-aligned'\n side?: Side\n sideOffset?: number\n}\n\nexport const Select = (\n allProps: SelectExtendedProps & {\n ref?: Ref<HTMLButtonElement>\n },\n) => {\n const isControlled = 'value' in allProps\n const {\n align = 'start',\n alignOffset = 0,\n avoidCollisions = true,\n className,\n collisionBoundary,\n collisionPadding = 10,\n defaultValue,\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found',\n errorMessage,\n disabled,\n id,\n label,\n labelClassName,\n loadingMessage = 'Loading…',\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onValueChange,\n options,\n placeholder = 'Select an option',\n position = 'popper',\n ref,\n required,\n side = 'bottom',\n sideOffset = 4,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n ...props\n } = allProps\n const value = isControlled ? (valueProp ?? '') : valueProp\n const [open, setOpen] = useState(false)\n const { dropdownShiftStyle, setDropdownElement } = useAutoDropdownHorizontalShift(open)\n const selectId = useFormFieldId(id, name)\n const listboxId = `${selectId}-listbox`\n const errorMessageId = getErrorMessageId(selectId)\n const warningMessageId = getWarningMessageId(selectId)\n const messageId = state === 'error' && errorMessage ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const { dropdownWidthMode, dropdownWidthStyle, resolvedDropdownWidth } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: 'var(--radix-select-trigger-width)',\n })\n const selectContentStyle = {\n '--spectral-select-content-width': resolvedDropdownWidth,\n ...(position === 'item-aligned' ? { width: resolvedDropdownWidth } : {}),\n ...dropdownWidthStyle,\n ...dropdownShiftStyle,\n } as CSSProperties\n const isDisabled = Boolean(disabled)\n const isLoading = state === 'loading'\n const isInvalid = state === 'error'\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n const { groups, ungrouped } = groupOptions(options)\n const handleValueChange = (nextValue: string) => {\n onChange?.(nextValue)\n onValueChange?.(nextValue)\n }\n\n const renderOptions = () => {\n if (isLoading) {\n return <LoadingState data-testid='spectral-select-loading' message={loadingMessage} />\n }\n\n if (options.length === 0) {\n return <EmptyState data-testid='spectral-select-empty' message={emptyMessage} />\n }\n\n const renderOption = (option: SelectOption) => {\n const isSelected = value === option.value\n\n return (\n <SelectPrimitive.Item className={cn(getOptionClasses(!!option.disabled, false, isSelected), 'relative flex w-full cursor-pointer items-center')} data-testid='spectral-select-item' disabled={option.disabled} key={option.value} value={option.value}>\n <SelectPrimitive.ItemText data-testid='spectral-select-item-text' className='block truncate'>\n {option.label}\n </SelectPrimitive.ItemText>\n <SelectPrimitive.ItemIndicator data-testid='spectral-select-item-selected-indicator' asChild>\n <span className='right-2 h-4 w-4 absolute flex items-center justify-center'>\n <CheckmarkIcon size={16} />\n </span>\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n )\n }\n\n return (\n <>\n {ungrouped.length > 0 && (\n <>\n {ungrouped.map(renderOption)}\n {Object.keys(groups).length > 0 && <SelectPrimitive.Separator className='-mx-1 my-1 h-px bg-border-secondary' data-testid='spectral-select-separator' />}\n </>\n )}\n\n {Object.entries(groups).map(([groupName, groupOptions], groupIndex) => (\n <SelectPrimitive.Group key={groupName} data-testid='spectral-select-group'>\n {groupIndex > 0 && <SelectPrimitive.Separator className='-mx-1 my-1 h-px bg-border-secondary' data-testid='spectral-select-group-separator' />}\n <Label className={cn('px-2 py-1.5 text-base font-semibold text-text-primary', labelClassName)} data-testid='spectral-select-group-label'>\n {groupName}\n </Label>\n {groupOptions.map((option: BaseOption) => renderOption(option))}\n </SelectPrimitive.Group>\n ))}\n </>\n )\n }\n\n return (\n <div className='w-full'>\n {label && (\n <Label className={cn('mb-2 block text-text-primary', labelClassName, isDisabled && 'text-text-secondary')} data-testid='spectral-select-label' htmlFor={selectId}>\n {label}\n </Label>\n )}\n <SelectPrimitive.Root data-testid='spectral-select' defaultValue={defaultValue} disabled={isDisabled} name={name} onOpenChange={setOpen} onValueChange={handleValueChange} open={open} required={required} value={value}>\n <SelectPrimitive.Trigger\n aria-controls={listboxId}\n aria-expanded={open}\n aria-label={ariaLabel ?? label}\n asChild\n className={cn(getTriggerClasses(open, state), 'text-input-text data-placeholder:text-input-text-placeholder!', className)}\n data-slot='select-trigger'\n data-state={state}\n data-testid='spectral-select-trigger'\n id={selectId}\n ref={ref}\n style={getFormFieldCSSProperties() as CSSProperties}\n {...ariaProps}\n {...props}\n >\n <button\n className='min-w-0 gap-2 [&>span]:min-w-0 grid w-full cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center overflow-hidden text-left whitespace-nowrap text-input-text! data-placeholder:text-input-text-placeholder! [&>span]:block [&>span]:overflow-hidden [&>span]:text-ellipsis [&>span]:whitespace-nowrap [&>span[data-placeholder]]:text-input-text-placeholder!'\n type='button'\n disabled={isDisabled}\n >\n <SelectValue data-testid='spectral-select-value' placeholder={placeholder} />\n <SelectPrimitive.Icon asChild>\n <div className='flex shrink-0 cursor-pointer items-center'>{isLoading ? <LoaderIcon size={20} /> : <ChevronDownIcon className={cn('transition-transform duration-200', open && 'rotate-180')} size={20} />}</div>\n </SelectPrimitive.Icon>\n </button>\n </SelectPrimitive.Trigger>\n\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n align={align}\n alignOffset={alignOffset}\n avoidCollisions={avoidCollisions}\n className={cn(\n 'relative z-50 max-h-[min(var(--radix-select-content-available-height),300px)] min-w-32 origin-(--radix-select-content-transform-origin) overflow-hidden pb-1',\n getDropdownSurfaceClasses(),\n 'motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95',\n 'motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=top]:slide-in-from-bottom-2',\n position === 'popper' && 'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1',\n )}\n collisionBoundary={collisionBoundary}\n collisionPadding={collisionPadding}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-slot='select-content'\n data-testid='spectral-select-content'\n id={listboxId}\n position={position}\n ref={setDropdownElement}\n side={side}\n sideOffset={sideOffset}\n style={selectContentStyle}\n >\n <SelectPrimitive.ScrollUpButton className='py-1 flex cursor-default items-center justify-center' data-testid='spectral-select-scroll-up-button'>\n <ChevronDownIcon aria-hidden='true' className='rotate-180' size={18} />\n </SelectPrimitive.ScrollUpButton>\n\n <SelectPrimitive.Viewport asChild>\n <div\n className={cn(\n 'p-1 overflow-x-hidden overflow-y-auto [&>div]:leading-5',\n position === 'popper' && (dropdownWidth === 'trigger' ? 'scroll-my-1 h-(--radix-select-trigger-height) w-(--spectral-select-content-width) min-w-(--spectral-select-content-width)' : 'scroll-my-1 h-(--radix-select-trigger-height)'),\n )}\n data-testid='spectral-select-items'\n >\n {renderOptions()}\n </div>\n </SelectPrimitive.Viewport>\n\n <SelectPrimitive.ScrollDownButton className='py-1 flex cursor-default items-center justify-center' data-testid='spectral-select-scroll-down-button'>\n <ChevronDownIcon aria-hidden='true' size={18} />\n </SelectPrimitive.ScrollDownButton>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n </SelectPrimitive.Root>\n\n <ErrorMessage\n dataTestId='spectral-select-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-select-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nSelect.displayName = 'Select'\n"],"mappings":"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"}
1
+ {"version":3,"file":"Select.js","names":[],"sources":["../src/components/Select/Select.tsx"],"sourcesContent":["import { CheckmarkIcon, ChevronDownIcon, LoaderIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { SelectValue } from '@primitives/select'\nimport * as SelectPrimitive from '@radix-ui/react-select'\nimport { useAutoDropdownHorizontalShift } from '@utils/dropdownPositioning'\nimport {\n EmptyState,\n ErrorMessage,\n getAriaProps,\n getDropdownSurfaceClasses,\n getDropdownWidthStyles,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getOptionClasses,\n getTriggerClasses,\n getWarningMessageId,\n groupOptions,\n LoadingState,\n useFormFieldId,\n WarningMessage,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useState, type ComponentPropsWithoutRef, type CSSProperties, type ReactNode, type Ref } from 'react'\n\ntype SelectOption = BaseOption\ntype Align = 'start' | 'center' | 'end'\ntype Side = 'top' | 'bottom' | 'left' | 'right'\n\nexport interface SelectProps extends Omit<ComponentPropsWithoutRef<'button'>, 'value' | 'onChange' | 'aria-disabled' | 'aria-invalid' | 'aria-required' | 'aria-describedby' | 'aria-label'>, Omit<BaseFormFieldProps, 'state'> {\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: ReactNode\n id?: string\n label?: string\n labelClassName?: string\n loadingMessage?: string\n onChange?: (value: string) => void\n /** @deprecated Use `onChange` instead. `onValueChange` will be removed in a future release. */\n onValueChange?: (value: string) => void\n options: SelectOption[]\n placeholder?: string\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nexport interface SelectExtendedProps extends SelectProps {\n align?: Align\n alignOffset?: number\n avoidCollisions?: boolean\n collisionBoundary?: Element | Element[] | null\n collisionPadding?: number | Partial<Record<Side, number>>\n position?: 'popper' | 'item-aligned'\n side?: Side\n sideOffset?: number\n}\n\nexport const Select = (\n allProps: SelectExtendedProps & {\n ref?: Ref<HTMLButtonElement>\n },\n) => {\n const isControlled = 'value' in allProps\n const {\n align = 'start',\n alignOffset = 0,\n avoidCollisions = true,\n className,\n collisionBoundary,\n collisionPadding = 10,\n defaultValue,\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found',\n errorMessage,\n disabled,\n id,\n label,\n labelClassName,\n loadingMessage = 'Loading…',\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onValueChange,\n options,\n placeholder = 'Select an option',\n position = 'popper',\n ref,\n required,\n side = 'bottom',\n sideOffset = 4,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n ...props\n } = allProps\n const value = isControlled ? (valueProp ?? '') : valueProp\n const [open, setOpen] = useState(false)\n const { dropdownShiftStyle, setDropdownElement } = useAutoDropdownHorizontalShift(open)\n const selectId = useFormFieldId(id, name)\n const listboxId = `${selectId}-listbox`\n const errorMessageId = getErrorMessageId(selectId)\n const warningMessageId = getWarningMessageId(selectId)\n const messageId = state === 'error' && errorMessage ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const { dropdownWidthMode, dropdownWidthStyle, resolvedDropdownWidth } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: 'var(--radix-select-trigger-width)',\n })\n const selectContentStyle = {\n '--spectral-select-content-width': resolvedDropdownWidth,\n ...(position === 'item-aligned' ? { width: resolvedDropdownWidth } : {}),\n ...dropdownWidthStyle,\n ...dropdownShiftStyle,\n } as CSSProperties\n const isDisabled = Boolean(disabled)\n const isLoading = state === 'loading'\n const isInvalid = state === 'error'\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n const { groups, ungrouped } = groupOptions(options)\n const handleValueChange = (nextValue: string) => {\n onChange?.(nextValue)\n onValueChange?.(nextValue)\n }\n\n const renderOptions = () => {\n if (isLoading) {\n return <LoadingState data-testid='spectral-select-loading' message={loadingMessage} />\n }\n\n if (options.length === 0) {\n return <EmptyState data-testid='spectral-select-empty' message={emptyMessage} />\n }\n\n const renderOption = (option: SelectOption) => {\n const isSelected = value === option.value\n\n return (\n <SelectPrimitive.Item className={cn(getOptionClasses(!!option.disabled, false, isSelected), 'relative flex w-full cursor-pointer items-center')} data-testid='spectral-select-item' disabled={option.disabled} key={option.value} value={option.value}>\n <SelectPrimitive.ItemText data-testid='spectral-select-item-text' className='block truncate'>\n {option.label}\n </SelectPrimitive.ItemText>\n <SelectPrimitive.ItemIndicator data-testid='spectral-select-item-selected-indicator' asChild>\n <span className='right-2 h-4 w-4 absolute flex items-center justify-center'>\n <CheckmarkIcon size={16} />\n </span>\n </SelectPrimitive.ItemIndicator>\n </SelectPrimitive.Item>\n )\n }\n\n return (\n <>\n {ungrouped.length > 0 && (\n <>\n {ungrouped.map(renderOption)}\n {Object.keys(groups).length > 0 && <SelectPrimitive.Separator className='-mx-1 my-1 h-px bg-border-secondary' data-testid='spectral-select-separator' />}\n </>\n )}\n\n {Object.entries(groups).map(([groupName, groupOptions], groupIndex) => (\n <SelectPrimitive.Group key={groupName} data-testid='spectral-select-group'>\n {groupIndex > 0 && <SelectPrimitive.Separator className='-mx-1 my-1 h-px bg-border-secondary' data-testid='spectral-select-group-separator' />}\n <Label className={cn('px-2 py-1.5 text-base font-semibold text-text-primary', labelClassName)} data-testid='spectral-select-group-label'>\n {groupName}\n </Label>\n {groupOptions.map((option: BaseOption) => renderOption(option))}\n </SelectPrimitive.Group>\n ))}\n </>\n )\n }\n\n return (\n <div className='w-full'>\n {label && (\n <Label className={cn('mb-2 block text-text-primary', labelClassName, isDisabled && 'text-text-secondary')} data-testid='spectral-select-label' htmlFor={selectId}>\n {label}\n </Label>\n )}\n <SelectPrimitive.Root data-testid='spectral-select' defaultValue={defaultValue} disabled={isDisabled} name={name} onOpenChange={setOpen} onValueChange={handleValueChange} open={open} required={required} value={value}>\n <SelectPrimitive.Trigger\n aria-controls={listboxId}\n aria-expanded={open}\n aria-label={ariaLabel ?? label}\n asChild\n className={cn(getTriggerClasses(open, state), 'text-input-text data-placeholder:text-input-text-placeholder!', className)}\n data-slot='select-trigger'\n data-state={state}\n data-testid='spectral-select-trigger'\n id={selectId}\n ref={ref}\n style={getFormFieldCSSProperties() as CSSProperties}\n {...ariaProps}\n {...props}\n >\n <button\n className='min-w-0 gap-2 [&>span]:min-w-0 grid w-full cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center overflow-hidden text-left whitespace-nowrap text-input-text! data-placeholder:text-input-text-placeholder! [&>span]:block [&>span]:overflow-hidden [&>span]:text-ellipsis [&>span]:whitespace-nowrap [&>span[data-placeholder]]:text-input-text-placeholder!'\n type='button'\n disabled={isDisabled}\n >\n <SelectValue data-testid='spectral-select-value' placeholder={placeholder} />\n <SelectPrimitive.Icon asChild>\n <div className='flex shrink-0 cursor-pointer items-center'>{isLoading ? <LoaderIcon size={20} /> : <ChevronDownIcon className={cn('transition-transform duration-200', open && 'rotate-180')} size={20} />}</div>\n </SelectPrimitive.Icon>\n </button>\n </SelectPrimitive.Trigger>\n\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n align={align}\n alignOffset={alignOffset}\n avoidCollisions={avoidCollisions}\n className={cn(\n 'relative z-50 max-h-[min(var(--radix-select-content-available-height),300px)] min-w-32 origin-(--radix-select-content-transform-origin) overflow-hidden pb-1',\n getDropdownSurfaceClasses(),\n 'motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95',\n 'motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=top]:slide-in-from-bottom-2',\n position === 'popper' && 'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1',\n )}\n collisionBoundary={collisionBoundary}\n collisionPadding={collisionPadding}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-slot='select-content'\n data-testid='spectral-select-content'\n id={listboxId}\n position={position}\n ref={setDropdownElement}\n side={side}\n sideOffset={sideOffset}\n style={selectContentStyle}\n >\n <SelectPrimitive.ScrollUpButton className='py-1 flex cursor-default items-center justify-center' data-testid='spectral-select-scroll-up-button'>\n <ChevronDownIcon aria-hidden='true' className='rotate-180' size={18} />\n </SelectPrimitive.ScrollUpButton>\n\n <SelectPrimitive.Viewport asChild>\n <div\n className={cn(\n 'p-1 overflow-x-hidden overflow-y-auto [&>div]:leading-5',\n position === 'popper' && (dropdownWidth === 'trigger' ? 'scroll-my-1 h-(--radix-select-trigger-height) w-(--spectral-select-content-width) min-w-(--spectral-select-content-width)' : 'scroll-my-1 h-(--radix-select-trigger-height)'),\n )}\n data-testid='spectral-select-items'\n >\n {renderOptions()}\n </div>\n </SelectPrimitive.Viewport>\n\n <SelectPrimitive.ScrollDownButton className='py-1 flex cursor-default items-center justify-center' data-testid='spectral-select-scroll-down-button'>\n <ChevronDownIcon aria-hidden='true' size={18} />\n </SelectPrimitive.ScrollDownButton>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n </SelectPrimitive.Root>\n\n <ErrorMessage\n dataTestId='spectral-select-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-select-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nSelect.displayName = 'Select'\n"],"mappings":"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"}
package/dist/Switch.d.ts CHANGED
@@ -11,7 +11,7 @@ type SwitchProps = Omit<SwitchProps$1, 'onCheckedChange'> & {
11
11
  hideLabel?: boolean;
12
12
  id?: string;
13
13
  label?: string;
14
- labelPosition?: 'left' | 'right';
14
+ labelPosition?: 'left' | 'right'; /** @deprecated Use `label` instead. `labelText` will be removed in a future release. */
15
15
  labelText?: string;
16
16
  messageReserveLines?: number;
17
17
  messageReserveSpace?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.d.ts","names":[],"sources":["../src/components/Switch/Switch.tsx"],"mappings":";;;;;;;KAOK,iBAAA,GAAoB,OAAA,CAAQ,cAAA;AAAA,KAErB,WAAA,GAAc,IAAA,CAAK,aAAA;EAC7B,YAAA,GAAe,kBAAA;EACf,SAAA;EACA,EAAA;EACA,KAAA;EACA,aAAA;EACA,SAAA;EACA,mBAAA;EACA,mBAAA;EACA,QAAA,IAAY,OAAA;EACZ,GAAA,GAAM,GAAA,CAAI,iBAAA;EACV,QAAA;EACA,KAAA,GAAQ,iBAAA;EACR,KAAA;EACA,OAAA;EACA,cAAA,GAAiB,kBAAA;EACjB,kBAAA;EACA,YAAA;AAAA;AAAA;EAMA,SAAA;EACA,QAAA;EACA,YAAA;EACA,SAAA;EACA,EAAA;EACA,KAAA;EACA,aAAA;EACA,SAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,QAAA;EACA,GAAA;EACA,QAAA;EACA,KAAA;EACA,KAAA;EACA,OAAA;EACA,cAAA;EAAA,oBACoB,eAAA;EAAA,cACN,SAAA;EAAA,GACX;AAAA,GACF,WAAA,GAAW,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"Switch.d.ts","names":[],"sources":["../src/components/Switch/Switch.tsx"],"mappings":";;;;;;;KAOK,iBAAA,GAAoB,OAAA,CAAQ,cAAA;AAAA,KAErB,WAAA,GAAc,IAAA,CAAK,aAAA;EAC7B,YAAA,GAAe,kBAAA;EACf,SAAA;EACA,EAAA;EACA,KAAA;EACA,aAAA,qBAP6C;EAS7C,SAAA;EACA,mBAAA;EACA,mBAAA;EACA,QAAA,IAAY,OAAA;EACZ,GAAA,GAAM,GAAA,CAAI,iBAAA;EACV,QAAA;EACA,KAAA,GAAQ,iBAAA;EACR,KAAA;EACA,OAAA;EACA,cAAA,GAAiB,kBAAA;EACjB,kBAAA;EACA,YAAA;AAAA;AAAA;EAMA,SAAA;EACA,QAAA;EACA,YAAA;EACA,SAAA;EACA,EAAA;EACA,KAAA;EACA,aAAA;EACA,SAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,QAAA;EACA,GAAA;EACA,QAAA;EACA,KAAA;EACA,KAAA;EACA,OAAA;EACA,cAAA;EAAA,oBACoB,eAAA;EAAA,cACN,SAAA;EAAA,GACX;AAAA,GACF,WAAA,GAAW,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
package/dist/Switch.js CHANGED
@@ -13,7 +13,7 @@ const thumbBase = "bg-switch-thumb--checked data-[state=checked]:bg-switch-thumb
13
13
  const Switch = ({ className, disabled, errorMessage, hideLabel = false, id, label, labelPosition = "right", labelText, messageReserveLines = 1, messageReserveSpace = false, name, onChange, ref, required, state = "default", value, variant, warningMessage, "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, ...props }) => {
14
14
  const switchId = useFormFieldId(id, name);
15
15
  const isDisabled = Boolean(disabled);
16
- const resolvedLabelText = labelText ?? label;
16
+ const resolvedLabelText = label ?? labelText;
17
17
  const errorMessageId = getErrorMessageId(switchId);
18
18
  const warningMessageId = getWarningMessageId(switchId);
19
19
  const messageId = state === "error" && errorMessage && errorMessageId ? errorMessageId : state === "warning" && warningMessage && warningMessageId ? warningMessageId : void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"Switch.js","names":[],"sources":["../src/components/Switch/Switch.tsx"],"sourcesContent":["import { Label } from '@components/Label/Label'\nimport { ErrorMessage, getErrorMessageId, getWarningMessageId, useFormFieldId, WarningMessage, type BaseFormFieldProps, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { CheckIcon, XIcon } from 'lucide-react'\nimport { type Ref } from 'react'\nimport { Switch as SwitchBase, SwitchThumb, type SwitchProps as SwitchBaseProps } from './SwitchBase'\n\ntype SwitchVisualState = Exclude<FormFieldState, 'disabled'>\n\nexport type SwitchProps = Omit<SwitchBaseProps, 'onCheckedChange'> & {\n errorMessage?: BaseFormFieldProps['errorMessage']\n hideLabel?: boolean\n id?: string\n label?: string\n labelPosition?: 'left' | 'right'\n labelText?: string\n messageReserveLines?: number\n messageReserveSpace?: boolean\n onChange?: (checked: boolean) => void\n ref?: Ref<HTMLButtonElement>\n required?: boolean\n state?: SwitchVisualState\n value?: string\n variant?: 'default' | 'squared' | 'permanent-indicator'\n warningMessage?: BaseFormFieldProps['errorMessage']\n 'aria-describedby'?: string\n 'aria-label'?: string\n}\n\nconst thumbBase = 'bg-switch-thumb--checked data-[state=checked]:bg-switch-thumb pointer-events-none block rounded-full ring-0 transition-transform motion-reduce:transition-none'\n\nexport const Switch = ({\n className,\n disabled,\n errorMessage,\n hideLabel = false,\n id,\n label,\n labelPosition = 'right',\n labelText,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n ref,\n required,\n state = 'default',\n value,\n variant,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n ...props\n}: SwitchProps) => {\n const switchId = useFormFieldId(id, name)\n const isDisabled = Boolean(disabled)\n const resolvedLabelText = labelText ?? label\n const errorMessageId = getErrorMessageId(switchId)\n const warningMessageId = getWarningMessageId(switchId)\n const messageId = state === 'error' && errorMessage && errorMessageId ? errorMessageId : state === 'warning' && warningMessage && warningMessageId ? warningMessageId : undefined\n const isSquared = variant === 'squared'\n const isPermanentIndicator = variant === 'permanent-indicator'\n\n return (\n <div className='flex items-center' data-testid='spectral-switch-container' data-state={state}>\n {labelPosition === 'left' && !hideLabel && resolvedLabelText && (\n <Label className='mr-2' data-testid='spectral-switch-label-left' htmlFor={switchId}>\n {resolvedLabelText}\n </Label>\n )}\n\n {isPermanentIndicator ? (\n <div className='h-7 text-sm font-medium relative inline-grid grid-cols-[1fr_1fr] items-center'>\n <SwitchBase\n aria-describedby={[messageId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n aria-invalid={state === 'error' ? true : undefined}\n aria-label={ariaLabel ?? (hideLabel ? resolvedLabelText : undefined)}\n aria-required={required}\n className={cn(\n 'peer inset-0 w-14 focus-visible:ring-ring absolute inline-flex h-[inherit] items-center focus-visible:ring-offset-background data-[state=checked]:bg-switch-bg--checked data-[state=unchecked]:bg-switch-bg/50',\n 'shadow-2xs cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n data-testid='spectral-switch'\n disabled={isDisabled}\n id={switchId}\n name={name}\n onCheckedChange={onChange}\n ref={ref}\n required={required}\n value={value}\n {...props}\n >\n <SwitchThumb className={cn(thumbBase, 'size-6.5 shadow-xs relative z-10 duration-200 ease-out data-[state=checked]:translate-x-[26px] motion-reduce:duration-0 data-[state=checked]:rtl:translate-x-[-26px]')} />\n </SwitchBase>\n <span className='ml-0.5 min-w-8 peer-data-[state=unchecked]:translate-x-6 peer-data-[state=unchecked]:rtl:-translate-x-6 motion-reduce:translate-x-0 pointer-events-none relative flex items-center justify-center text-center transition-transform duration-200 ease-out peer-data-[state=checked]:invisible motion-reduce:transition-none motion-reduce:duration-0'>\n <XIcon aria-hidden='true' className='size-4' />\n </span>\n <span className='min-w-8 motion-reduce:translate-x-0 pointer-events-none relative flex items-center justify-center text-center transition-transform duration-200 ease-out peer-data-[state=checked]:-translate-x-full peer-data-[state=checked]:text-background peer-data-[state=unchecked]:invisible motion-reduce:transition-none motion-reduce:duration-0 peer-data-[state=checked]:rtl:translate-x-full'>\n <CheckIcon aria-hidden='true' className='size-4' />\n </span>\n </div>\n ) : (\n <SwitchBase\n aria-required={required}\n aria-describedby={[messageId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n aria-invalid={state === 'error' ? true : undefined}\n aria-label={ariaLabel ?? (hideLabel ? resolvedLabelText : undefined)}\n className={cn(\n isSquared\n ? 'peer h-6 w-10 rounded-sm focus-visible:ring-black inline-flex shrink-0 items-center border-2 border-transparent transition-colors outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-switch-bg--checked data-[state=unchecked]:bg-switch-bg [&_span]:rounded-[4px]'\n : 'focus-visible:ring-ring peer h-6 w-10 shadow-2xs inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-switch-bg--checked data-[state=unchecked]:bg-switch-bg',\n className,\n )}\n data-testid='spectral-switch'\n disabled={isDisabled}\n id={switchId}\n name={name}\n onCheckedChange={onChange}\n ref={ref}\n required={required}\n value={value}\n {...props}\n >\n <SwitchThumb className={cn(thumbBase, isSquared ? 'size-5 shadow-xs data-[state=checked]:translate-x-4 data-[state=checked]:rtl:-translate-x-4' : 'h-5 w-5 shadow-lg data-[state=checked]:translate-x-4')} />\n </SwitchBase>\n )}\n\n {labelPosition === 'right' && !hideLabel && resolvedLabelText && (\n <Label className='ml-2' data-testid='spectral-switch-label-right' htmlFor={switchId} id={`${switchId}-label`}>\n {resolvedLabelText}\n </Label>\n )}\n\n {hideLabel && resolvedLabelText && (\n <Label className='sr-only' data-testid='spectral-switch-label-hidden' htmlFor={switchId}>\n {resolvedLabelText}\n </Label>\n )}\n <ErrorMessage\n dataTestId='spectral-switch-error-message'\n id={errorMessageId}\n message={state === 'error' ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-switch-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nSwitch.displayName = 'Switch'\n"],"mappings":";;;;;;;;;;;AA6BA,MAAM,YAAY;AAElB,MAAa,UAAU,EACrB,WACA,UACA,cACA,YAAY,OACZ,IACA,OACA,gBAAgB,SAChB,WACA,sBAAsB,GACtB,sBAAsB,OACtB,MACA,UACA,KACA,UACA,QAAQ,WACR,OACA,SACA,gBACA,oBAAoB,iBACpB,cAAc,WACd,GAAG,YACc;CACjB,MAAM,WAAW,eAAe,IAAI,KAAI;CACxC,MAAM,aAAa,QAAQ,SAAQ;CACnC,MAAM,oBAAoB,aAAa;CACvC,MAAM,iBAAiB,kBAAkB,SAAQ;CACjD,MAAM,mBAAmB,oBAAoB,SAAQ;CACrD,MAAM,YAAY,UAAU,WAAW,gBAAgB,iBAAiB,iBAAiB,UAAU,aAAa,kBAAkB,mBAAmB,mBAAmB;CACxK,MAAM,YAAY,YAAY;AAG9B,QACE,qBAAC,OAAD;EAAK,WAAU;EAA4D,cAAY;YAAvF;GACG,kBAAkB,UAAU,CAAC,aAAa,qBACzC,oBAAC,OAAD;IAAO,WAAU;IAAgD,SAAS;cACvE;IACI;GAPgB,YAAY,wBAWnC,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,UAAD;MACE,oBAAkB,CAAC,WAAW,gBAAgB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;MAC5E,gBAAc,UAAU,UAAU,OAAO;MACzC,cAAY,cAAc,YAAY,oBAAoB;MAC1D,iBAAe;MACf,WAAW,GACT,kNACA,oKACA,mDACA,UACD;MAED,UAAU;MACV,IAAI;MACE;MACN,iBAAiB;MACZ;MACK;MACH;MACP,GAAI;gBAEJ,oBAAC,aAAD,EAAa,WAAW,GAAG,WAAW,uKAAuK,EAAG;MACtM;KACZ,oBAAC,QAAD;MAAM,WAAU;gBACd,oBAAC,OAAD;OAAO,eAAY;OAAO,WAAU;OAAU;MAC1C;KACN,oBAAC,QAAD;MAAM,WAAU;gBACd,oBAAC,WAAD;OAAW,eAAY;OAAO,WAAU;OAAU;MAC9C;KACH;QAEL,oBAAC,UAAD;IACE,iBAAe;IACf,oBAAkB,CAAC,WAAW,gBAAgB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;IAC5E,gBAAc,UAAU,UAAU,OAAO;IACzC,cAAY,cAAc,YAAY,oBAAoB;IAC1D,WAAW,GACT,YACI,kUACA,gZACJ,UACD;IAED,UAAU;IACV,IAAI;IACE;IACN,iBAAiB;IACZ;IACK;IACH;IACP,GAAI;cAEJ,oBAAC,aAAD,EAAa,WAAW,GAAG,WAAW,YAAY,gGAAgG,uDAAuD,EAAG;IAClM;GAGb,kBAAkB,WAAW,CAAC,aAAa,qBAC1C,oBAAC,OAAD;IAAO,WAAU;IAAiD,SAAS;IAAU,IAAI,GAAG,SAAS;cAClG;IACI;GAGR,aAAa,qBACZ,oBAAC,OAAD;IAAO,WAAU;IAAqD,SAAS;cAC5E;IACI;GAET,oBAAC,cAAD;IACE,YAAW;IACX,IAAI;IACJ,SAAS,UAAU,UAAW,gBAAgB,OAAQ;IACjC;IACrB,qBAAqB,uBAAuB,UAAU;IACvD;GACD,oBAAC,gBAAD;IACE,YAAW;IACX,IAAI;IACJ,SAAS,UAAU,YAAa,kBAAkB,OAAQ;IACrC;IACrB,qBAAqB,uBAAuB,UAAU;IACvD;GACE;;;AAGT,OAAO,cAAc"}
1
+ {"version":3,"file":"Switch.js","names":[],"sources":["../src/components/Switch/Switch.tsx"],"sourcesContent":["import { Label } from '@components/Label/Label'\nimport { ErrorMessage, getErrorMessageId, getWarningMessageId, useFormFieldId, WarningMessage, type BaseFormFieldProps, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { CheckIcon, XIcon } from 'lucide-react'\nimport { type Ref } from 'react'\nimport { Switch as SwitchBase, SwitchThumb, type SwitchProps as SwitchBaseProps } from './SwitchBase'\n\ntype SwitchVisualState = Exclude<FormFieldState, 'disabled'>\n\nexport type SwitchProps = Omit<SwitchBaseProps, 'onCheckedChange'> & {\n errorMessage?: BaseFormFieldProps['errorMessage']\n hideLabel?: boolean\n id?: string\n label?: string\n labelPosition?: 'left' | 'right'\n /** @deprecated Use `label` instead. `labelText` will be removed in a future release. */\n labelText?: string\n messageReserveLines?: number\n messageReserveSpace?: boolean\n onChange?: (checked: boolean) => void\n ref?: Ref<HTMLButtonElement>\n required?: boolean\n state?: SwitchVisualState\n value?: string\n variant?: 'default' | 'squared' | 'permanent-indicator'\n warningMessage?: BaseFormFieldProps['errorMessage']\n 'aria-describedby'?: string\n 'aria-label'?: string\n}\n\nconst thumbBase = 'bg-switch-thumb--checked data-[state=checked]:bg-switch-thumb pointer-events-none block rounded-full ring-0 transition-transform motion-reduce:transition-none'\n\nexport const Switch = ({\n className,\n disabled,\n errorMessage,\n hideLabel = false,\n id,\n label,\n labelPosition = 'right',\n labelText,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n ref,\n required,\n state = 'default',\n value,\n variant,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n ...props\n}: SwitchProps) => {\n const switchId = useFormFieldId(id, name)\n const isDisabled = Boolean(disabled)\n const resolvedLabelText = label ?? labelText\n const errorMessageId = getErrorMessageId(switchId)\n const warningMessageId = getWarningMessageId(switchId)\n const messageId = state === 'error' && errorMessage && errorMessageId ? errorMessageId : state === 'warning' && warningMessage && warningMessageId ? warningMessageId : undefined\n const isSquared = variant === 'squared'\n const isPermanentIndicator = variant === 'permanent-indicator'\n\n return (\n <div className='flex items-center' data-testid='spectral-switch-container' data-state={state}>\n {labelPosition === 'left' && !hideLabel && resolvedLabelText && (\n <Label className='mr-2' data-testid='spectral-switch-label-left' htmlFor={switchId}>\n {resolvedLabelText}\n </Label>\n )}\n\n {isPermanentIndicator ? (\n <div className='h-7 text-sm font-medium relative inline-grid grid-cols-[1fr_1fr] items-center'>\n <SwitchBase\n aria-describedby={[messageId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n aria-invalid={state === 'error' ? true : undefined}\n aria-label={ariaLabel ?? (hideLabel ? resolvedLabelText : undefined)}\n aria-required={required}\n className={cn(\n 'peer inset-0 w-14 focus-visible:ring-ring absolute inline-flex h-[inherit] items-center focus-visible:ring-offset-background data-[state=checked]:bg-switch-bg--checked data-[state=unchecked]:bg-switch-bg/50',\n 'shadow-2xs cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n data-testid='spectral-switch'\n disabled={isDisabled}\n id={switchId}\n name={name}\n onCheckedChange={onChange}\n ref={ref}\n required={required}\n value={value}\n {...props}\n >\n <SwitchThumb className={cn(thumbBase, 'size-6.5 shadow-xs relative z-10 duration-200 ease-out data-[state=checked]:translate-x-[26px] motion-reduce:duration-0 data-[state=checked]:rtl:translate-x-[-26px]')} />\n </SwitchBase>\n <span className='ml-0.5 min-w-8 peer-data-[state=unchecked]:translate-x-6 peer-data-[state=unchecked]:rtl:-translate-x-6 motion-reduce:translate-x-0 pointer-events-none relative flex items-center justify-center text-center transition-transform duration-200 ease-out peer-data-[state=checked]:invisible motion-reduce:transition-none motion-reduce:duration-0'>\n <XIcon aria-hidden='true' className='size-4' />\n </span>\n <span className='min-w-8 motion-reduce:translate-x-0 pointer-events-none relative flex items-center justify-center text-center transition-transform duration-200 ease-out peer-data-[state=checked]:-translate-x-full peer-data-[state=checked]:text-background peer-data-[state=unchecked]:invisible motion-reduce:transition-none motion-reduce:duration-0 peer-data-[state=checked]:rtl:translate-x-full'>\n <CheckIcon aria-hidden='true' className='size-4' />\n </span>\n </div>\n ) : (\n <SwitchBase\n aria-required={required}\n aria-describedby={[messageId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n aria-invalid={state === 'error' ? true : undefined}\n aria-label={ariaLabel ?? (hideLabel ? resolvedLabelText : undefined)}\n className={cn(\n isSquared\n ? 'peer h-6 w-10 rounded-sm focus-visible:ring-black inline-flex shrink-0 items-center border-2 border-transparent transition-colors outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-switch-bg--checked data-[state=unchecked]:bg-switch-bg [&_span]:rounded-[4px]'\n : 'focus-visible:ring-ring peer h-6 w-10 shadow-2xs inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-switch-bg--checked data-[state=unchecked]:bg-switch-bg',\n className,\n )}\n data-testid='spectral-switch'\n disabled={isDisabled}\n id={switchId}\n name={name}\n onCheckedChange={onChange}\n ref={ref}\n required={required}\n value={value}\n {...props}\n >\n <SwitchThumb className={cn(thumbBase, isSquared ? 'size-5 shadow-xs data-[state=checked]:translate-x-4 data-[state=checked]:rtl:-translate-x-4' : 'h-5 w-5 shadow-lg data-[state=checked]:translate-x-4')} />\n </SwitchBase>\n )}\n\n {labelPosition === 'right' && !hideLabel && resolvedLabelText && (\n <Label className='ml-2' data-testid='spectral-switch-label-right' htmlFor={switchId} id={`${switchId}-label`}>\n {resolvedLabelText}\n </Label>\n )}\n\n {hideLabel && resolvedLabelText && (\n <Label className='sr-only' data-testid='spectral-switch-label-hidden' htmlFor={switchId}>\n {resolvedLabelText}\n </Label>\n )}\n <ErrorMessage\n dataTestId='spectral-switch-error-message'\n id={errorMessageId}\n message={state === 'error' ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-switch-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nSwitch.displayName = 'Switch'\n"],"mappings":";;;;;;;;;;;AA8BA,MAAM,YAAY;AAElB,MAAa,UAAU,EACrB,WACA,UACA,cACA,YAAY,OACZ,IACA,OACA,gBAAgB,SAChB,WACA,sBAAsB,GACtB,sBAAsB,OACtB,MACA,UACA,KACA,UACA,QAAQ,WACR,OACA,SACA,gBACA,oBAAoB,iBACpB,cAAc,WACd,GAAG,YACc;CACjB,MAAM,WAAW,eAAe,IAAI,KAAI;CACxC,MAAM,aAAa,QAAQ,SAAQ;CACnC,MAAM,oBAAoB,SAAS;CACnC,MAAM,iBAAiB,kBAAkB,SAAQ;CACjD,MAAM,mBAAmB,oBAAoB,SAAQ;CACrD,MAAM,YAAY,UAAU,WAAW,gBAAgB,iBAAiB,iBAAiB,UAAU,aAAa,kBAAkB,mBAAmB,mBAAmB;CACxK,MAAM,YAAY,YAAY;AAG9B,QACE,qBAAC,OAAD;EAAK,WAAU;EAA4D,cAAY;YAAvF;GACG,kBAAkB,UAAU,CAAC,aAAa,qBACzC,oBAAC,OAAD;IAAO,WAAU;IAAgD,SAAS;cACvE;IACI;GAPgB,YAAY,wBAWnC,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,UAAD;MACE,oBAAkB,CAAC,WAAW,gBAAgB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;MAC5E,gBAAc,UAAU,UAAU,OAAO;MACzC,cAAY,cAAc,YAAY,oBAAoB;MAC1D,iBAAe;MACf,WAAW,GACT,kNACA,oKACA,mDACA,UACD;MAED,UAAU;MACV,IAAI;MACE;MACN,iBAAiB;MACZ;MACK;MACH;MACP,GAAI;gBAEJ,oBAAC,aAAD,EAAa,WAAW,GAAG,WAAW,uKAAuK,EAAG;MACtM;KACZ,oBAAC,QAAD;MAAM,WAAU;gBACd,oBAAC,OAAD;OAAO,eAAY;OAAO,WAAU;OAAU;MAC1C;KACN,oBAAC,QAAD;MAAM,WAAU;gBACd,oBAAC,WAAD;OAAW,eAAY;OAAO,WAAU;OAAU;MAC9C;KACH;QAEL,oBAAC,UAAD;IACE,iBAAe;IACf,oBAAkB,CAAC,WAAW,gBAAgB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IAAI;IAC5E,gBAAc,UAAU,UAAU,OAAO;IACzC,cAAY,cAAc,YAAY,oBAAoB;IAC1D,WAAW,GACT,YACI,kUACA,gZACJ,UACD;IAED,UAAU;IACV,IAAI;IACE;IACN,iBAAiB;IACZ;IACK;IACH;IACP,GAAI;cAEJ,oBAAC,aAAD,EAAa,WAAW,GAAG,WAAW,YAAY,gGAAgG,uDAAuD,EAAG;IAClM;GAGb,kBAAkB,WAAW,CAAC,aAAa,qBAC1C,oBAAC,OAAD;IAAO,WAAU;IAAiD,SAAS;IAAU,IAAI,GAAG,SAAS;cAClG;IACI;GAGR,aAAa,qBACZ,oBAAC,OAAD;IAAO,WAAU;IAAqD,SAAS;cAC5E;IACI;GAET,oBAAC,cAAD;IACE,YAAW;IACX,IAAI;IACJ,SAAS,UAAU,UAAW,gBAAgB,OAAQ;IACjC;IACrB,qBAAqB,uBAAuB,UAAU;IACvD;GACD,oBAAC,gBAAD;IACE,YAAW;IACX,IAAI;IACJ,SAAS,UAAU,YAAa,kBAAkB,OAAQ;IACrC;IACrB,qBAAqB,uBAAuB,UAAU;IACvD;GACE;;;AAGT,OAAO,cAAc"}
@@ -171,6 +171,8 @@
171
171
  --color-radio-border--hover: var(--horizon-color-accent);
172
172
  --color-radio-border--selected: var(--horizon-color-accent);
173
173
 
174
+ --color-radio-button-outline-border: var(--horizon-color-level-four);
175
+
174
176
  --color-radio-tile-border: oklch(from var(--horizon-color-white) l c h / 0.1);
175
177
  --color-radio-tile-border--hover: var(--horizon-color-neutral-300);
176
178
  --color-radio-tile-border--selected: var(--horizon-color-accent);
@@ -234,9 +236,9 @@
234
236
  --color-toggle-text--active-default: var(--color-text-primary);
235
237
  --color-toggle-text--active-accent: var(--color-text-inverted);
236
238
  --color-toggle-text--active: var(--color-toggle-text--active-default);
237
- --color-toggle-outline-border: var(--horizon-color-neutral-700);
238
- --color-toggle-outline-divider: var(--horizon-color-neutral-600);
239
- --color-toggle-outline-border--hover: var(--horizon-color-neutral-400);
239
+ --color-toggle-outline-border: var(--horizon-color-level-four);
240
+ --color-toggle-outline-divider: var(--horizon-color-level-four);
241
+ --color-toggle-outline-border--hover: var(--horizon-color-level-five);
240
242
  --color-toggle-group-bg: var(--horizon-color-level-five);
241
243
 
242
244
  --color-tooltip-bg: var(--horizon-color-level-one);