@spear-ai/spectral 1.15.0 → 1.15.1

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 (272) hide show
  1. package/dist/Accordion.d.ts.map +1 -1
  2. package/dist/Accordion.js.map +1 -1
  3. package/dist/Alert/AlertBase.d.ts.map +1 -1
  4. package/dist/Alert/AlertBase.js.map +1 -1
  5. package/dist/Alert.js.map +1 -1
  6. package/dist/Avatar.js.map +1 -1
  7. package/dist/Badge.d.ts.map +1 -1
  8. package/dist/Badge.js +3 -3
  9. package/dist/Badge.js.map +1 -1
  10. package/dist/Button.js.map +1 -1
  11. package/dist/ButtonGroup/ButtonGroupButton.js.map +1 -1
  12. package/dist/ButtonGroup.d.ts.map +1 -1
  13. package/dist/ButtonGroup.js.map +1 -1
  14. package/dist/ButtonIcon.js.map +1 -1
  15. package/dist/Checkbox/CheckboxBase.js.map +1 -1
  16. package/dist/Checkbox.js.map +1 -1
  17. package/dist/Combobox.js +0 -1
  18. package/dist/Combobox.js.map +1 -1
  19. package/dist/ControlGroup/ControlGroupSelect.js.map +1 -1
  20. package/dist/ControlGroup.d.ts.map +1 -1
  21. package/dist/ControlGroup.js.map +1 -1
  22. package/dist/DataCard/Card.d.ts.map +1 -1
  23. package/dist/DataCard/Card.js.map +1 -1
  24. package/dist/DataCard.js.map +1 -1
  25. package/dist/DateTimePicker/Calendar.js.map +1 -1
  26. package/dist/DateTimePicker/DateTimeDisplayInput.js.map +1 -1
  27. package/dist/DateTimePicker/TimePeriodSelect.js.map +1 -1
  28. package/dist/DateTimePicker/TimePicker.js.map +1 -1
  29. package/dist/DateTimePicker.js.map +1 -1
  30. package/dist/Dialog.d.ts.map +1 -1
  31. package/dist/Dialog.js.map +1 -1
  32. package/dist/Drawer.js.map +1 -1
  33. package/dist/DropdownMenu.js.map +1 -1
  34. package/dist/FormFieldMessage.d.ts.map +1 -1
  35. package/dist/FormFieldMessage.js +2 -2
  36. package/dist/FormFieldMessage.js.map +1 -1
  37. package/dist/HoverCard.d.ts.map +1 -1
  38. package/dist/HoverCard.js.map +1 -1
  39. package/dist/Icons/AdjustmentsIcon.d.ts.map +1 -1
  40. package/dist/Icons/AdjustmentsIcon.js.map +1 -1
  41. package/dist/Icons/AnalyzeIcon.d.ts.map +1 -1
  42. package/dist/Icons/AnalyzeIcon.js.map +1 -1
  43. package/dist/Icons/AnnotationsIcon.d.ts.map +1 -1
  44. package/dist/Icons/AnnotationsIcon.js.map +1 -1
  45. package/dist/Icons/ApprovedIcon.d.ts.map +1 -1
  46. package/dist/Icons/ApprovedIcon.js.map +1 -1
  47. package/dist/Icons/ArrowDownIcon.d.ts.map +1 -1
  48. package/dist/Icons/ArrowDownIcon.js.map +1 -1
  49. package/dist/Icons/ArrowUpIcon.d.ts.map +1 -1
  50. package/dist/Icons/ArrowUpIcon.js.map +1 -1
  51. package/dist/Icons/BoxToolIcon.d.ts.map +1 -1
  52. package/dist/Icons/BoxToolIcon.js.map +1 -1
  53. package/dist/Icons/CalendarIcon.d.ts.map +1 -1
  54. package/dist/Icons/CalendarIcon.js.map +1 -1
  55. package/dist/Icons/CheckCircleIcon.d.ts.map +1 -1
  56. package/dist/Icons/CheckCircleIcon.js.map +1 -1
  57. package/dist/Icons/CheckSquareIcon.d.ts.map +1 -1
  58. package/dist/Icons/CheckSquareIcon.js.map +1 -1
  59. package/dist/Icons/CheckmarkIcon.d.ts.map +1 -1
  60. package/dist/Icons/CheckmarkIcon.js.map +1 -1
  61. package/dist/Icons/ChevronDownIcon.d.ts.map +1 -1
  62. package/dist/Icons/ChevronDownIcon.js.map +1 -1
  63. package/dist/Icons/ChevronUpIcon.d.ts.map +1 -1
  64. package/dist/Icons/ChevronUpIcon.js.map +1 -1
  65. package/dist/Icons/ClockIcon.d.ts.map +1 -1
  66. package/dist/Icons/ClockIcon.js.map +1 -1
  67. package/dist/Icons/CloseCircleIcon.d.ts.map +1 -1
  68. package/dist/Icons/CloseCircleIcon.js.map +1 -1
  69. package/dist/Icons/CloseIcon.d.ts.map +1 -1
  70. package/dist/Icons/CloseIcon.js.map +1 -1
  71. package/dist/Icons/Crosshairs2Icon.d.ts.map +1 -1
  72. package/dist/Icons/Crosshairs2Icon.js.map +1 -1
  73. package/dist/Icons/CrosshairsIcon.d.ts.map +1 -1
  74. package/dist/Icons/CrosshairsIcon.js.map +1 -1
  75. package/dist/Icons/DashboardIcon.d.ts.map +1 -1
  76. package/dist/Icons/DashboardIcon.js.map +1 -1
  77. package/dist/Icons/DatabaseIcon.d.ts.map +1 -1
  78. package/dist/Icons/DatabaseIcon.js.map +1 -1
  79. package/dist/Icons/DeleteIcon.d.ts.map +1 -1
  80. package/dist/Icons/DeleteIcon.js.map +1 -1
  81. package/dist/Icons/DurationIcon.d.ts.map +1 -1
  82. package/dist/Icons/DurationIcon.js.map +1 -1
  83. package/dist/Icons/EditIcon.d.ts.map +1 -1
  84. package/dist/Icons/EditIcon.js.map +1 -1
  85. package/dist/Icons/EmailIcon.d.ts.map +1 -1
  86. package/dist/Icons/EmailIcon.js.map +1 -1
  87. package/dist/Icons/EraserIcon.d.ts.map +1 -1
  88. package/dist/Icons/EraserIcon.js.map +1 -1
  89. package/dist/Icons/ErrorIcon.d.ts.map +1 -1
  90. package/dist/Icons/ErrorIcon.js.map +1 -1
  91. package/dist/Icons/EyeClosedIcon.d.ts.map +1 -1
  92. package/dist/Icons/EyeClosedIcon.js.map +1 -1
  93. package/dist/Icons/EyeClosedIcon2.d.ts.map +1 -1
  94. package/dist/Icons/EyeClosedIcon2.js.map +1 -1
  95. package/dist/Icons/EyeOpenIcon.d.ts.map +1 -1
  96. package/dist/Icons/EyeOpenIcon.js.map +1 -1
  97. package/dist/Icons/FileDownloadIcon.d.ts.map +1 -1
  98. package/dist/Icons/FileDownloadIcon.js.map +1 -1
  99. package/dist/Icons/GoToFirstIcon.d.ts.map +1 -1
  100. package/dist/Icons/GoToFirstIcon.js.map +1 -1
  101. package/dist/Icons/GoToLastIcon.d.ts.map +1 -1
  102. package/dist/Icons/GoToLastIcon.js.map +1 -1
  103. package/dist/Icons/HarmonicCursorsIcon.d.ts.map +1 -1
  104. package/dist/Icons/HarmonicCursorsIcon.js.map +1 -1
  105. package/dist/Icons/InfoIcon.d.ts.map +1 -1
  106. package/dist/Icons/InfoIcon.js.map +1 -1
  107. package/dist/Icons/KeyboardIcon.d.ts.map +1 -1
  108. package/dist/Icons/KeyboardIcon.js.map +1 -1
  109. package/dist/Icons/LabelIcon.d.ts.map +1 -1
  110. package/dist/Icons/LabelIcon.js.map +1 -1
  111. package/dist/Icons/LassoIcon.d.ts.map +1 -1
  112. package/dist/Icons/LassoIcon.js.map +1 -1
  113. package/dist/Icons/LineToolIcon.d.ts.map +1 -1
  114. package/dist/Icons/LineToolIcon.js.map +1 -1
  115. package/dist/Icons/LiveViewIcon.d.ts.map +1 -1
  116. package/dist/Icons/LiveViewIcon.js.map +1 -1
  117. package/dist/Icons/LoaderIcon.d.ts.map +1 -1
  118. package/dist/Icons/LoaderIcon.js.map +1 -1
  119. package/dist/Icons/LocationIcon.d.ts.map +1 -1
  120. package/dist/Icons/LocationIcon.js.map +1 -1
  121. package/dist/Icons/LogoutIcon.d.ts.map +1 -1
  122. package/dist/Icons/LogoutIcon.js.map +1 -1
  123. package/dist/Icons/MaximizeIcon.d.ts.map +1 -1
  124. package/dist/Icons/MaximizeIcon.js.map +1 -1
  125. package/dist/Icons/MeasureIcon.d.ts.map +1 -1
  126. package/dist/Icons/MeasureIcon.js.map +1 -1
  127. package/dist/Icons/MenuDotsIcon.d.ts.map +1 -1
  128. package/dist/Icons/MenuDotsIcon.js.map +1 -1
  129. package/dist/Icons/MenuIcon.d.ts.map +1 -1
  130. package/dist/Icons/MenuIcon.js.map +1 -1
  131. package/dist/Icons/MessagesIcon.d.ts.map +1 -1
  132. package/dist/Icons/MessagesIcon.js.map +1 -1
  133. package/dist/Icons/MetadataIcon.d.ts.map +1 -1
  134. package/dist/Icons/MetadataIcon.js.map +1 -1
  135. package/dist/Icons/MinimizeIcon.d.ts.map +1 -1
  136. package/dist/Icons/MinimizeIcon.js.map +1 -1
  137. package/dist/Icons/MinusIcon.d.ts.map +1 -1
  138. package/dist/Icons/MinusIcon.js.map +1 -1
  139. package/dist/Icons/OntologyIcon.d.ts.map +1 -1
  140. package/dist/Icons/OntologyIcon.js.map +1 -1
  141. package/dist/Icons/PanelIconClose.d.ts.map +1 -1
  142. package/dist/Icons/PanelIconClose.js.map +1 -1
  143. package/dist/Icons/PanelIconOpen.d.ts.map +1 -1
  144. package/dist/Icons/PanelIconOpen.js.map +1 -1
  145. package/dist/Icons/PauseIcon.d.ts.map +1 -1
  146. package/dist/Icons/PauseIcon.js.map +1 -1
  147. package/dist/Icons/PlayIcon.d.ts.map +1 -1
  148. package/dist/Icons/PlayIcon.js.map +1 -1
  149. package/dist/Icons/PlusIcon.d.ts.map +1 -1
  150. package/dist/Icons/PlusIcon.js.map +1 -1
  151. package/dist/Icons/PolygonIcon.d.ts.map +1 -1
  152. package/dist/Icons/PolygonIcon.js.map +1 -1
  153. package/dist/Icons/PrinterIcon.d.ts.map +1 -1
  154. package/dist/Icons/PrinterIcon.js.map +1 -1
  155. package/dist/Icons/ProgressCheckIcon.d.ts.map +1 -1
  156. package/dist/Icons/ProgressCheckIcon.js.map +1 -1
  157. package/dist/Icons/ResetIcon.d.ts.map +1 -1
  158. package/dist/Icons/ResetIcon.js.map +1 -1
  159. package/dist/Icons/ReviewedIcon.d.ts.map +1 -1
  160. package/dist/Icons/ReviewedIcon.js.map +1 -1
  161. package/dist/Icons/ScissorsIcon.d.ts.map +1 -1
  162. package/dist/Icons/ScissorsIcon.js.map +1 -1
  163. package/dist/Icons/SearchIcon.d.ts.map +1 -1
  164. package/dist/Icons/SearchIcon.js.map +1 -1
  165. package/dist/Icons/SettingsIcon.d.ts.map +1 -1
  166. package/dist/Icons/SettingsIcon.js.map +1 -1
  167. package/dist/Icons/SortAscendingIcon.d.ts.map +1 -1
  168. package/dist/Icons/SortAscendingIcon.js.map +1 -1
  169. package/dist/Icons/SortAtoZIcon.d.ts.map +1 -1
  170. package/dist/Icons/SortAtoZIcon.js.map +1 -1
  171. package/dist/Icons/SortDescendingIcon.d.ts.map +1 -1
  172. package/dist/Icons/SortDescendingIcon.js.map +1 -1
  173. package/dist/Icons/SortZtoAIcon.d.ts.map +1 -1
  174. package/dist/Icons/SortZtoAIcon.js.map +1 -1
  175. package/dist/Icons/SparklesIcon.d.ts.map +1 -1
  176. package/dist/Icons/SparklesIcon.js.map +1 -1
  177. package/dist/Icons/StackIcon.d.ts.map +1 -1
  178. package/dist/Icons/StackIcon.js.map +1 -1
  179. package/dist/Icons/StarIcon.d.ts.map +1 -1
  180. package/dist/Icons/StarIcon.js.map +1 -1
  181. package/dist/Icons/TrashIcon.d.ts.map +1 -1
  182. package/dist/Icons/TrashIcon.js.map +1 -1
  183. package/dist/Icons/UndoIcon.d.ts.map +1 -1
  184. package/dist/Icons/UndoIcon.js.map +1 -1
  185. package/dist/Icons/UploadIcon.d.ts.map +1 -1
  186. package/dist/Icons/UploadIcon.js.map +1 -1
  187. package/dist/Icons/User2Icon.d.ts.map +1 -1
  188. package/dist/Icons/User2Icon.js.map +1 -1
  189. package/dist/Icons/UserIcon.d.ts.map +1 -1
  190. package/dist/Icons/UserIcon.js.map +1 -1
  191. package/dist/Icons/WarningIcon.d.ts.map +1 -1
  192. package/dist/Icons/WarningIcon.js.map +1 -1
  193. package/dist/Icons/ZoomAllIcon.d.ts.map +1 -1
  194. package/dist/Icons/ZoomAllIcon.js.map +1 -1
  195. package/dist/Icons/ZoomXIcon.d.ts.map +1 -1
  196. package/dist/Icons/ZoomXIcon.js.map +1 -1
  197. package/dist/Icons/ZoomYIcon.d.ts.map +1 -1
  198. package/dist/Icons/ZoomYIcon.js.map +1 -1
  199. package/dist/IconsAnimated/PanelLeftCloseIcon.js.map +1 -1
  200. package/dist/IconsAnimated/PanelLeftOpenIcon.js.map +1 -1
  201. package/dist/Input.js.map +1 -1
  202. package/dist/InputNumeric.js +0 -6
  203. package/dist/InputNumeric.js.map +1 -1
  204. package/dist/InputOTP.d.ts.map +1 -1
  205. package/dist/InputOTP.js +0 -1
  206. package/dist/InputOTP.js.map +1 -1
  207. package/dist/Kbd.d.ts.map +1 -1
  208. package/dist/Kbd.js.map +1 -1
  209. package/dist/Label.js.map +1 -1
  210. package/dist/MultiSelect/MultiSelectBase.js +1 -1
  211. package/dist/MultiSelect/MultiSelectBase.js.map +1 -1
  212. package/dist/MultiSelect.js.map +1 -1
  213. package/dist/Popover.d.ts.map +1 -1
  214. package/dist/Popover.js.map +1 -1
  215. package/dist/RadioButton.d.ts +6 -0
  216. package/dist/RadioButton.d.ts.map +1 -1
  217. package/dist/RadioButton.js +7 -2
  218. package/dist/RadioButton.js.map +1 -1
  219. package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts +15 -1
  220. package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts.map +1 -1
  221. package/dist/RadioButtonGroup/RadioButtonGroupBase.js +166 -15
  222. package/dist/RadioButtonGroup/RadioButtonGroupBase.js.map +1 -1
  223. package/dist/RadioButtonGroup.d.ts +4 -1
  224. package/dist/RadioButtonGroup.d.ts.map +1 -1
  225. package/dist/RadioButtonGroup.js.map +1 -1
  226. package/dist/RadioGroup.d.ts.map +1 -1
  227. package/dist/RadioGroup.js.map +1 -1
  228. package/dist/Select.js +1 -2
  229. package/dist/Select.js.map +1 -1
  230. package/dist/Skeleton.js.map +1 -1
  231. package/dist/Slider.js.map +1 -1
  232. package/dist/Switch/SwitchBase.d.ts.map +1 -1
  233. package/dist/Switch/SwitchBase.js.map +1 -1
  234. package/dist/Switch.js.map +1 -1
  235. package/dist/Tabs/TabsBase.d.ts.map +1 -1
  236. package/dist/Tabs/TabsBase.js +1 -0
  237. package/dist/Tabs/TabsBase.js.map +1 -1
  238. package/dist/Tabs.d.ts.map +1 -1
  239. package/dist/Tabs.js.map +1 -1
  240. package/dist/Textarea.js.map +1 -1
  241. package/dist/Toast.d.ts.map +1 -1
  242. package/dist/Toast.js.map +1 -1
  243. package/dist/Toggle/ToggleBase.js.map +1 -1
  244. package/dist/Toggle.d.ts +5 -6
  245. package/dist/Toggle.d.ts.map +1 -1
  246. package/dist/Toggle.js +4 -19
  247. package/dist/Toggle.js.map +1 -1
  248. package/dist/ToggleGroup/ToggleGroupBase.d.ts.map +1 -1
  249. package/dist/ToggleGroup/ToggleGroupBase.js.map +1 -1
  250. package/dist/ToggleGroup/ToggleGroupItem.d.ts +3 -2
  251. package/dist/ToggleGroup/ToggleGroupItem.d.ts.map +1 -1
  252. package/dist/ToggleGroup/ToggleGroupItem.js +3 -2
  253. package/dist/ToggleGroup/ToggleGroupItem.js.map +1 -1
  254. package/dist/ToggleGroup/ToggleGroupSplitMenuItem.d.ts.map +1 -1
  255. package/dist/ToggleGroup/ToggleGroupSplitMenuItem.js +3 -2
  256. package/dist/ToggleGroup/ToggleGroupSplitMenuItem.js.map +1 -1
  257. package/dist/ToggleGroup.d.ts +3 -2
  258. package/dist/ToggleGroup.d.ts.map +1 -1
  259. package/dist/ToggleGroup.js +1 -1
  260. package/dist/ToggleGroup.js.map +1 -1
  261. package/dist/Tooltip.d.ts.map +1 -1
  262. package/dist/Tooltip.js.map +1 -1
  263. package/dist/Tray.d.ts.map +1 -1
  264. package/dist/Tray.js.map +1 -1
  265. package/dist/components/ToggleGroup/ToggleGroup.context.js.map +1 -1
  266. package/dist/styles/horizon/colors.css +9 -16
  267. package/dist/styles/spectral.css +2 -2
  268. package/dist/utils/activeColorStyle.d.ts +11 -0
  269. package/dist/utils/activeColorStyle.d.ts.map +1 -0
  270. package/dist/utils/activeColorStyle.js +41 -0
  271. package/dist/utils/activeColorStyle.js.map +1 -0
  272. package/package.json +25 -20
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelectBase.js","names":[],"sources":["../../src/components/MultiSelect/MultiSelectBase.tsx"],"sourcesContent":["import { CheckmarkIcon, ChevronDownIcon, CloseIcon, SearchIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport * as Popover from '@radix-ui/react-popover'\nimport { useAutoDropdownHorizontalShift } from '@utils/dropdownPositioning'\nimport { EmptyState, ErrorMessage, getAriaProps, getDropdownSurfaceClasses, getDropdownWidthStyles, getErrorMessageId, getTriggerClasses, LoadingState, WarningMessage, useFormFieldId, type BaseFormFieldProps, type DropdownWidth, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState, type ButtonHTMLAttributes, type ChangeEvent, type CSSProperties, type KeyboardEvent, type Ref } from 'react'\n\nexport type MultiSelectState = Exclude<FormFieldState, 'disabled'>\n\nexport interface MultiSelectOption {\n disabled?: boolean\n group?: string\n label: string\n value: string\n}\n\nexport interface MultiSelectBaseProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value' | 'onChange'> {\n clearAllLabel?: string\n closeOnSelect?: boolean\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n errorMessage?: BaseFormFieldProps['errorMessage']\n id?: string\n label?: string\n loadingMessage?: string\n maxCount?: number\n messageReserveLines?: number\n messageReserveSpace?: boolean\n name?: string\n defaultValue?: string[]\n onChange?: (value: string[]) => void\n options: MultiSelectOption[]\n placeholder?: string\n required?: boolean\n searchPlaceholder?: string\n showClearAll?: boolean\n showSearch?: boolean\n showSelectAll?: boolean\n selectAllLabel?: string\n sortAlphabetically?: boolean\n state?: MultiSelectState\n value?: string[]\n warningMessage?: BaseFormFieldProps['errorMessage']\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nconst ICON_SIZE = 'h-4 w-4'\n\nconst getDropdownClasses = (): string => {\n return cn(\n 'max-h-80 z-50 overflow-hidden',\n getDropdownSurfaceClasses(),\n 'motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:animate-in',\n 'motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:fade-in-0',\n 'motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:zoom-in-95',\n 'motion-safe:data-[side=bottom]:slide-in-from-top-2',\n 'motion-safe:data-[side=top]:slide-in-from-bottom-2',\n 'origin-(--radix-popover-content-transform-origin)',\n )\n}\n\ntype FocusableItem = { type: 'search' } | { type: 'select-all' } | { type: 'option'; index: number; value: string } | { type: 'clear-all' }\n\nconst useKeyboardNavigation = (\n options: MultiSelectOption[],\n onClearAll: () => void,\n onClose: () => void,\n onSelect: (value: string) => void,\n onSelectAll: () => void,\n searchInputRef: React.RefObject<HTMLInputElement | null>,\n showSearch: boolean,\n showSelectAll: boolean,\n showClearAll: boolean,\n) => {\n const [focusedIndex, setFocusedIndex] = useState(-1)\n\n // Build a flat list of all focusable items\n const focusableItems = useMemo((): FocusableItem[] => {\n const items: FocusableItem[] = []\n\n if (showSearch) {\n items.push({ type: 'search' })\n }\n\n if (showSelectAll) {\n items.push({ type: 'select-all' })\n }\n\n options.forEach((option, index) => {\n if (!option.disabled) {\n items.push({ type: 'option', index, value: option.value })\n }\n })\n\n if (showClearAll) {\n items.push({ type: 'clear-all' })\n }\n\n return items\n }, [options, showSearch, showSelectAll, showClearAll])\n\n // Focus the appropriate element when focusedIndex changes\n const focusCurrentItem = useCallback(\n (index: number) => {\n if (index < 0 || index >= focusableItems.length) return\n const item = focusableItems[index]\n if (item.type === 'search') {\n searchInputRef.current?.focus()\n }\n },\n [focusableItems, searchInputRef],\n )\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n const currentItem = focusedIndex >= 0 && focusedIndex < focusableItems.length ? focusableItems[focusedIndex] : null\n\n // Don't prevent default for space in search input (allow typing spaces)\n if (event.key === ' ' && currentItem?.type === 'search') {\n return\n }\n\n // Don't prevent default for Enter in search input (allow form submission behavior)\n if (event.key === 'Enter' && currentItem?.type === 'search') {\n return\n }\n\n const keyHandlers: Record<string, () => void> = {\n ArrowDown: () => {\n event.preventDefault()\n const newIndex = Math.min(focusedIndex + 1, focusableItems.length - 1)\n setFocusedIndex(newIndex)\n focusCurrentItem(newIndex)\n },\n ArrowUp: () => {\n event.preventDefault()\n const newIndex = Math.max(focusedIndex - 1, 0)\n setFocusedIndex(newIndex)\n focusCurrentItem(newIndex)\n },\n Tab: () => {\n // Allow Tab to cycle through focusable items\n if (event.shiftKey) {\n if (focusedIndex <= 0) {\n // At start, close dropdown and return to trigger\n onClose()\n } else {\n event.preventDefault()\n const newIndex = focusedIndex - 1\n setFocusedIndex(newIndex)\n focusCurrentItem(newIndex)\n }\n } else {\n if (focusedIndex >= focusableItems.length - 1) {\n // At end, close dropdown and move to next element\n onClose()\n } else {\n event.preventDefault()\n const newIndex = focusedIndex + 1\n setFocusedIndex(newIndex)\n focusCurrentItem(newIndex)\n }\n }\n },\n Enter: () => {\n event.preventDefault()\n if (focusedIndex >= 0 && focusedIndex < focusableItems.length) {\n const item = focusableItems[focusedIndex]\n if (item.type === 'select-all') {\n onSelectAll()\n } else if (item.type === 'clear-all') {\n onClearAll()\n } else if (item.type === 'option') {\n onSelect(item.value)\n }\n }\n },\n ' ': () => {\n event.preventDefault()\n if (focusedIndex >= 0 && focusedIndex < focusableItems.length) {\n const item = focusableItems[focusedIndex]\n if (item.type === 'select-all') {\n onSelectAll()\n } else if (item.type === 'clear-all') {\n onClearAll()\n } else if (item.type === 'option') {\n onSelect(item.value)\n }\n }\n },\n Escape: () => {\n event.preventDefault()\n onClose()\n },\n }\n\n const handler = keyHandlers[event.key]\n if (handler) {\n handler()\n }\n },\n [focusableItems, focusedIndex, onSelect, onSelectAll, onClearAll, onClose, focusCurrentItem],\n )\n\n // Get the option index for visual focus styling (accounting for select-all offset)\n const getOptionFocusIndex = useCallback(\n (optionIndex: number): boolean => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return false\n const item = focusableItems[focusedIndex]\n return item.type === 'option' && item.index === optionIndex\n },\n [focusedIndex, focusableItems],\n )\n\n const isSearchFocused = useMemo(() => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return false\n return focusableItems[focusedIndex].type === 'search'\n }, [focusedIndex, focusableItems])\n\n const isSelectAllFocused = useMemo(() => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return false\n return focusableItems[focusedIndex].type === 'select-all'\n }, [focusedIndex, focusableItems])\n\n const isClearAllFocused = useMemo(() => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return false\n return focusableItems[focusedIndex].type === 'clear-all'\n }, [focusedIndex, focusableItems])\n\n const focusedOptionValue = useMemo(() => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return null\n const item = focusableItems[focusedIndex]\n return item.type === 'option' ? item.value : null\n }, [focusedIndex, focusableItems])\n\n return {\n focusedIndex,\n setFocusedIndex,\n handleKeyDown,\n getOptionFocusIndex,\n isSearchFocused,\n isSelectAllFocused,\n isClearAllFocused,\n focusedOptionValue,\n }\n}\n\nexport const MultiSelectBase = ({\n className,\n clearAllLabel = 'Clear all',\n closeOnSelect = false,\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found',\n errorMessage,\n defaultValue = [],\n disabled,\n id,\n label,\n loadingMessage = 'Loading options…',\n messageReserveLines = 1,\n messageReserveSpace = false,\n maxCount = 3,\n name,\n onChange,\n options = [],\n placeholder = 'Select options',\n ref,\n searchPlaceholder = 'Search options…',\n selectAllLabel = 'Select all',\n showClearAll = true,\n showSearch = true,\n showSelectAll = true,\n sortAlphabetically = false,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n ...props\n}: MultiSelectBaseProps & {\n ref?: Ref<HTMLButtonElement>\n}) => {\n const generatedId = useId()\n const fallbackName = name ?? `multiselect-${generatedId}`\n const multiSelectId = useFormFieldId(id, fallbackName)\n const listboxId = `${multiSelectId}-listbox`\n const errorMessageId = getErrorMessageId(multiSelectId)\n const warningMessageId = `${multiSelectId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n\n const [isOpen, setIsOpen] = useState(false)\n const { dropdownShiftStyle, setDropdownElement } = useAutoDropdownHorizontalShift(isOpen)\n const [searchValue, setSearchValue] = useState('')\n const [value, setValue] = useUncontrolledState<string[]>({\n value: valueProp,\n defaultValue,\n onChange,\n })\n\n const searchInputRef = useRef<HTMLInputElement>(null)\n\n const isDisabled = Boolean(disabled)\n const isLoading = state === 'loading'\n const ariaProps = getAriaProps(state, ariaDescribedBy, props.required, messageId)\n const { dropdownOverflowStyle, dropdownWidthMode, resolvedDropdownWidth } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: 'var(--radix-popover-trigger-width)',\n })\n\n const filteredOptions = useMemo(() => {\n let filtered = options.filter((option) => option.label.toLowerCase().includes(searchValue.toLowerCase()))\n\n if (sortAlphabetically) {\n filtered = [...filtered].sort((a, b) => a.label.localeCompare(b.label))\n }\n\n return filtered\n }, [options, searchValue, sortAlphabetically])\n\n const groupedOptions = useMemo(() => {\n const groups: Record<string, MultiSelectOption[]> = {}\n const ungrouped: MultiSelectOption[] = []\n\n filteredOptions.forEach((option) => {\n if (option.group) {\n if (!groups[option.group]) {\n groups[option.group] = []\n }\n groups[option.group].push(option)\n } else {\n ungrouped.push(option)\n }\n })\n\n return { groups, ungrouped, hasGroups: Object.keys(groups).length > 0 }\n }, [filteredOptions])\n\n const toggleOption = useCallback(\n (optionValue: string) => {\n const option = options.find((o) => o.value === optionValue)\n if (option?.disabled) return\n\n const newValue = value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue]\n\n setValue(newValue)\n\n if (closeOnSelect) {\n setIsOpen(false)\n }\n },\n [closeOnSelect, options, setValue, value],\n )\n\n const handleSelectAll = useCallback(() => {\n const allValues = options.filter((o) => !o.disabled).map((o) => o.value)\n const isAllSelected = allValues.every((v) => value.includes(v))\n\n if (isAllSelected) {\n setValue([])\n } else {\n setValue(allValues)\n }\n }, [options, setValue, value])\n\n const handleClearAll = useCallback(() => {\n setValue([])\n }, [setValue])\n\n // Check if all non-disabled options are selected\n const allSelectableValues = useMemo(() => options.filter((o) => !o.disabled).map((o) => o.value), [options])\n const isAllSelected = allSelectableValues.length > 0 && allSelectableValues.every((v) => value.includes(v))\n\n const { focusedOptionValue, getOptionFocusIndex, handleKeyDown, isSelectAllFocused, setFocusedIndex } = useKeyboardNavigation(\n filteredOptions,\n handleClearAll,\n () => setIsOpen(false),\n toggleOption,\n handleSelectAll,\n searchInputRef,\n showSearch,\n showSelectAll,\n false, // No separate clear-all button in dropdown\n )\n\n // Set initial focus index when dropdown opens/closes\n useEffect(() => {\n if (isOpen) {\n setFocusedIndex(0)\n } else {\n setFocusedIndex(-1)\n }\n }, [isOpen, setFocusedIndex])\n\n const handleSearchChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value)\n }, [])\n\n const renderSelectedItems = () => {\n if (value.length === 0) {\n return <span className='min-h-8 flex items-center text-input-text-placeholder'>{placeholder}</span>\n }\n\n const displayedValues = value.slice(0, maxCount)\n const remainingCount = value.length - maxCount\n\n return (\n <div className='gap-1 flex flex-wrap items-center overflow-hidden'>\n {displayedValues.map((val) => {\n const option = options.find((o) => o.value === val)\n if (!option) return null\n\n return (\n <span className='gap-1 px-2 py-1 rounded-md text-xs max-w-48 inline-flex items-center bg-input-bg--selected text-input-text' key={val}>\n <span className='truncate'>{option.label}</span>\n <span\n aria-hidden='true'\n className='hover:text-danger rounded-sm cursor-pointer'\n data-testid='spectral-multiselect-remove-item-button'\n onClick={(e) => {\n e.preventDefault()\n e.stopPropagation()\n toggleOption(val)\n }}\n onPointerDown={(e) => {\n e.stopPropagation()\n }}\n >\n <CloseIcon size={12} />\n </span>\n </span>\n )\n })}\n {remainingCount > 0 && <span className='text-input-text-secondary text-xs py-1 flex items-center tabular-nums'>+{remainingCount} more</span>}\n </div>\n )\n }\n\n const renderOption = (option: MultiSelectOption, index: number) => {\n const isSelected = value.includes(option.value)\n const isFocused = getOptionFocusIndex(index)\n const optionId = `${listboxId}-option-${option.value}`\n\n return (\n <button\n aria-selected={isSelected}\n className={cn(\n 'my-0.5 first:mt-0 last:mb-0 gap-3 rounded-sm px-3 py-2 text-sm flex w-full items-center text-left hover:bg-input-bg--hover focus-visible:bg-input-bg--hover focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',\n isFocused && 'bg-input-bg--hover',\n isSelected && 'font-medium text-input-text',\n )}\n disabled={option.disabled}\n id={optionId}\n key={option.value}\n onClick={() => toggleOption(option.value)}\n role='option'\n type='button'\n >\n <div data-testid='spectral-multiselect-selected-indicator' className={cn('w-4 h-4 rounded flex items-center justify-center border border-input-border', isSelected && 'bg-primary border-primary')}>\n {isSelected && <CheckmarkIcon size={12} />}\n </div>\n <span>{option.label}</span>\n </button>\n )\n }\n\n const getCSSCustomProperties = () => ({\n '--multiselect-border-radius': '0.5rem',\n '--multiselect-trigger-height': '3rem',\n '--multiselect-dropdown-max-height': '20rem',\n })\n\n return (\n <div className='w-full' data-testid='spectral-multiselect-root'>\n {label && (\n <Label className={cn('mb-2 block text-text-primary', isDisabled && 'text-text-secondary')} data-testid='spectral-multiselect-label' htmlFor={multiSelectId}>\n {label}\n </Label>\n )}\n <Popover.Root open={isOpen} onOpenChange={setIsOpen}>\n <div className='relative' data-testid='spectral-multiselect-wrapper' onKeyDown={isOpen ? handleKeyDown : undefined} role='none'>\n <Popover.Trigger asChild>\n <button\n aria-activedescendant={isOpen && focusedOptionValue ? `${listboxId}-option-${focusedOptionValue}` : undefined}\n aria-controls={isOpen ? listboxId : undefined}\n aria-expanded={isOpen}\n aria-label={ariaLabel ?? label}\n className={cn(getTriggerClasses(isOpen, state, className), 'max-h-22 py-2 text-sm')}\n data-state={state}\n data-testid='spectral-multiselect-trigger'\n disabled={isDisabled}\n id={multiSelectId}\n name={name}\n ref={ref}\n role='combobox'\n style={getCSSCustomProperties() as CSSProperties}\n type='button'\n {...ariaProps}\n {...props}\n >\n <div className='min-w-0 flex-1 overflow-hidden' data-testid='spectral-multiselect-selected-items'>\n {renderSelectedItems()}\n </div>\n <div className='gap-2 ml-2 flex shrink-0 items-center'>\n <ChevronDownIcon className={cn('text-input-icon transition-transform duration-200', isOpen && 'rotate-180')} size={20} />\n </div>\n </button>\n </Popover.Trigger>\n {showClearAll && value.length > 0 && (\n <button\n aria-label='Clear all selections'\n className='right-10 text-input-icon hover:text-input-icon--hover rounded-sm absolute top-1/2 z-10 -translate-y-1/2 cursor-pointer focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50'\n data-testid='spectral-multiselect-clear-all-button'\n disabled={isDisabled}\n onClick={(e) => {\n e.stopPropagation()\n handleClearAll()\n document.getElementById(multiSelectId)?.focus()\n }}\n type='button'\n >\n <CloseIcon size={12} />\n </button>\n )}\n\n <Popover.Portal>\n <Popover.Content\n align='start'\n avoidCollisions\n className={getDropdownClasses()}\n collisionPadding={10}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-multiselect-dropdown'\n onOpenAutoFocus={(e) => {\n e.preventDefault()\n if (showSearch) {\n searchInputRef.current?.focus()\n }\n }}\n side='bottom'\n sideOffset={4}\n ref={setDropdownElement}\n style={{\n width: resolvedDropdownWidth,\n ...(dropdownWidth === 'trigger' ? {} : dropdownOverflowStyle),\n ...dropdownShiftStyle,\n }}\n >\n <div className='p-1'>\n {showSearch && (\n <div className='mb-2 relative'>\n <SearchIcon className={cn(ICON_SIZE, 'left-3 text-input-icon absolute top-1/2 -translate-y-1/2')} />\n <input\n aria-label='Search options'\n className='pl-9 pr-3 py-2 text-sm rounded-md focus-visible:ring-black w-full border border-input-border bg-input-bg focus-visible:border-input-border--focus focus-visible:ring-1 focus-visible:outline-none'\n data-testid='spectral-multiselect-search-input'\n onChange={handleSearchChange}\n placeholder={searchPlaceholder}\n ref={searchInputRef}\n type='text'\n value={searchValue}\n />\n </div>\n )}\n\n <div className='max-h-64 overflow-y-auto' id={listboxId} role='listbox' aria-multiselectable='true'>\n {isLoading ? (\n <LoadingState className='text-sm' message={loadingMessage} data-testid='spectral-multiselect-loading' />\n ) : filteredOptions.length === 0 ? (\n <EmptyState className='text-sm' data-testid='spectral-multiselect-empty-message' message={emptyMessage} />\n ) : (\n <>\n {showSelectAll && (\n <div className='mb-1'>\n <button\n className={cn(\n 'my-0.5 first:mt-0 last:mb-0 gap-3 rounded-sm px-3 py-2 text-sm font-medium text-input-text-secondary flex w-full items-center hover:bg-input-bg--hover focus-visible:bg-input-bg--hover focus-visible:outline-none',\n isSelectAllFocused && 'bg-input-bg--hover',\n )}\n data-testid='spectral-multiselect-select-all-button'\n onClick={handleSelectAll}\n type='button'\n >\n {isAllSelected ? clearAllLabel : selectAllLabel}\n </button>\n <div className='mx-3 my-1 h-px bg-input-border' />\n </div>\n )}\n\n {groupedOptions.ungrouped.length > 0 && <div className='mb-1'>{groupedOptions.ungrouped.map((option, index) => renderOption(option, index))}</div>}\n\n {Object.entries(groupedOptions.groups).map(([groupName, groupOptions]) => (\n <div key={groupName} className='mb-1' data-testid='spectral-multiselect-group'>\n {(groupedOptions.ungrouped.length > 0 || Object.keys(groupedOptions.groups).indexOf(groupName) > 0) && <div className='mx-3 my-1 h-px bg-input-border' />}\n <div data-testid='spectral-multiselect-group-name' className='px-3 py-1 text-xs font-semibold text-input-text-secondary tracking-wide uppercase'>\n {groupName}\n </div>\n {groupOptions.map((option, _index) => renderOption(option, filteredOptions.indexOf(option)))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n </Popover.Content>\n </Popover.Portal>\n </div>\n </Popover.Root>\n\n <ErrorMessage dataTestId='spectral-multiselect-error-message' id={errorMessageId} message={state === 'error' ? errorMessage : null} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace && state === 'error'} />\n <WarningMessage dataTestId='spectral-multiselect-warning-message' id={warningMessageId} message={state === 'warning' ? warningMessage : null} messageReserveLines={messageReserveLines} messageReserveSpace={messageReserveSpace && state === 'warning'} />\n </div>\n )\n}\nMultiSelectBase.displayName = 'MultiSelectBase'\n"],"mappings":"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"}
1
+ {"version":3,"file":"MultiSelectBase.js","names":[],"sources":["../../src/components/MultiSelect/MultiSelectBase.tsx"],"sourcesContent":["import { CheckmarkIcon, ChevronDownIcon, CloseIcon, SearchIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport * as Popover from '@radix-ui/react-popover'\nimport { useAutoDropdownHorizontalShift } from '@utils/dropdownPositioning'\nimport { EmptyState, ErrorMessage, getAriaProps, getDropdownSurfaceClasses, getDropdownWidthStyles, getErrorMessageId, getTriggerClasses, LoadingState, WarningMessage, useFormFieldId, type BaseFormFieldProps, type DropdownWidth, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState, type ButtonHTMLAttributes, type ChangeEvent, type CSSProperties, type KeyboardEvent, type Ref } from 'react'\n\nexport type MultiSelectState = Exclude<FormFieldState, 'disabled'>\n\nexport interface MultiSelectOption {\n disabled?: boolean\n group?: string\n label: string\n value: string\n}\n\nexport interface MultiSelectBaseProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value' | 'onChange'> {\n clearAllLabel?: string\n closeOnSelect?: boolean\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n errorMessage?: BaseFormFieldProps['errorMessage']\n id?: string\n label?: string\n loadingMessage?: string\n maxCount?: number\n messageReserveLines?: number\n messageReserveSpace?: boolean\n name?: string\n defaultValue?: string[]\n onChange?: (value: string[]) => void\n options: MultiSelectOption[]\n placeholder?: string\n required?: boolean\n searchPlaceholder?: string\n showClearAll?: boolean\n showSearch?: boolean\n showSelectAll?: boolean\n selectAllLabel?: string\n sortAlphabetically?: boolean\n state?: MultiSelectState\n value?: string[]\n warningMessage?: BaseFormFieldProps['errorMessage']\n 'aria-label'?: string\n 'aria-describedby'?: string\n}\n\nconst ICON_SIZE = 'h-4 w-4'\n\nconst getDropdownClasses = (): string => {\n return cn(\n 'max-h-80 z-50 overflow-hidden',\n getDropdownSurfaceClasses(),\n 'motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:animate-in',\n 'motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:fade-in-0',\n 'motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:zoom-in-95',\n 'motion-safe:data-[side=bottom]:slide-in-from-top-2',\n 'motion-safe:data-[side=top]:slide-in-from-bottom-2',\n 'origin-(--radix-popover-content-transform-origin)',\n )\n}\n\ntype FocusableItem = { type: 'search' } | { type: 'select-all' } | { type: 'option'; index: number; value: string } | { type: 'clear-all' }\n\nconst useKeyboardNavigation = (\n options: MultiSelectOption[],\n onClearAll: () => void,\n onClose: () => void,\n onSelect: (value: string) => void,\n onSelectAll: () => void,\n searchInputRef: React.RefObject<HTMLInputElement | null>,\n showSearch: boolean,\n showSelectAll: boolean,\n showClearAll: boolean,\n) => {\n const [focusedIndex, setFocusedIndex] = useState(-1)\n\n // Build a flat list of all focusable items\n const focusableItems = useMemo((): FocusableItem[] => {\n const items: FocusableItem[] = []\n\n if (showSearch) {\n items.push({ type: 'search' })\n }\n\n if (showSelectAll) {\n items.push({ type: 'select-all' })\n }\n\n options.forEach((option, index) => {\n if (!option.disabled) {\n items.push({ type: 'option', index, value: option.value })\n }\n })\n\n if (showClearAll) {\n items.push({ type: 'clear-all' })\n }\n\n return items\n }, [options, showSearch, showSelectAll, showClearAll])\n\n // Focus the appropriate element when focusedIndex changes\n const focusCurrentItem = useCallback(\n (index: number) => {\n if (index < 0 || index >= focusableItems.length) return\n const item = focusableItems[index]\n if (item.type === 'search') {\n searchInputRef.current?.focus()\n }\n },\n [focusableItems, searchInputRef],\n )\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n const currentItem = focusedIndex >= 0 && focusedIndex < focusableItems.length ? focusableItems[focusedIndex] : null\n\n // Don't prevent default for space in search input (allow typing spaces)\n if (event.key === ' ' && currentItem?.type === 'search') {\n return\n }\n\n // Don't prevent default for Enter in search input (allow form submission behavior)\n if (event.key === 'Enter' && currentItem?.type === 'search') {\n return\n }\n\n const keyHandlers: Record<string, () => void> = {\n ArrowDown: () => {\n event.preventDefault()\n const newIndex = Math.min(focusedIndex + 1, focusableItems.length - 1)\n setFocusedIndex(newIndex)\n focusCurrentItem(newIndex)\n },\n ArrowUp: () => {\n event.preventDefault()\n const newIndex = Math.max(focusedIndex - 1, 0)\n setFocusedIndex(newIndex)\n focusCurrentItem(newIndex)\n },\n Tab: () => {\n // Allow Tab to cycle through focusable items\n if (event.shiftKey) {\n if (focusedIndex <= 0) {\n // At start, close dropdown and return to trigger\n onClose()\n } else {\n event.preventDefault()\n const newIndex = focusedIndex - 1\n setFocusedIndex(newIndex)\n focusCurrentItem(newIndex)\n }\n } else {\n if (focusedIndex >= focusableItems.length - 1) {\n // At end, close dropdown and move to next element\n onClose()\n } else {\n event.preventDefault()\n const newIndex = focusedIndex + 1\n setFocusedIndex(newIndex)\n focusCurrentItem(newIndex)\n }\n }\n },\n Enter: () => {\n event.preventDefault()\n if (focusedIndex >= 0 && focusedIndex < focusableItems.length) {\n const item = focusableItems[focusedIndex]\n if (item.type === 'select-all') {\n onSelectAll()\n } else if (item.type === 'clear-all') {\n onClearAll()\n } else if (item.type === 'option') {\n onSelect(item.value)\n }\n }\n },\n ' ': () => {\n event.preventDefault()\n if (focusedIndex >= 0 && focusedIndex < focusableItems.length) {\n const item = focusableItems[focusedIndex]\n if (item.type === 'select-all') {\n onSelectAll()\n } else if (item.type === 'clear-all') {\n onClearAll()\n } else if (item.type === 'option') {\n onSelect(item.value)\n }\n }\n },\n Escape: () => {\n event.preventDefault()\n onClose()\n },\n }\n\n const handler = keyHandlers[event.key]\n if (handler) {\n handler()\n }\n },\n [focusableItems, focusedIndex, onSelect, onSelectAll, onClearAll, onClose, focusCurrentItem],\n )\n\n // Get the option index for visual focus styling (accounting for select-all offset)\n const getOptionFocusIndex = useCallback(\n (optionIndex: number): boolean => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return false\n const item = focusableItems[focusedIndex]\n return item.type === 'option' && item.index === optionIndex\n },\n [focusedIndex, focusableItems],\n )\n\n const isSearchFocused = useMemo(() => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return false\n return focusableItems[focusedIndex].type === 'search'\n }, [focusedIndex, focusableItems])\n\n const isSelectAllFocused = useMemo(() => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return false\n return focusableItems[focusedIndex].type === 'select-all'\n }, [focusedIndex, focusableItems])\n\n const isClearAllFocused = useMemo(() => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return false\n return focusableItems[focusedIndex].type === 'clear-all'\n }, [focusedIndex, focusableItems])\n\n const focusedOptionValue = useMemo(() => {\n if (focusedIndex < 0 || focusedIndex >= focusableItems.length) return null\n const item = focusableItems[focusedIndex]\n return item.type === 'option' ? item.value : null\n }, [focusedIndex, focusableItems])\n\n return {\n focusedIndex,\n setFocusedIndex,\n handleKeyDown,\n getOptionFocusIndex,\n isSearchFocused,\n isSelectAllFocused,\n isClearAllFocused,\n focusedOptionValue,\n }\n}\n\nexport const MultiSelectBase = ({\n className,\n clearAllLabel = 'Clear all',\n closeOnSelect = false,\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found',\n errorMessage,\n defaultValue = [],\n disabled,\n id,\n label,\n loadingMessage = 'Loading options…',\n messageReserveLines = 1,\n messageReserveSpace = false,\n maxCount = 3,\n name,\n onChange,\n options = [],\n placeholder = 'Select options',\n ref,\n searchPlaceholder = 'Search options…',\n selectAllLabel = 'Select all',\n showClearAll = true,\n showSearch = true,\n showSelectAll = true,\n sortAlphabetically = false,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n ...props\n}: MultiSelectBaseProps & {\n ref?: Ref<HTMLButtonElement>\n}) => {\n const generatedId = useId()\n const fallbackName = name ?? `multiselect-${generatedId}`\n const multiSelectId = useFormFieldId(id, fallbackName)\n const listboxId = `${multiSelectId}-listbox`\n const errorMessageId = getErrorMessageId(multiSelectId)\n const warningMessageId = `${multiSelectId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n\n const [isOpen, setIsOpen] = useState(false)\n const { dropdownShiftStyle, setDropdownElement } = useAutoDropdownHorizontalShift(isOpen)\n const [searchValue, setSearchValue] = useState('')\n const [value, setValue] = useUncontrolledState<string[]>({\n value: valueProp,\n defaultValue,\n onChange,\n })\n\n const searchInputRef = useRef<HTMLInputElement>(null)\n\n const isDisabled = Boolean(disabled)\n const isLoading = state === 'loading'\n const ariaProps = getAriaProps(state, ariaDescribedBy, props.required, messageId)\n const { dropdownOverflowStyle, dropdownWidthMode, resolvedDropdownWidth } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: 'var(--radix-popover-trigger-width)',\n })\n\n const filteredOptions = useMemo(() => {\n let filtered = options.filter((option) => option.label.toLowerCase().includes(searchValue.toLowerCase()))\n\n if (sortAlphabetically) {\n filtered = [...filtered].sort((a, b) => a.label.localeCompare(b.label))\n }\n\n return filtered\n }, [options, searchValue, sortAlphabetically])\n\n const groupedOptions = useMemo(() => {\n const groups: Record<string, MultiSelectOption[]> = {}\n const ungrouped: MultiSelectOption[] = []\n\n filteredOptions.forEach((option) => {\n if (option.group) {\n if (!groups[option.group]) {\n groups[option.group] = []\n }\n groups[option.group].push(option)\n } else {\n ungrouped.push(option)\n }\n })\n\n return { groups, ungrouped, hasGroups: Object.keys(groups).length > 0 }\n }, [filteredOptions])\n\n const toggleOption = useCallback(\n (optionValue: string) => {\n const option = options.find((o) => o.value === optionValue)\n if (option?.disabled) return\n\n const newValue = value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue]\n\n setValue(newValue)\n\n if (closeOnSelect) {\n setIsOpen(false)\n }\n },\n [closeOnSelect, options, setValue, value],\n )\n\n const handleSelectAll = useCallback(() => {\n const allValues = options.filter((o) => !o.disabled).map((o) => o.value)\n const isAllSelected = allValues.every((v) => value.includes(v))\n\n if (isAllSelected) {\n setValue([])\n } else {\n setValue(allValues)\n }\n }, [options, setValue, value])\n\n const handleClearAll = useCallback(() => {\n setValue([])\n }, [setValue])\n\n // Check if all non-disabled options are selected\n const allSelectableValues = useMemo(() => options.filter((o) => !o.disabled).map((o) => o.value), [options])\n const isAllSelected = allSelectableValues.length > 0 && allSelectableValues.every((v) => value.includes(v))\n\n const { focusedOptionValue, getOptionFocusIndex, handleKeyDown, isSelectAllFocused, setFocusedIndex } = useKeyboardNavigation(\n filteredOptions,\n handleClearAll,\n () => setIsOpen(false),\n toggleOption,\n handleSelectAll,\n searchInputRef,\n showSearch,\n showSelectAll,\n false, // No separate clear-all button in dropdown\n )\n\n // Set initial focus index when dropdown opens/closes\n useEffect(() => {\n if (isOpen) {\n setFocusedIndex(0)\n } else {\n setFocusedIndex(-1)\n }\n }, [isOpen, setFocusedIndex])\n\n const handleSearchChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value)\n }, [])\n\n const renderSelectedItems = () => {\n if (value.length === 0) {\n return <span className='min-h-8 flex items-center text-input-text-placeholder'>{placeholder}</span>\n }\n\n const displayedValues = value.slice(0, maxCount)\n const remainingCount = value.length - maxCount\n\n return (\n <div className='gap-1 flex flex-wrap items-center overflow-hidden'>\n {displayedValues.map((val) => {\n const option = options.find((o) => o.value === val)\n if (!option) return null\n\n return (\n <span\n className='gap-1 px-2 py-1 rounded-md text-xs max-w-48 inline-flex items-center bg-input-bg--selected text-input-text'\n key={val}\n >\n <span className='truncate'>{option.label}</span>\n <span\n aria-hidden='true'\n className='hover:text-danger rounded-sm cursor-pointer'\n data-testid='spectral-multiselect-remove-item-button'\n onClick={(e) => {\n e.preventDefault()\n e.stopPropagation()\n toggleOption(val)\n }}\n onPointerDown={(e) => {\n e.stopPropagation()\n }}\n >\n <CloseIcon size={12} />\n </span>\n </span>\n )\n })}\n {remainingCount > 0 && <span className='text-input-text-secondary text-xs py-1 flex items-center tabular-nums'>+{remainingCount} more</span>}\n </div>\n )\n }\n\n const renderOption = (option: MultiSelectOption, index: number) => {\n const isSelected = value.includes(option.value)\n const isFocused = getOptionFocusIndex(index)\n const optionId = `${listboxId}-option-${option.value}`\n\n return (\n <button\n aria-selected={isSelected}\n className={cn(\n 'my-0.5 first:mt-0 last:mb-0 gap-3 rounded-sm px-3 py-2 text-sm flex w-full items-center text-left hover:bg-input-bg--hover focus-visible:bg-input-bg--hover focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',\n isFocused && 'bg-input-bg--hover',\n isSelected && 'font-medium text-input-text',\n )}\n disabled={option.disabled}\n id={optionId}\n key={option.value}\n onClick={() => toggleOption(option.value)}\n role='option'\n type='button'\n >\n <div\n data-testid='spectral-multiselect-selected-indicator'\n className={cn('w-4 h-4 rounded flex items-center justify-center border border-input-border', isSelected && 'bg-primary border-primary')}\n >\n {isSelected && <CheckmarkIcon size={12} />}\n </div>\n <span>{option.label}</span>\n </button>\n )\n }\n\n const getCSSCustomProperties = () => ({\n '--multiselect-border-radius': '0.5rem',\n '--multiselect-trigger-height': '3rem',\n '--multiselect-dropdown-max-height': '20rem',\n })\n\n return (\n <div\n className='w-full'\n data-testid='spectral-multiselect-root'\n >\n {label && (\n <Label\n className={cn('mb-2 block text-text-primary', isDisabled && 'text-text-secondary')}\n data-testid='spectral-multiselect-label'\n htmlFor={multiSelectId}\n >\n {label}\n </Label>\n )}\n <Popover.Root\n open={isOpen}\n onOpenChange={setIsOpen}\n >\n <div\n className='relative'\n data-testid='spectral-multiselect-wrapper'\n onKeyDown={isOpen ? handleKeyDown : undefined}\n role='none'\n >\n <Popover.Trigger asChild>\n <button\n aria-activedescendant={isOpen && focusedOptionValue ? `${listboxId}-option-${focusedOptionValue}` : undefined}\n aria-controls={isOpen ? listboxId : undefined}\n aria-expanded={isOpen}\n aria-label={ariaLabel ?? label}\n className={cn(getTriggerClasses(isOpen, state, className), 'max-h-22 py-2 text-sm')}\n data-state={state}\n data-testid='spectral-multiselect-trigger'\n disabled={isDisabled}\n id={multiSelectId}\n name={name}\n ref={ref}\n role='combobox' // oxlint-disable-line jsx-a11y/prefer-tag-over-role -- trigger uses button-based combobox semantics for listbox popup\n style={getCSSCustomProperties() as CSSProperties}\n type='button'\n {...ariaProps}\n {...props}\n >\n <div\n className='min-w-0 flex-1 overflow-hidden'\n data-testid='spectral-multiselect-selected-items'\n >\n {renderSelectedItems()}\n </div>\n <div className='gap-2 ml-2 flex shrink-0 items-center'>\n <ChevronDownIcon\n className={cn('text-input-icon transition-transform duration-200', isOpen && 'rotate-180')}\n size={20}\n />\n </div>\n </button>\n </Popover.Trigger>\n {showClearAll && value.length > 0 && (\n <button\n aria-label='Clear all selections'\n className='right-10 text-input-icon hover:text-input-icon--hover rounded-sm absolute top-1/2 z-10 -translate-y-1/2 cursor-pointer focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50'\n data-testid='spectral-multiselect-clear-all-button'\n disabled={isDisabled}\n onClick={(e) => {\n e.stopPropagation()\n handleClearAll()\n document.getElementById(multiSelectId)?.focus()\n }}\n type='button'\n >\n <CloseIcon size={12} />\n </button>\n )}\n\n <Popover.Portal>\n <Popover.Content\n align='start'\n avoidCollisions\n className={getDropdownClasses()}\n collisionPadding={10}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-multiselect-dropdown'\n onOpenAutoFocus={(e) => {\n e.preventDefault()\n if (showSearch) {\n searchInputRef.current?.focus()\n }\n }}\n side='bottom'\n sideOffset={4}\n ref={setDropdownElement}\n style={{\n width: resolvedDropdownWidth,\n ...(dropdownWidth === 'trigger' ? {} : dropdownOverflowStyle),\n ...dropdownShiftStyle,\n }}\n >\n <div className='p-1'>\n {showSearch && (\n <div className='mb-2 relative'>\n <SearchIcon className={cn(ICON_SIZE, 'left-3 text-input-icon absolute top-1/2 -translate-y-1/2')} />\n <input\n aria-label='Search options'\n className='pl-9 pr-3 py-2 text-sm rounded-md focus-visible:ring-black w-full border border-input-border bg-input-bg focus-visible:border-input-border--focus focus-visible:ring-1 focus-visible:outline-none'\n data-testid='spectral-multiselect-search-input'\n onChange={handleSearchChange}\n placeholder={searchPlaceholder}\n ref={searchInputRef}\n type='text'\n value={searchValue}\n />\n </div>\n )}\n <div\n aria-multiselectable='true'\n className='max-h-64 overflow-y-auto'\n id={listboxId}\n role='listbox' // oxlint-disable-line jsx-a11y/prefer-tag-over-role -- custom multi-select uses ARIA listbox semantics with option children\n >\n {isLoading ? (\n <LoadingState\n className='text-sm'\n message={loadingMessage}\n data-testid='spectral-multiselect-loading'\n />\n ) : filteredOptions.length === 0 ? (\n <EmptyState\n className='text-sm'\n data-testid='spectral-multiselect-empty-message'\n message={emptyMessage}\n />\n ) : (\n <>\n {showSelectAll && (\n <div className='mb-1'>\n <button\n className={cn(\n 'my-0.5 first:mt-0 last:mb-0 gap-3 rounded-sm px-3 py-2 text-sm font-medium text-input-text-secondary flex w-full items-center hover:bg-input-bg--hover focus-visible:bg-input-bg--hover focus-visible:outline-none',\n isSelectAllFocused && 'bg-input-bg--hover',\n )}\n data-testid='spectral-multiselect-select-all-button'\n onClick={handleSelectAll}\n type='button'\n >\n {isAllSelected ? clearAllLabel : selectAllLabel}\n </button>\n <div className='mx-3 my-1 h-px bg-input-border' />\n </div>\n )}\n\n {groupedOptions.ungrouped.length > 0 && <div className='mb-1'>{groupedOptions.ungrouped.map((option, index) => renderOption(option, index))}</div>}\n\n {Object.entries(groupedOptions.groups).map(([groupName, groupOptions]) => (\n <div\n key={groupName}\n className='mb-1'\n data-testid='spectral-multiselect-group'\n >\n {(groupedOptions.ungrouped.length > 0 || Object.keys(groupedOptions.groups).indexOf(groupName) > 0) && <div className='mx-3 my-1 h-px bg-input-border' />}\n <div\n data-testid='spectral-multiselect-group-name'\n className='px-3 py-1 text-xs font-semibold text-input-text-secondary tracking-wide uppercase'\n >\n {groupName}\n </div>\n {groupOptions.map((option, _index) => renderOption(option, filteredOptions.indexOf(option)))}\n </div>\n ))}\n </>\n )}\n </div>\n </div>\n </Popover.Content>\n </Popover.Portal>\n </div>\n </Popover.Root>\n\n <ErrorMessage\n dataTestId='spectral-multiselect-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-multiselect-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? warningMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nMultiSelectBase.displayName = 'MultiSelectBase'\n"],"mappings":";;;;;;;;;;;;;;;;AAiDA,MAAM,YAAY;AAElB,MAAM,2BAAmC;AACvC,QAAO,GACL,iCACA,2BAA2B,EAC3B,wFACA,sFACA,wFACA,sDACA,sDACA,oDACD;;AAKH,MAAM,yBACJ,SACA,YACA,SACA,UACA,aACA,gBACA,YACA,eACA,iBACG;CACH,MAAM,CAAC,cAAc,mBAAmB,SAAS,GAAG;CAGpD,MAAM,iBAAiB,cAA+B;EACpD,MAAM,QAAyB,EAAE;AAEjC,MAAI,WACF,OAAM,KAAK,EAAE,MAAM,UAAU,CAAC;AAGhC,MAAI,cACF,OAAM,KAAK,EAAE,MAAM,cAAc,CAAC;AAGpC,UAAQ,SAAS,QAAQ,UAAU;AACjC,OAAI,CAAC,OAAO,SACV,OAAM,KAAK;IAAE,MAAM;IAAU;IAAO,OAAO,OAAO;IAAO,CAAC;IAE5D;AAEF,MAAI,aACF,OAAM,KAAK,EAAE,MAAM,aAAa,CAAC;AAGnC,SAAO;IACN;EAAC;EAAS;EAAY;EAAe;EAAa,CAAC;CAGtD,MAAM,mBAAmB,aACtB,UAAkB;AACjB,MAAI,QAAQ,KAAK,SAAS,eAAe,OAAQ;AAEjD,MADa,eAAe,OACnB,SAAS,SAChB,gBAAe,SAAS,OAAO;IAGnC,CAAC,gBAAgB,eAAe,CACjC;AA4HD,QAAO;EACL;EACA;EACA,eA7HoB,aACnB,UAAyC;GACxC,MAAM,cAAc,gBAAgB,KAAK,eAAe,eAAe,SAAS,eAAe,gBAAgB;AAG/G,OAAI,MAAM,QAAQ,OAAO,aAAa,SAAS,SAC7C;AAIF,OAAI,MAAM,QAAQ,WAAW,aAAa,SAAS,SACjD;GAwEF,MAAM,UAAU;IApEd,iBAAiB;AACf,WAAM,gBAAgB;KACtB,MAAM,WAAW,KAAK,IAAI,eAAe,GAAG,eAAe,SAAS,EAAE;AACtE,qBAAgB,SAAS;AACzB,sBAAiB,SAAS;;IAE5B,eAAe;AACb,WAAM,gBAAgB;KACtB,MAAM,WAAW,KAAK,IAAI,eAAe,GAAG,EAAE;AAC9C,qBAAgB,SAAS;AACzB,sBAAiB,SAAS;;IAE5B,WAAW;AAET,SAAI,MAAM,SACR,KAAI,gBAAgB,EAElB,UAAS;UACJ;AACL,YAAM,gBAAgB;MACtB,MAAM,WAAW,eAAe;AAChC,sBAAgB,SAAS;AACzB,uBAAiB,SAAS;;cAGxB,gBAAgB,eAAe,SAAS,EAE1C,UAAS;UACJ;AACL,YAAM,gBAAgB;MACtB,MAAM,WAAW,eAAe;AAChC,sBAAgB,SAAS;AACzB,uBAAiB,SAAS;;;IAIhC,aAAa;AACX,WAAM,gBAAgB;AACtB,SAAI,gBAAgB,KAAK,eAAe,eAAe,QAAQ;MAC7D,MAAM,OAAO,eAAe;AAC5B,UAAI,KAAK,SAAS,aAChB,cAAa;eACJ,KAAK,SAAS,YACvB,aAAY;eACH,KAAK,SAAS,SACvB,UAAS,KAAK,MAAM;;;IAI1B,WAAW;AACT,WAAM,gBAAgB;AACtB,SAAI,gBAAgB,KAAK,eAAe,eAAe,QAAQ;MAC7D,MAAM,OAAO,eAAe;AAC5B,UAAI,KAAK,SAAS,aAChB,cAAa;eACJ,KAAK,SAAS,YACvB,aAAY;eACH,KAAK,SAAS,SACvB,UAAS,KAAK,MAAM;;;IAI1B,cAAc;AACZ,WAAM,gBAAgB;AACtB,cAAS;;IAIc,CAAC,MAAM;AAClC,OAAI,QACF,UAAS;KAGb;GAAC;GAAgB;GAAc;GAAU;GAAa;GAAY;GAAS;GAAiB,CAqC/E;EACb,qBAlC0B,aACzB,gBAAiC;AAChC,OAAI,eAAe,KAAK,gBAAgB,eAAe,OAAQ,QAAO;GACtE,MAAM,OAAO,eAAe;AAC5B,UAAO,KAAK,SAAS,YAAY,KAAK,UAAU;KAElD,CAAC,cAAc,eAAe,CA4BX;EACnB,iBA1BsB,cAAc;AACpC,OAAI,eAAe,KAAK,gBAAgB,eAAe,OAAQ,QAAO;AACtE,UAAO,eAAe,cAAc,SAAS;KAC5C,CAAC,cAAc,eAAe,CAuBhB;EACf,oBAtByB,cAAc;AACvC,OAAI,eAAe,KAAK,gBAAgB,eAAe,OAAQ,QAAO;AACtE,UAAO,eAAe,cAAc,SAAS;KAC5C,CAAC,cAAc,eAAe,CAmBb;EAClB,mBAlBwB,cAAc;AACtC,OAAI,eAAe,KAAK,gBAAgB,eAAe,OAAQ,QAAO;AACtE,UAAO,eAAe,cAAc,SAAS;KAC5C,CAAC,cAAc,eAAe,CAed;EACjB,oBAdyB,cAAc;AACvC,OAAI,eAAe,KAAK,gBAAgB,eAAe,OAAQ,QAAO;GACtE,MAAM,OAAO,eAAe;AAC5B,UAAO,KAAK,SAAS,WAAW,KAAK,QAAQ;KAC5C,CAAC,cAAc,eAAe,CAUb;EACnB;;AAGH,MAAa,mBAAmB,EAC9B,WACA,gBAAgB,aAChB,gBAAgB,OAChB,gBAAgB,WAChB,eAAe,oBACf,cACA,eAAe,EAAE,EACjB,UACA,IACA,OACA,iBAAiB,oBACjB,sBAAsB,GACtB,sBAAsB,OACtB,WAAW,GACX,MACA,UACA,UAAU,EAAE,EACZ,cAAc,kBACd,KACA,oBAAoB,mBACpB,iBAAiB,cACjB,eAAe,MACf,aAAa,MACb,gBAAgB,MAChB,qBAAqB,OACrB,QAAQ,WACR,OAAO,WACP,gBACA,cAAc,WACd,oBAAoB,iBACpB,GAAG,YAGC;CACJ,MAAM,cAAc,OAAO;CAE3B,MAAM,gBAAgB,eAAe,IADhB,QAAQ,eAAe,cACU;CACtD,MAAM,YAAY,GAAG,cAAc;CACnC,MAAM,iBAAiB,kBAAkB,cAAc;CACvD,MAAM,mBAAmB,GAAG,cAAc;CAC1C,MAAM,YAAY,UAAU,UAAU,iBAAiB,UAAU,aAAa,iBAAiB,mBAAmB;CAElH,MAAM,CAAC,QAAQ,aAAa,SAAS,MAAM;CAC3C,MAAM,EAAE,oBAAoB,uBAAuB,+BAA+B,OAAO;CACzF,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,MAAM,CAAC,OAAO,YAAY,qBAA+B;EACvD,OAAO;EACP;EACA;EACD,CAAC;CAEF,MAAM,iBAAiB,OAAyB,KAAK;CAErD,MAAM,aAAa,QAAQ,SAAS;CACpC,MAAM,YAAY,UAAU;CAC5B,MAAM,YAAY,aAAa,OAAO,iBAAiB,MAAM,UAAU,UAAU;CACjF,MAAM,EAAE,uBAAuB,mBAAmB,0BAA0B,uBAAuB;EACjG;EACA,cAAc;EACf,CAAC;CAEF,MAAM,kBAAkB,cAAc;EACpC,IAAI,WAAW,QAAQ,QAAQ,WAAW,OAAO,MAAM,aAAa,CAAC,SAAS,YAAY,aAAa,CAAC,CAAC;AAEzG,MAAI,mBACF,YAAW,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,MAAM,CAAC;AAGzE,SAAO;IACN;EAAC;EAAS;EAAa;EAAmB,CAAC;CAE9C,MAAM,iBAAiB,cAAc;EACnC,MAAM,SAA8C,EAAE;EACtD,MAAM,YAAiC,EAAE;AAEzC,kBAAgB,SAAS,WAAW;AAClC,OAAI,OAAO,OAAO;AAChB,QAAI,CAAC,OAAO,OAAO,OACjB,QAAO,OAAO,SAAS,EAAE;AAE3B,WAAO,OAAO,OAAO,KAAK,OAAO;SAEjC,WAAU,KAAK,OAAO;IAExB;AAEF,SAAO;GAAE;GAAQ;GAAW,WAAW,OAAO,KAAK,OAAO,CAAC,SAAS;GAAG;IACtE,CAAC,gBAAgB,CAAC;CAErB,MAAM,eAAe,aAClB,gBAAwB;AAEvB,MADe,QAAQ,MAAM,MAAM,EAAE,UAAU,YACrC,EAAE,SAAU;AAItB,WAFiB,MAAM,SAAS,YAAY,GAAG,MAAM,QAAQ,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,OAAO,YAAY,CAE7F;AAElB,MAAI,cACF,WAAU,MAAM;IAGpB;EAAC;EAAe;EAAS;EAAU;EAAM,CAC1C;CAED,MAAM,kBAAkB,kBAAkB;EACxC,MAAM,YAAY,QAAQ,QAAQ,MAAM,CAAC,EAAE,SAAS,CAAC,KAAK,MAAM,EAAE,MAAM;AAGxE,MAFsB,UAAU,OAAO,MAAM,MAAM,SAAS,EAAE,CAE7C,CACf,UAAS,EAAE,CAAC;MAEZ,UAAS,UAAU;IAEpB;EAAC;EAAS;EAAU;EAAM,CAAC;CAE9B,MAAM,iBAAiB,kBAAkB;AACvC,WAAS,EAAE,CAAC;IACX,CAAC,SAAS,CAAC;CAGd,MAAM,sBAAsB,cAAc,QAAQ,QAAQ,MAAM,CAAC,EAAE,SAAS,CAAC,KAAK,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC;CAC5G,MAAM,gBAAgB,oBAAoB,SAAS,KAAK,oBAAoB,OAAO,MAAM,MAAM,SAAS,EAAE,CAAC;CAE3G,MAAM,EAAE,oBAAoB,qBAAqB,eAAe,oBAAoB,oBAAoB,sBACtG,iBACA,sBACM,UAAU,MAAM,EACtB,cACA,iBACA,gBACA,YACA,eACA,MACD;AAGD,iBAAgB;AACd,MAAI,OACF,iBAAgB,EAAE;MAElB,iBAAgB,GAAG;IAEpB,CAAC,QAAQ,gBAAgB,CAAC;CAE7B,MAAM,qBAAqB,aAAa,MAAqC;AAC3E,iBAAe,EAAE,OAAO,MAAM;IAC7B,EAAE,CAAC;CAEN,MAAM,4BAA4B;AAChC,MAAI,MAAM,WAAW,EACnB,QAAO,oBAAC,QAAD;GAAM,WAAU;aAAyD;GAAmB;EAGrG,MAAM,kBAAkB,MAAM,MAAM,GAAG,SAAS;EAChD,MAAM,iBAAiB,MAAM,SAAS;AAEtC,SACE,qBAAC,OAAD;GAAK,WAAU;aAAf,CACG,gBAAgB,KAAK,QAAQ;IAC5B,MAAM,SAAS,QAAQ,MAAM,MAAM,EAAE,UAAU,IAAI;AACnD,QAAI,CAAC,OAAQ,QAAO;AAEpB,WACE,qBAAC,QAAD;KACE,WAAU;eADZ,CAIE,oBAAC,QAAD;MAAM,WAAU;gBAAY,OAAO;MAAa,GAChD,oBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MACV,eAAY;MACZ,UAAU,MAAM;AACd,SAAE,gBAAgB;AAClB,SAAE,iBAAiB;AACnB,oBAAa,IAAI;;MAEnB,gBAAgB,MAAM;AACpB,SAAE,iBAAiB;;gBAGrB,oBAAC,WAAD,EAAW,MAAM,IAAM;MAClB,EACF;OAlBA,IAkBA;KAET,EACD,iBAAiB,KAAK,qBAAC,QAAD;IAAM,WAAU;cAAhB;KAAwF;KAAE;KAAe;KAAY;MACxI;;;CAIV,MAAM,gBAAgB,QAA2B,UAAkB;EACjE,MAAM,aAAa,MAAM,SAAS,OAAO,MAAM;EAC/C,MAAM,YAAY,oBAAoB,MAAM;EAC5C,MAAM,WAAW,GAAG,UAAU,UAAU,OAAO;AAE/C,SACE,qBAAC,UAAD;GACE,iBAAe;GACf,WAAW,GACT,0OACA,aAAa,sBACb,cAAc,8BACf;GACD,UAAU,OAAO;GACjB,IAAI;GAEJ,eAAe,aAAa,OAAO,MAAM;GACzC,MAAK;GACL,MAAK;aAZP,CAcE,oBAAC,OAAD;IACE,eAAY;IACZ,WAAW,GAAG,+EAA+E,cAAc,4BAA4B;cAEtI,cAAc,oBAAC,eAAD,EAAe,MAAM,IAAM;IACtC,GACN,oBAAC,QAAD,YAAO,OAAO,OAAa,EACpB;KAZF,OAAO,MAYL;;CAIb,MAAM,gCAAgC;EACpC,+BAA+B;EAC/B,gCAAgC;EAChC,qCAAqC;EACtC;AAED,QACE,qBAAC,OAAD;EACE,WAAU;EACV,eAAY;YAFd;GAIG,SACC,oBAAC,OAAD;IACE,WAAW,GAAG,gCAAgC,cAAc,sBAAsB;IAClF,eAAY;IACZ,SAAS;cAER;IACK;GAEV,oBAAC,QAAQ,MAAT;IACE,MAAM;IACN,cAAc;cAEd,qBAAC,OAAD;KACE,WAAU;KACV,eAAY;KACZ,WAAW,SAAS,gBAAgB;KACpC,MAAK;eAJP;MAME,oBAAC,QAAQ,SAAT;OAAiB;iBACf,qBAAC,UAAD;QACE,yBAAuB,UAAU,qBAAqB,GAAG,UAAU,UAAU,uBAAuB;QACpG,iBAAe,SAAS,YAAY;QACpC,iBAAe;QACf,cAAY,aAAa;QACzB,WAAW,GAAG,kBAAkB,QAAQ,OAAO,UAAU,EAAE,wBAAwB;QACnF,cAAY;QACZ,eAAY;QACZ,UAAU;QACV,IAAI;QACE;QACD;QACL,MAAK;QACL,OAAO,wBAAwB;QAC/B,MAAK;QACL,GAAI;QACJ,GAAI;kBAhBN,CAkBE,oBAAC,OAAD;SACE,WAAU;SACV,eAAY;mBAEX,qBAAqB;SAClB,GACN,oBAAC,OAAD;SAAK,WAAU;mBACb,oBAAC,iBAAD;UACE,WAAW,GAAG,qDAAqD,UAAU,aAAa;UAC1F,MAAM;UACN;SACE,EACC;;OACO;MACjB,gBAAgB,MAAM,SAAS,KAC9B,oBAAC,UAAD;OACE,cAAW;OACX,WAAU;OACV,eAAY;OACZ,UAAU;OACV,UAAU,MAAM;AACd,UAAE,iBAAiB;AACnB,wBAAgB;AAChB,iBAAS,eAAe,cAAc,EAAE,OAAO;;OAEjD,MAAK;iBAEL,oBAAC,WAAD,EAAW,MAAM,IAAM;OAChB;MAGX,oBAAC,QAAQ,QAAT,YACE,oBAAC,QAAQ,SAAT;OACE,OAAM;OACN;OACA,WAAW,oBAAoB;OAC/B,kBAAkB;OAClB,4BAA0B;OAC1B,6BAA2B,sBAAsB,WAAW,gBAAgB;OAC5E,eAAY;OACZ,kBAAkB,MAAM;AACtB,UAAE,gBAAgB;AAClB,YAAI,WACF,gBAAe,SAAS,OAAO;;OAGnC,MAAK;OACL,YAAY;OACZ,KAAK;OACL,OAAO;QACL,OAAO;QACP,GAAI,kBAAkB,YAAY,EAAE,GAAG;QACvC,GAAG;QACJ;iBAED,qBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,cACC,qBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,oBAAC,YAAD,EAAY,WAAW,GAAG,WAAW,2DAA2D,EAAI,GACpG,oBAAC,SAAD;UACE,cAAW;UACX,WAAU;UACV,eAAY;UACZ,UAAU;UACV,aAAa;UACb,KAAK;UACL,MAAK;UACL,OAAO;UACP,EACE;YAER,oBAAC,OAAD;SACE,wBAAqB;SACrB,WAAU;SACV,IAAI;SACJ,MAAK;mBAEJ,YACC,oBAAC,cAAD;UACE,WAAU;UACV,SAAS;UACT,eAAY;UACZ,IACA,gBAAgB,WAAW,IAC7B,oBAAC,YAAD;UACE,WAAU;UACV,eAAY;UACZ,SAAS;UACT,IAEF;UACG,iBACC,qBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,oBAAC,UAAD;YACE,WAAW,GACT,sNACA,sBAAsB,qBACvB;YACD,eAAY;YACZ,SAAS;YACT,MAAK;sBAEJ,gBAAgB,gBAAgB;YAC1B,GACT,oBAAC,OAAD,EAAK,WAAU,kCAAmC,EAC9C;;UAGP,eAAe,UAAU,SAAS,KAAK,oBAAC,OAAD;WAAK,WAAU;qBAAQ,eAAe,UAAU,KAAK,QAAQ,UAAU,aAAa,QAAQ,MAAM,CAAC;WAAO;UAEjJ,OAAO,QAAQ,eAAe,OAAO,CAAC,KAAK,CAAC,WAAW,kBACtD,qBAAC,OAAD;WAEE,WAAU;WACV,eAAY;qBAHd;aAKI,eAAe,UAAU,SAAS,KAAK,OAAO,KAAK,eAAe,OAAO,CAAC,QAAQ,UAAU,GAAG,MAAM,oBAAC,OAAD,EAAK,WAAU,kCAAmC;YACzJ,oBAAC,OAAD;aACE,eAAY;aACZ,WAAU;uBAET;aACG;YACL,aAAa,KAAK,QAAQ,WAAW,aAAa,QAAQ,gBAAgB,QAAQ,OAAO,CAAC,CAAC;YACxF;aAZC,UAYD,CACN;UACD;SAED,EACF;;OACU,GACH;MACb;;IACO;GAEf,oBAAC,cAAD;IACE,YAAW;IACX,IAAI;IACJ,SAAS,UAAU,UAAU,eAAe;IACvB;IACrB,qBAAqB,uBAAuB,UAAU;IACtD;GACF,oBAAC,gBAAD;IACE,YAAW;IACX,IAAI;IACJ,SAAS,UAAU,YAAY,iBAAiB;IAC3B;IACrB,qBAAqB,uBAAuB,UAAU;IACtD;GACE;;;AAGV,gBAAgB,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelect.js","names":[],"sources":["../src/components/MultiSelect/MultiSelect.tsx"],"sourcesContent":["import { type Ref } from 'react'\nimport { MultiSelectBase, type MultiSelectBaseProps, type MultiSelectOption } from './MultiSelectBase'\n\nexport interface MultiSelectProps extends Omit<MultiSelectBaseProps, 'defaultValue' | 'onChange' | 'options' | 'value'> {\n defaultValue?: string[]\n emptyText?: string\n loading?: boolean\n onChange?: (value: string[]) => void\n onSearchChange?: (search: string) => void\n onValueChange?: (value: string[]) => void\n options: {\n disabled?: boolean\n group?: string\n label: string\n value: string\n }[]\n sortAlphabetically?: boolean\n value?: string[]\n}\n\nexport const MultiSelect = ({\n emptyText = 'No matches found.',\n loading = false,\n onChange,\n onValueChange,\n options,\n defaultValue,\n ref,\n state,\n value,\n ...props\n}: MultiSelectProps & {\n ref?: Ref<HTMLButtonElement>\n}) => {\n const baseOptions: MultiSelectOption[] = options.map((option) => ({\n disabled: option.disabled ?? false,\n group: option.group,\n label: option.label,\n value: option.value,\n }))\n\n const derivedState = loading ? 'loading' : state\n\n const handleChange = (nextValue: string[]) => {\n onValueChange?.(nextValue)\n onChange?.(nextValue)\n }\n\n return <MultiSelectBase data-disabled={props.disabled} defaultValue={defaultValue} disabled={props.disabled} emptyMessage={emptyText} onChange={handleChange} options={baseOptions} ref={ref} state={derivedState} value={value} {...props} />\n}\nMultiSelect.displayName = 'MultiSelect'\n"],"mappings":";;;;;;AAoBA,MAAa,eAAe,EAC1B,YAAY,qBACZ,UAAU,OACV,UACA,eACA,SACA,cACA,KACA,OACA,OACA,GAAG,YAGC;CACJ,MAAM,cAAmC,QAAQ,KAAK,YAAY;EAChE,UAAU,OAAO,YAAY;EAC7B,OAAO,OAAO;EACd,OAAO,OAAO;EACd,OAAO,OAAO;EACf,EAAE;CAEH,MAAM,eAAe,UAAU,YAAY;CAE3C,MAAM,gBAAgB,cAAwB;AAC5C,kBAAgB,UAAU;AAC1B,aAAW,UAAU;;AAGvB,QAAO,oBAAC,iBAAD;EAAiB,iBAAe,MAAM;EAAwB;EAAc,UAAU,MAAM;EAAU,cAAc;EAAW,UAAU;EAAc,SAAS;EAAkB;EAAK,OAAO;EAAqB;EAAO,GAAI;EAAS;;AAEhP,YAAY,cAAc"}
1
+ {"version":3,"file":"MultiSelect.js","names":[],"sources":["../src/components/MultiSelect/MultiSelect.tsx"],"sourcesContent":["import { type Ref } from 'react'\nimport { MultiSelectBase, type MultiSelectBaseProps, type MultiSelectOption } from './MultiSelectBase'\n\nexport interface MultiSelectProps extends Omit<MultiSelectBaseProps, 'defaultValue' | 'onChange' | 'options' | 'value'> {\n defaultValue?: string[]\n emptyText?: string\n loading?: boolean\n onChange?: (value: string[]) => void\n onSearchChange?: (search: string) => void\n onValueChange?: (value: string[]) => void\n options: {\n disabled?: boolean\n group?: string\n label: string\n value: string\n }[]\n sortAlphabetically?: boolean\n value?: string[]\n}\n\nexport const MultiSelect = ({\n emptyText = 'No matches found.',\n loading = false,\n onChange,\n onValueChange,\n options,\n defaultValue,\n ref,\n state,\n value,\n ...props\n}: MultiSelectProps & {\n ref?: Ref<HTMLButtonElement>\n}) => {\n const baseOptions: MultiSelectOption[] = options.map((option) => ({\n disabled: option.disabled ?? false,\n group: option.group,\n label: option.label,\n value: option.value,\n }))\n\n const derivedState = loading ? 'loading' : state\n\n const handleChange = (nextValue: string[]) => {\n onValueChange?.(nextValue)\n onChange?.(nextValue)\n }\n\n return (\n <MultiSelectBase\n data-disabled={props.disabled}\n defaultValue={defaultValue}\n disabled={props.disabled}\n emptyMessage={emptyText}\n onChange={handleChange}\n options={baseOptions}\n ref={ref}\n state={derivedState}\n value={value}\n {...props}\n />\n )\n}\nMultiSelect.displayName = 'MultiSelect'\n"],"mappings":";;;;;;AAoBA,MAAa,eAAe,EAC1B,YAAY,qBACZ,UAAU,OACV,UACA,eACA,SACA,cACA,KACA,OACA,OACA,GAAG,YAGC;CACJ,MAAM,cAAmC,QAAQ,KAAK,YAAY;EAChE,UAAU,OAAO,YAAY;EAC7B,OAAO,OAAO;EACd,OAAO,OAAO;EACd,OAAO,OAAO;EACf,EAAE;CAEH,MAAM,eAAe,UAAU,YAAY;CAE3C,MAAM,gBAAgB,cAAwB;AAC5C,kBAAgB,UAAU;AAC1B,aAAW,UAAU;;AAGvB,QACE,oBAAC,iBAAD;EACE,iBAAe,MAAM;EACP;EACd,UAAU,MAAM;EAChB,cAAc;EACd,UAAU;EACV,SAAS;EACJ;EACL,OAAO;EACA;EACP,GAAI;EACJ;;AAGN,YAAY,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.d.ts","names":[],"sources":["../src/components/Popover/Popover.tsx"],"mappings":";;;;;;KAIY,mBAAA,GAAsB,wBAAA,QAAgC,gBAAA,CAAiB,OAAA;EACjF,KAAA;AAAA;AAAA,cAGW,OAAA;EAAA,GAAO;AAAA,GAAkB,wBAAA,QAAgC,gBAAA,CAAiB,IAAA,MAAK,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cAI/E,cAAA;EAAA,GAAc;AAAA,GAAkB,wBAAA,QAAgC,gBAAA,CAAiB,OAAA,MAAQ,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cAIzF,cAAA;EAAc,KAAA;EAAA,SAAA;EAAA,gBAAA;EAAA,IAAA;EAAA,UAAA;EAAA,KAAA;EAAA,GAAA;AAAA,GAAuH,mBAAA,KAAmB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cA6BxJ,aAAA;EAAA,GAAa;AAAA,GAAkB,wBAAA,QAAgC,gBAAA,CAAiB,MAAA,MAAO,oBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"Popover.d.ts","names":[],"sources":["../src/components/Popover/Popover.tsx"],"mappings":";;;;;;KAIY,mBAAA,GAAsB,wBAAA,QAAgC,gBAAA,CAAiB,OAAA;EACjF,KAAA;AAAA;AAAA,cAGW,OAAA;EAAA,GAAO;AAAA,GAAkB,wBAAA,QAAgC,gBAAA,CAAiB,IAAA,MAAK,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cAU/E,cAAA;EAAA,GAAc;AAAA,GAAkB,wBAAA,QAAgC,gBAAA,CAAiB,OAAA,MAAQ,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cAUzF,cAAA;EAAc,KAAA;EAAA,SAAA;EAAA,gBAAA;EAAA,IAAA;EAAA,UAAA;EAAA,KAAA;EAAA,GAAA;AAAA,GAAuH,mBAAA,KAAmB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,cA6BxJ,aAAA;EAAA,GAAa;AAAA,GAAkB,wBAAA,QAAgC,gBAAA,CAAiB,MAAA,MAAO,oBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Popover.js","names":[],"sources":["../src/components/Popover/Popover.tsx"],"sourcesContent":["import * as PopoverPrimitive from '@radix-ui/react-popover'\nimport { cn } from '@utils/twUtils'\nimport { type ComponentPropsWithoutRef, type CSSProperties } from 'react'\n\nexport type PopoverContentProps = ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {\n width?: number | string\n}\n\nexport const Popover = ({ ...props }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>) => {\n return <PopoverPrimitive.Root data-slot='popover' data-testid='spectral-popover' {...props} />\n}\n\nexport const PopoverTrigger = ({ ...props }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>) => {\n return <PopoverPrimitive.Trigger data-slot='popover-trigger' data-testid='spectral-popover-trigger' {...props} />\n}\n\nexport const PopoverContent = ({ align = 'center', className, collisionPadding = 8, side = 'bottom', sideOffset = 4, width = 'w-fit', ...props }: PopoverContentProps) => {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n align={align}\n collisionPadding={collisionPadding}\n data-slot='popover-content'\n data-testid='spectral-popover-content'\n side={side}\n sideOffset={sideOffset}\n className={cn(\n 'bg-popover-bg text-popover-text motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:animate-in',\n 'motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95',\n 'z-50 h-fit motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2',\n 'rounded-lg p-4 max-h-(--radix-popover-content-available-height) origin-[--radix-popover-content-transform-origin] border-none',\n 'shadow-md w-(--popover-width) overflow-x-hidden overflow-y-auto outline-none',\n className,\n )}\n style={\n {\n '--popover-width': typeof width === 'number' ? `${width}px` : width === 'w-fit' ? '320px' : width,\n } as CSSProperties\n }\n {...props}\n />\n </PopoverPrimitive.Portal>\n )\n}\n\nexport const PopoverAnchor = ({ ...props }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Anchor>) => {\n return <PopoverPrimitive.Anchor data-slot='popover-anchor' {...props} />\n}\n"],"mappings":";;;;;;;AAQA,MAAa,WAAW,EAAE,GAAG,YAAoE;AAC/F,QAAO,oBAAC,iBAAiB,MAAlB;EAAuB,aAAU;EAAU,eAAY;EAAmB,GAAI;EAAS;;AAGhG,MAAa,kBAAkB,EAAE,GAAG,YAAuE;AACzG,QAAO,oBAAC,iBAAiB,SAAlB;EAA0B,aAAU;EAAkB,eAAY;EAA2B,GAAI;EAAS;;AAGnH,MAAa,kBAAkB,EAAE,QAAQ,UAAU,WAAW,mBAAmB,GAAG,OAAO,UAAU,aAAa,GAAG,QAAQ,SAAS,GAAG,YAAiC;AACxK,QACE,oBAAC,iBAAiB,QAAlB,YACE,oBAAC,iBAAiB,SAAlB;EACS;EACW;EAClB,aAAU;EACV,eAAY;EACN;EACM;EACZ,WAAW,GACT,mKACA,mLACA,uKACA,iIACA,gFACA,UACD;EACD,OACE,EACE,mBAAmB,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM,UAAU,UAAU,UAAU,OAC7F;EAEH,GAAI;EACJ,GACsB;;AAI9B,MAAa,iBAAiB,EAAE,GAAG,YAAsE;AACvG,QAAO,oBAAC,iBAAiB,QAAlB;EAAyB,aAAU;EAAiB,GAAI;EAAS"}
1
+ {"version":3,"file":"Popover.js","names":[],"sources":["../src/components/Popover/Popover.tsx"],"sourcesContent":["import * as PopoverPrimitive from '@radix-ui/react-popover'\nimport { cn } from '@utils/twUtils'\nimport { type ComponentPropsWithoutRef, type CSSProperties } from 'react'\n\nexport type PopoverContentProps = ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {\n width?: number | string\n}\n\nexport const Popover = ({ ...props }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>) => {\n return (\n <PopoverPrimitive.Root\n data-slot='popover'\n data-testid='spectral-popover'\n {...props}\n />\n )\n}\n\nexport const PopoverTrigger = ({ ...props }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>) => {\n return (\n <PopoverPrimitive.Trigger\n data-slot='popover-trigger'\n data-testid='spectral-popover-trigger'\n {...props}\n />\n )\n}\n\nexport const PopoverContent = ({ align = 'center', className, collisionPadding = 8, side = 'bottom', sideOffset = 4, width = 'w-fit', ...props }: PopoverContentProps) => {\n return (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n align={align}\n collisionPadding={collisionPadding}\n data-slot='popover-content'\n data-testid='spectral-popover-content'\n side={side}\n sideOffset={sideOffset}\n className={cn(\n 'bg-popover-bg text-popover-text motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:animate-in',\n 'motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95',\n 'z-50 h-fit motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2',\n 'rounded-lg p-4 max-h-(--radix-popover-content-available-height) origin-[--radix-popover-content-transform-origin] border-none',\n 'shadow-md w-(--popover-width) overflow-x-hidden overflow-y-auto outline-none',\n className,\n )}\n style={\n {\n '--popover-width': typeof width === 'number' ? `${width}px` : width === 'w-fit' ? '320px' : width,\n } as CSSProperties\n }\n {...props}\n />\n </PopoverPrimitive.Portal>\n )\n}\n\nexport const PopoverAnchor = ({ ...props }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Anchor>) => {\n return (\n <PopoverPrimitive.Anchor\n data-slot='popover-anchor'\n {...props}\n />\n )\n}\n"],"mappings":";;;;;;;AAQA,MAAa,WAAW,EAAE,GAAG,YAAoE;AAC/F,QACE,oBAAC,iBAAiB,MAAlB;EACE,aAAU;EACV,eAAY;EACZ,GAAI;EACJ;;AAIN,MAAa,kBAAkB,EAAE,GAAG,YAAuE;AACzG,QACE,oBAAC,iBAAiB,SAAlB;EACE,aAAU;EACV,eAAY;EACZ,GAAI;EACJ;;AAIN,MAAa,kBAAkB,EAAE,QAAQ,UAAU,WAAW,mBAAmB,GAAG,OAAO,UAAU,aAAa,GAAG,QAAQ,SAAS,GAAG,YAAiC;AACxK,QACE,oBAAC,iBAAiB,QAAlB,YACE,oBAAC,iBAAiB,SAAlB;EACS;EACW;EAClB,aAAU;EACV,eAAY;EACN;EACM;EACZ,WAAW,GACT,mKACA,mLACA,uKACA,iIACA,gFACA,UACD;EACD,OACE,EACE,mBAAmB,OAAO,UAAU,WAAW,GAAG,MAAM,MAAM,UAAU,UAAU,UAAU,OAC7F;EAEH,GAAI;EACJ,GACsB;;AAI9B,MAAa,iBAAiB,EAAE,GAAG,YAAsE;AACvG,QACE,oBAAC,iBAAiB,QAAlB;EACE,aAAU;EACV,GAAI;EACJ"}
@@ -1,10 +1,13 @@
1
1
  'use client';
2
2
  import { AsChildProp } from "./primitives/slot.js";
3
+ import { ActiveColor, ActiveTextColor } from "./utils/activeColorStyle.js";
3
4
  import { ButtonHTMLAttributes, ReactNode, Ref } from "react";
4
5
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
5
6
 
6
7
  //#region src/components/RadioButton/RadioButton.d.ts
7
8
  interface RadioButtonProps extends AsChildProp, Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onSelect' | 'type'> {
9
+ activeColor?: ActiveColor;
10
+ activeTextColor?: ActiveTextColor;
8
11
  checked?: boolean;
9
12
  children: ReactNode;
10
13
  expanded?: boolean;
@@ -15,6 +18,8 @@ interface RadioButtonProps extends AsChildProp, Omit<ButtonHTMLAttributes<HTMLBu
15
18
  }
16
19
  declare function RadioButton({
17
20
  asChild,
21
+ activeColor,
22
+ activeTextColor,
18
23
  checked,
19
24
  children,
20
25
  className,
@@ -26,6 +31,7 @@ declare function RadioButton({
26
31
  onSelect,
27
32
  ref,
28
33
  variant,
34
+ style,
29
35
  ...rest
30
36
  }: RadioButtonProps & {
31
37
  ref?: Ref<HTMLButtonElement>;
@@ -1 +1 @@
1
- {"version":3,"file":"RadioButton.d.ts","names":[],"sources":["../src/components/RadioButton/RadioButton.tsx"],"mappings":";;;;;;UAIiB,gBAAA,SAAyB,WAAA,EAAa,IAAA,CAAK,oBAAA,CAAqB,iBAAA;EAC/E,OAAA;EACA,QAAA,EAAU,SAAA;EACV,QAAA;EACA,YAAA;EACA,eAAA,IAAmB,OAAA;EACnB,QAAA,IAAY,OAAA;EACZ,OAAA;AAAA;AAAA;EAIA,OAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;EACA,QAAA;EACA,QAAA;EACA,YAAA;EACA,eAAA;EACA,OAAA;EACA,QAAA;EACA,GAAA;EACA,OAAA;EAAA,GACG;AAAA,GACF,gBAAA;EACD,GAAA,GAAM,GAAA,CAAI,iBAAA;AAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"RadioButton.d.ts","names":[],"sources":["../src/components/RadioButton/RadioButton.tsx"],"mappings":";;;;;;;UAKiB,gBAAA,SAAyB,WAAA,EAAa,IAAA,CAAK,oBAAA,CAAqB,iBAAA;EAC/E,WAAA,GAAc,WAAA;EACd,eAAA,GAAkB,eAAA;EAClB,OAAA;EACA,QAAA,EAAU,SAAA;EACV,QAAA;EACA,YAAA;EACA,eAAA,IAAmB,OAAA;EACnB,QAAA,IAAY,OAAA;EACZ,OAAA;AAAA;AAAA;EAIA,OAAA;EACA,WAAA;EACA,eAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;EACA,QAAA;EACA,QAAA;EACA,YAAA;EACA,eAAA;EACA,OAAA;EACA,QAAA;EACA,GAAA;EACA,OAAA;EACA,KAAA;EAAA,GACG;AAAA,GACF,gBAAA;EACD,GAAA,GAAM,GAAA,CAAI,iBAAA;AAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
@@ -1,11 +1,12 @@
1
1
  'use client';
2
2
  import { cn } from "./utils/twUtils.js";
3
3
  import { Slot } from "./primitives/slot.js";
4
+ import { getActiveColorStyle } from "./utils/activeColorStyle.js";
4
5
  import "react";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
 
7
8
  //#region src/components/RadioButton/RadioButton.tsx
8
- const RadioButton = ({ asChild = false, checked = false, children, className, disabled = false, expanded = false, isKeptActive = false, onCheckedChange, onClick, onSelect, ref, variant = "default", ...rest }) => {
9
+ const RadioButton = ({ asChild = false, activeColor = "default", activeTextColor, checked = false, children, className, disabled = false, expanded = false, isKeptActive = false, onCheckedChange, onClick, onSelect, ref, variant = "default", style, ...rest }) => {
9
10
  const handleClick = (event) => {
10
11
  onClick?.(event);
11
12
  if (event.defaultPrevented || disabled) return;
@@ -14,7 +15,11 @@ const RadioButton = ({ asChild = false, checked = false, children, className, di
14
15
  };
15
16
  const baseProps = {
16
17
  ...rest,
17
- className: cn(`gap-2 rounded-md text-sm font-medium focus-visible:border-ring focus-visible:ring-ring/50 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 bg-toggle-bg text-toggle-text shadow-none transition-colors outline-none hover:cursor-pointer hover:border-toggle-border--hover hover:bg-toggle-bg--hover hover:text-toggle-text--hover focus:z-10 focus:outline-none focus-visible:z-10 focus-visible:ring-[3px] active:border-toggle-border--active active:bg-toggle-bg--active active:text-toggle-text--active 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),
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 data-[variant=outline]:[--color-toggle-border:var(--color-toggle-outline-border) inline-flex items-center justify-center border border-toggle-border 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 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 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0`, 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),
19
+ style: {
20
+ ...getActiveColorStyle(activeColor, activeTextColor),
21
+ ...style
22
+ },
18
23
  disabled,
19
24
  onClick: handleClick,
20
25
  role: "radio",
@@ -1 +1 @@
1
- {"version":3,"file":"RadioButton.js","names":[],"sources":["../src/components/RadioButton/RadioButton.tsx"],"sourcesContent":["import { Slot, type AsChildProp } from '@primitives/slot'\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 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 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 ...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 focus-visible:border-ring focus-visible:ring-ring/50 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 bg-toggle-bg text-toggle-text shadow-none transition-colors outline-none hover:cursor-pointer hover:border-toggle-border--hover hover:bg-toggle-bg--hover hover:text-toggle-text--hover focus:z-10 focus:outline-none focus-visible:z-10 focus-visible:ring-[3px] active:border-toggle-border--active active:bg-toggle-bg--active active:text-toggle-text--active disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0`,\n '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 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}\nRadioButton.displayName = 'RadioButton'\n"],"mappings":";;;;;;;AAcA,MAAa,eAAe,EAC1B,UAAU,OACV,UAAU,OACV,UACA,WACA,WAAW,OACX,WAAW,OACX,eAAe,OACf,iBACA,SACA,UACA,KACA,UAAU,WACV,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,8sBACA,qFACA,YAAY,UACZ,gBAAgB,8HAChB,UACD;EACD;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;;AAGb,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 data-[variant=outline]:[--color-toggle-border:var(--color-toggle-outline-border) inline-flex items-center justify-center border border-toggle-border 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 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 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0`,\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\n ref={ref as Ref<HTMLElement>}\n {...baseProps}\n >\n {children}\n </Slot>\n )\n }\n\n return (\n <button\n ref={ref}\n {...baseProps}\n type='button'\n >\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,0vBACA,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;EACO;EACL,GAAI;EAEH;EACI;AAIX,QACE,oBAAC,UAAD;EACO;EACL,GAAI;EACJ,MAAK;EAEJ;EACM;;AAIb,YAAY,cAAc"}
@@ -1,22 +1,29 @@
1
1
  'use client';
2
2
  import { AsChildProp } from "../primitives/slot.js";
3
+ import { ActiveColor, ActiveTextColor } from "../utils/activeColorStyle.js";
3
4
  import { ButtonHTMLAttributes, ReactNode, Ref } from "react";
4
5
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
5
6
 
6
7
  //#region src/components/RadioButtonGroup/RadioButtonGroupBase.d.ts
7
8
  interface RadioButtonGroupProps {
9
+ activeColor?: ActiveColor;
10
+ activeTextColor?: ActiveTextColor;
8
11
  asChild?: boolean;
9
12
  children: ReactNode;
10
13
  className?: string;
11
14
  expanded?: boolean;
12
15
  isKeptActive?: boolean;
16
+ loop?: boolean;
13
17
  onValueChange?: (value: string) => void;
18
+ orientation?: 'horizontal' | 'vertical';
14
19
  value?: string;
15
20
  variant?: 'default' | 'outline';
16
21
  'aria-label'?: string;
17
22
  'aria-labelledby'?: string;
18
23
  }
19
24
  interface RadioButtonGroupItemProps extends AsChildProp, Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value' | 'onSelect' | 'type'> {
25
+ activeColor?: ActiveColor;
26
+ activeTextColor?: ActiveTextColor;
20
27
  children: ReactNode;
21
28
  onSelect?: (value: string) => void;
22
29
  value: string;
@@ -24,26 +31,33 @@ interface RadioButtonGroupItemProps extends AsChildProp, Omit<ButtonHTMLAttribut
24
31
  declare const RadioButtonGroupBase: ({
25
32
  'aria-label': ariaLabel,
26
33
  'aria-labelledby': ariaLabelledby,
34
+ activeColor,
35
+ activeTextColor,
27
36
  children,
28
37
  className,
29
38
  expanded,
30
39
  isKeptActive,
40
+ loop,
31
41
  onValueChange,
42
+ orientation,
32
43
  value,
33
44
  variant
34
45
  }: RadioButtonGroupProps) => _$react_jsx_runtime0.JSX.Element;
35
46
  declare function RadioButtonGroupItem({
36
47
  asChild,
48
+ activeColor,
49
+ activeTextColor,
37
50
  children,
38
51
  className,
39
52
  disabled,
40
53
  onClick,
41
54
  onSelect,
42
55
  ref,
56
+ style,
43
57
  value,
44
58
  ...rest
45
59
  }: RadioButtonGroupItemProps & {
46
- ref?: Ref<HTMLButtonElement>;
60
+ ref?: Ref<HTMLButtonElement | null>;
47
61
  }): _$react_jsx_runtime0.JSX.Element;
48
62
  declare namespace RadioButtonGroupItem {
49
63
  var displayName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"RadioButtonGroupBase.d.ts","names":[],"sources":["../../src/components/RadioButtonGroup/RadioButtonGroupBase.tsx"],"mappings":";;;;;;UAciB,qBAAA;EACf,OAAA;EACA,QAAA,EAAU,SAAA;EACV,SAAA;EACA,QAAA;EACA,YAAA;EACA,aAAA,IAAiB,KAAA;EACjB,KAAA;EACA,OAAA;EACA,YAAA;EACA,iBAAA;AAAA;AAAA,UAGe,yBAAA,SAAkC,WAAA,EAAa,IAAA,CAAK,oBAAA,CAAqB,iBAAA;EACxF,QAAA,EAAU,SAAA;EACV,QAAA,IAAY,KAAA;EACZ,KAAA;AAAA;AAAA,cAGW,oBAAA;EAAA,cAAoB,SAAA;EAAA,mBAAA,cAAA;EAAA,QAAA;EAAA,SAAA;EAAA,QAAA;EAAA,YAAA;EAAA,aAAA;EAAA,KAAA;EAAA;AAAA,GAA4K,qBAAA,KAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA;EAwBhO,OAAA;EACA,QAAA;EACA,SAAA;EACA,QAAA;EACA,OAAA;EACA,QAAA;EACA,GAAA;EACA,KAAA;EAAA,GACG;AAAA,GACF,yBAAA;EACD,GAAA,GAAM,GAAA,CAAI,iBAAA;AAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"RadioButtonGroupBase.d.ts","names":[],"sources":["../../src/components/RadioButtonGroup/RadioButtonGroupBase.tsx"],"mappings":";;;;;;;UAyBiB,qBAAA;EACf,WAAA,GAAc,WAAA;EACd,eAAA,GAAkB,eAAA;EAClB,OAAA;EACA,QAAA,EAAU,SAAA;EACV,SAAA;EACA,QAAA;EACA,YAAA;EACA,IAAA;EACA,aAAA,IAAiB,KAAA;EACjB,WAAA;EACA,KAAA;EACA,OAAA;EACA,YAAA;EACA,iBAAA;AAAA;AAAA,UAGe,yBAAA,SAAkC,WAAA,EAAa,IAAA,CAAK,oBAAA,CAAqB,iBAAA;EACxF,WAAA,GAAc,WAAA;EACd,eAAA,GAAkB,eAAA;EAClB,QAAA,EAAU,SAAA;EACV,QAAA,IAAY,KAAA;EACZ,KAAA;AAAA;AAAA,cASW,oBAAA;EAAA,cAAoB,SAAA;EAAA,mBAAA,cAAA;EAAA,WAAA;EAAA,eAAA;EAAA,QAAA;EAAA,SAAA;EAAA,QAAA;EAAA,YAAA;EAAA,IAAA;EAAA,aAAA;EAAA,WAAA;EAAA,KAAA;EAAA;AAAA,GAc9B,qBAAA,KAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA;EA4GtB,OAAA;EACA,WAAA;EACA,eAAA;EACA,QAAA;EACA,SAAA;EACA,QAAA;EACA,OAAA;EACA,QAAA;EACA,GAAA;EACA,KAAA;EACA,KAAA;EAAA,GACG;AAAA,GACF,yBAAA;EACD,GAAA,GAAM,GAAA,CAAI,iBAAA;AAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
@@ -1,37 +1,133 @@
1
1
  'use client';
2
2
  import { cn } from "../utils/twUtils.js";
3
3
  import { Slot } from "../primitives/slot.js";
4
- import { createContext, useContext } from "react";
4
+ import { getActiveColorStyle } from "../utils/activeColorStyle.js";
5
+ import { createContext, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
 
7
8
  //#region src/components/RadioButtonGroup/RadioButtonGroupBase.tsx
8
9
  const RadioButtonGroupContext = createContext(null);
9
- const RadioButtonGroupBase = ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, children, className, expanded = false, isKeptActive = false, onValueChange, value, variant = "default" }) => {
10
+ const RadioButtonGroupBase = ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, activeColor = "default", activeTextColor, children, className, expanded = false, isKeptActive = false, loop = true, onValueChange, orientation = "horizontal", value, variant = "default" }) => {
11
+ const [registry, setRegistry] = useState([]);
12
+ const register = useCallback((nextValue, element, disabled) => {
13
+ setRegistry((previousRegistry) => {
14
+ const existingIndex = previousRegistry.findIndex((item) => item.value === nextValue);
15
+ if (existingIndex !== -1) {
16
+ const existingItem = previousRegistry[existingIndex];
17
+ if (existingItem?.element === element && existingItem.disabled === disabled) return previousRegistry;
18
+ return previousRegistry.map((item, index) => index === existingIndex ? {
19
+ value: nextValue,
20
+ element,
21
+ disabled
22
+ } : item);
23
+ }
24
+ return [...previousRegistry, {
25
+ value: nextValue,
26
+ element,
27
+ disabled
28
+ }];
29
+ });
30
+ return () => {
31
+ setRegistry((previousRegistry) => previousRegistry.filter((item) => item.value !== nextValue));
32
+ };
33
+ }, []);
34
+ const getIndexByValue = useCallback((itemValue) => registry.findIndex((item) => item.value === itemValue), [registry]);
35
+ const itemCount = useCallback(() => registry.length, [registry]);
36
+ const isDisabledAtIndex = useCallback((index) => registry[index]?.disabled ?? false, [registry]);
37
+ const getNextEnabledIndex = useCallback((startIndex, direction) => {
38
+ const items = registry;
39
+ if (items.length === 0) return -1;
40
+ let currentIndex = startIndex;
41
+ for (let attempt = 0; attempt < items.length; attempt += 1) {
42
+ if (!loop && (currentIndex < 0 || currentIndex >= items.length)) return -1;
43
+ const boundedIndex = loop ? (currentIndex + items.length) % items.length : currentIndex;
44
+ if (!items[boundedIndex]?.disabled) return boundedIndex;
45
+ currentIndex += direction;
46
+ }
47
+ return -1;
48
+ }, [loop, registry]);
49
+ const focusItemByIndex = useCallback((index) => {
50
+ const targetIndex = getNextEnabledIndex(index, 1);
51
+ if (targetIndex === -1) return;
52
+ registry[targetIndex]?.element?.focus();
53
+ }, [getNextEnabledIndex, registry]);
54
+ const selectByIndex = useCallback((index) => {
55
+ const target = registry[index];
56
+ if (!target || target.disabled) return;
57
+ onValueChange?.(target.value);
58
+ }, [onValueChange, registry]);
59
+ const contextValue = useMemo(() => ({
60
+ activeColor,
61
+ activeTextColor,
62
+ focusItemByIndex,
63
+ getIndexByValue,
64
+ isDisabledAtIndex,
65
+ itemCount,
66
+ loop,
67
+ onValueChange,
68
+ orientation,
69
+ selectByIndex,
70
+ value,
71
+ isKeptActive,
72
+ expanded,
73
+ variant,
74
+ register
75
+ }), [
76
+ activeColor,
77
+ activeTextColor,
78
+ expanded,
79
+ focusItemByIndex,
80
+ getIndexByValue,
81
+ isDisabledAtIndex,
82
+ isKeptActive,
83
+ itemCount,
84
+ loop,
85
+ onValueChange,
86
+ orientation,
87
+ register,
88
+ selectByIndex,
89
+ value,
90
+ variant
91
+ ]);
10
92
  return /* @__PURE__ */ jsx(RadioButtonGroupContext.Provider, {
11
- value: {
12
- value,
13
- onValueChange,
14
- isKeptActive,
15
- expanded,
16
- variant
17
- },
93
+ value: contextValue,
18
94
  children: /* @__PURE__ */ jsx("div", {
19
95
  "aria-label": ariaLabel,
20
96
  "aria-labelledby": ariaLabelledby,
21
97
  className: cn("rounded-md [&_button:first-of-type]:rounded-l-md [&_button:last-of-type]:rounded-r-md flex h-fit w-fit items-center", "data-[expanded=true]:w-full", `data-[variant=outline]:gap-0 data-[variant=outline]:[--color-toggle-border:var(--color-toggle-outline-border)] data-[variant=outline]:[&_button:not(:last-of-type)]:[border-right-color:var(--color-toggle-outline-divider)]`, className),
22
98
  "data-expanded": expanded,
99
+ "data-orientation": orientation,
23
100
  "data-testid": "spectral-radio-button-group",
24
101
  "data-variant": variant,
102
+ "aria-orientation": orientation,
25
103
  role: "radiogroup",
26
104
  children
27
105
  })
28
106
  });
29
107
  };
30
- const RadioButtonGroupItem = ({ asChild = false, children, className, disabled = false, onClick, onSelect, ref, value, ...rest }) => {
108
+ const RadioButtonGroupItem = ({ asChild = false, activeColor, activeTextColor, children, className, disabled = false, onClick, onSelect, ref, style, value, ...rest }) => {
31
109
  const context = useContext(RadioButtonGroupContext);
32
110
  if (!context) throw new Error("RadioButtonGroupItem must be used within a RadioButtonGroup");
33
- const { value: selectedValue, onValueChange, isKeptActive, expanded, variant } = context;
111
+ const { activeColor: contextActiveColor, activeTextColor: contextActiveTextColor, value: selectedValue, onValueChange, isKeptActive, expanded, variant, orientation, register, getIndexByValue, isDisabledAtIndex, focusItemByIndex, itemCount, loop, selectByIndex } = context;
34
112
  const isSelected = selectedValue === value;
113
+ const resolvedActiveColor = activeColor ?? contextActiveColor;
114
+ const resolvedActiveTextColor = activeTextColor ?? contextActiveTextColor;
115
+ const itemRef = useRef(null);
116
+ useImperativeHandle(ref, () => itemRef.current);
117
+ useEffect(() => register(value, itemRef.current, disabled), [
118
+ disabled,
119
+ register,
120
+ value
121
+ ]);
122
+ const setItemRef = (node) => {
123
+ itemRef.current = node;
124
+ if (!ref) return;
125
+ if (typeof ref === "function") {
126
+ ref(node);
127
+ return;
128
+ }
129
+ ref.current = node;
130
+ };
35
131
  const handleClick = (event) => {
36
132
  if (onClick) onClick(event);
37
133
  if (event.defaultPrevented) return;
@@ -40,24 +136,79 @@ const RadioButtonGroupItem = ({ asChild = false, children, className, disabled =
40
136
  if (onSelect) onSelect(value);
41
137
  }
42
138
  };
139
+ const handleKeyDown = (event) => {
140
+ if (rest.onKeyDown) rest.onKeyDown(event);
141
+ if (event.defaultPrevented) return;
142
+ const currentIndex = getIndexByValue(value);
143
+ if (currentIndex === -1) return;
144
+ const key = event.key;
145
+ const isHorizontal = orientation === "horizontal";
146
+ const moveNext = isHorizontal && key === "ArrowRight" || !isHorizontal && key === "ArrowDown";
147
+ const movePrev = isHorizontal && key === "ArrowLeft" || !isHorizontal && key === "ArrowUp";
148
+ if (key === "Home") {
149
+ event.preventDefault();
150
+ for (let index = 0; index < itemCount(); index += 1) if (!isDisabledAtIndex(index)) {
151
+ selectByIndex(index);
152
+ focusItemByIndex(index);
153
+ return;
154
+ }
155
+ return;
156
+ }
157
+ if (key === "End") {
158
+ event.preventDefault();
159
+ for (let index = itemCount() - 1; index >= 0; index -= 1) if (!isDisabledAtIndex(index)) {
160
+ selectByIndex(index);
161
+ focusItemByIndex(index);
162
+ return;
163
+ }
164
+ return;
165
+ }
166
+ if (!moveNext && !movePrev) return;
167
+ event.preventDefault();
168
+ const direction = moveNext ? 1 : -1;
169
+ let nextIndex = currentIndex + direction;
170
+ for (let attempt = 0; attempt < itemCount(); attempt += 1) {
171
+ if (!loop && (nextIndex < 0 || nextIndex >= itemCount())) return;
172
+ const boundedIndex = loop ? (nextIndex + itemCount()) % itemCount() : nextIndex;
173
+ if (!isDisabledAtIndex(boundedIndex)) {
174
+ selectByIndex(boundedIndex);
175
+ focusItemByIndex(boundedIndex);
176
+ return;
177
+ }
178
+ nextIndex += direction;
179
+ }
180
+ };
181
+ const selectedIndex = selectedValue ? getIndexByValue(selectedValue) : -1;
182
+ const firstEnabledIndex = (() => {
183
+ for (let index = 0; index < itemCount(); index += 1) if (!isDisabledAtIndex(index)) return index;
184
+ return -1;
185
+ })();
186
+ const currentIndex = getIndexByValue(value);
187
+ const tabIndex = isSelected || selectedIndex === -1 && firstEnabledIndex !== -1 && currentIndex === firstEnabledIndex ? 0 : -1;
43
188
  const baseProps = {
44
189
  ...rest,
45
- className: cn(`gap-2 text-sm font-medium focus-visible:border-ring focus-visible:ring-ring/50 h-9 px-3 min-w-9 [&_svg:not([class*='size-']):not([width]):not([height])]:size-4 inline-flex items-center justify-center rounded-none border border-toggle-border bg-toggle-bg text-toggle-text shadow-none transition-[colors] outline-none hover:cursor-pointer hover:border-toggle-border--hover hover:bg-toggle-bg--hover hover:text-toggle-text--hover focus:z-10 focus:outline-none focus-visible:z-10 focus-visible:ring-[3px] active:border-toggle-border--active active:bg-toggle-bg--active active:text-toggle-text--active disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&:not(:first-child)]:border-l-0`, 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),
190
+ className: cn(`gap-2 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 rounded-none border border-toggle-border 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 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 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&:not(:first-child)]:border-l-0`, 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),
191
+ style: {
192
+ ...getActiveColorStyle(resolvedActiveColor, resolvedActiveTextColor),
193
+ ...style
194
+ },
46
195
  disabled,
47
196
  onClick: handleClick,
197
+ onKeyDown: handleKeyDown,
48
198
  role: "radio",
49
199
  "aria-checked": isSelected,
50
200
  "data-state": isSelected ? "on" : "off",
51
201
  "data-testid": "spectral-radio-button-group-item",
52
- "data-variant": variant
202
+ "data-variant": variant,
203
+ tabIndex: disabled ? -1 : tabIndex
53
204
  };
54
205
  if (asChild) return /* @__PURE__ */ jsx(Slot, {
55
- ref,
206
+ ref: setItemRef,
56
207
  ...baseProps,
57
208
  children
58
209
  });
59
210
  return /* @__PURE__ */ jsx("button", {
60
- ref,
211
+ ref: setItemRef,
61
212
  ...baseProps,
62
213
  type: "button",
63
214
  children