@spear-ai/spectral 1.20.1 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/Accordion.js +0 -4
  2. package/dist/Accordion.js.map +1 -1
  3. package/dist/Alert/AlertBase.d.ts +0 -1
  4. package/dist/Alert/AlertBase.d.ts.map +1 -1
  5. package/dist/Alert/AlertBase.js +14 -17
  6. package/dist/Alert/AlertBase.js.map +1 -1
  7. package/dist/Alert.js +16 -4
  8. package/dist/Alert.js.map +1 -1
  9. package/dist/AlertDialog.js +3 -3
  10. package/dist/AlertDialog.js.map +1 -1
  11. package/dist/Avatar.js +1 -9
  12. package/dist/Avatar.js.map +1 -1
  13. package/dist/Badge.js +0 -1
  14. package/dist/Badge.js.map +1 -1
  15. package/dist/Button.js +0 -3
  16. package/dist/Button.js.map +1 -1
  17. package/dist/ButtonGroup.js +0 -4
  18. package/dist/ButtonGroup.js.map +1 -1
  19. package/dist/ButtonIcon.js +0 -1
  20. package/dist/ButtonIcon.js.map +1 -1
  21. package/dist/ButtonIconSlideout.js +0 -3
  22. package/dist/ButtonIconSlideout.js.map +1 -1
  23. package/dist/Checkbox.d.ts.map +1 -1
  24. package/dist/Checkbox.js +5 -8
  25. package/dist/Checkbox.js.map +1 -1
  26. package/dist/Combobox.d.ts +1 -0
  27. package/dist/Combobox.d.ts.map +1 -1
  28. package/dist/Combobox.js +0 -4
  29. package/dist/Combobox.js.map +1 -1
  30. package/dist/ControlGroup/ControlGroupSelect.js +0 -5
  31. package/dist/ControlGroup/ControlGroupSelect.js.map +1 -1
  32. package/dist/DataCard/Card.js +0 -6
  33. package/dist/DataCard/Card.js.map +1 -1
  34. package/dist/DataCard.js +1 -7
  35. package/dist/DataCard.js.map +1 -1
  36. package/dist/DateTimePicker/Calendar.js +0 -1
  37. package/dist/DateTimePicker/Calendar.js.map +1 -1
  38. package/dist/DateTimePicker/DateTimeInput.js +0 -1
  39. package/dist/DateTimePicker/DateTimeInput.js.map +1 -1
  40. package/dist/DateTimePicker/TimePeriodSelect.js +0 -4
  41. package/dist/DateTimePicker/TimePeriodSelect.js.map +1 -1
  42. package/dist/DateTimePicker/TimePicker.js +0 -3
  43. package/dist/DateTimePicker/TimePicker.js.map +1 -1
  44. package/dist/DateTimePicker.js +0 -4
  45. package/dist/DateTimePicker.js.map +1 -1
  46. package/dist/Dialog.js +0 -16
  47. package/dist/Dialog.js.map +1 -1
  48. package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.js +0 -2
  49. package/dist/DirectionalColorWheel/DirectionalColorWheelDisclosure.js.map +1 -1
  50. package/dist/DirectionalColorWheel/DirectionalColorWheelGlyph.js +0 -1
  51. package/dist/DirectionalColorWheel/DirectionalColorWheelGlyph.js.map +1 -1
  52. package/dist/DirectionalColorWheel.js +9 -21
  53. package/dist/DirectionalColorWheel.js.map +1 -1
  54. package/dist/Drawer.js +6 -29
  55. package/dist/Drawer.js.map +1 -1
  56. package/dist/DropdownMenu.js +1 -9
  57. package/dist/DropdownMenu.js.map +1 -1
  58. package/dist/FormFieldMessage.js +0 -1
  59. package/dist/FormFieldMessage.js.map +1 -1
  60. package/dist/HoverCard.js +0 -3
  61. package/dist/HoverCard.js.map +1 -1
  62. package/dist/Input.js +0 -10
  63. package/dist/Input.js.map +1 -1
  64. package/dist/InputOTP.js +0 -4
  65. package/dist/InputOTP.js.map +1 -1
  66. package/dist/InputSearch.js +3 -15
  67. package/dist/InputSearch.js.map +1 -1
  68. package/dist/Kbd.js +0 -2
  69. package/dist/Kbd.js.map +1 -1
  70. package/dist/Meter.d.ts +23 -0
  71. package/dist/Meter.d.ts.map +1 -0
  72. package/dist/Meter.js +45 -0
  73. package/dist/Meter.js.map +1 -0
  74. package/dist/MultiSelect/MultiSelectBase.js +1 -16
  75. package/dist/MultiSelect/MultiSelectBase.js.map +1 -1
  76. package/dist/Popover.js +0 -3
  77. package/dist/Popover.js.map +1 -1
  78. package/dist/RadialMenu.js +1 -7
  79. package/dist/RadialMenu.js.map +1 -1
  80. package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts.map +1 -1
  81. package/dist/RadioButtonGroup/RadioButtonGroupBase.js +1 -4
  82. package/dist/RadioButtonGroup/RadioButtonGroupBase.js.map +1 -1
  83. package/dist/RadioButtonGroup.js +1 -1
  84. package/dist/RadioButtonGroup.js.map +1 -1
  85. package/dist/RadioGroup.js +0 -6
  86. package/dist/RadioGroup.js.map +1 -1
  87. package/dist/Select.js +12 -41
  88. package/dist/Select.js.map +1 -1
  89. package/dist/Slider.js +2 -11
  90. package/dist/Slider.js.map +1 -1
  91. package/dist/Switch.js +0 -6
  92. package/dist/Switch.js.map +1 -1
  93. package/dist/Tabs/TabsBase.js +0 -5
  94. package/dist/Tabs/TabsBase.js.map +1 -1
  95. package/dist/Textarea.js +0 -4
  96. package/dist/Textarea.js.map +1 -1
  97. package/dist/Toast.d.ts +2 -0
  98. package/dist/Toast.d.ts.map +1 -1
  99. package/dist/Toast.js +9 -8
  100. package/dist/Toast.js.map +1 -1
  101. package/dist/Toggle.js +0 -1
  102. package/dist/Toggle.js.map +1 -1
  103. package/dist/ToggleGroup/ToggleGroupItem.js +0 -1
  104. package/dist/ToggleGroup/ToggleGroupItem.js.map +1 -1
  105. package/dist/ToggleGroup.js +0 -1
  106. package/dist/ToggleGroup.js.map +1 -1
  107. package/dist/Tooltip.d.ts.map +1 -1
  108. package/dist/Tooltip.js +4 -5
  109. package/dist/Tooltip.js.map +1 -1
  110. package/dist/Tray.js +1 -9
  111. package/dist/Tray.js.map +1 -1
  112. package/dist/index.d.ts +2 -1
  113. package/dist/index.js +2 -1
  114. package/dist/styles/horizon/base.css +31 -16
  115. package/dist/styles/horizon/colors.css +37 -21
  116. package/dist/styles/horizon/theme.css +15 -7
  117. package/dist/styles/horizon/utilities.css +19 -45
  118. package/dist/styles/spectral.css +1 -1
  119. package/package.json +4 -1
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { ComponentProps, ReactNode } from "react";
3
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
+ import { Meter as Meter$1 } from "@base-ui/react/meter";
5
+
6
+ //#region src/components/Meter/Meter.d.ts
7
+ interface MeterProps extends ComponentProps<typeof Meter$1.Root> {
8
+ dataTestId?: string;
9
+ label: ReactNode;
10
+ value: number;
11
+ }
12
+ declare const Meter: ({
13
+ className,
14
+ dataTestId,
15
+ label,
16
+ max,
17
+ min,
18
+ value,
19
+ ...props
20
+ }: MeterProps) => _$react_jsx_runtime0.JSX.Element;
21
+ //#endregion
22
+ export { Meter, MeterProps };
23
+ //# sourceMappingURL=Meter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Meter.d.ts","names":[],"sources":["../src/components/Meter/Meter.tsx"],"mappings":";;;;;;UAIiB,UAAA,SAAmB,cAAA,QAAsB,OAAA,CAAe,IAAA;EACvE,UAAA;EACA,KAAA,EAAO,SAAA;EACP,KAAA;AAAA;AAAA,cAGW,KAAA;EAAK,SAAA;EAAA,UAAA;EAAA,KAAA;EAAA,GAAA;EAAA,GAAA;EAAA,KAAA;EAAA,GAAA;AAAA,GAA8F,UAAA,KAAU,oBAAA,CAAA,GAAA,CAAA,OAAA"}
package/dist/Meter.js ADDED
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+ import { cn } from "./utils/twUtils.js";
3
+ import "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Meter as Meter$1 } from "@base-ui/react/meter";
6
+
7
+ //#region src/components/Meter/Meter.tsx
8
+ const Meter = ({ className, dataTestId = "spectral-meter", label, max = 100, min = 0, value, ...props }) => {
9
+ const scale = max === min ? 0 : Math.min(1, Math.max(0, (value - min) / (max - min)));
10
+ return /* @__PURE__ */ jsxs(Meter$1.Root, {
11
+ "aria-valuetext": `${value}%`,
12
+ ...props,
13
+ className: cn("w-full flex flex-col gap-2", className),
14
+ "data-slot": "meter",
15
+ max,
16
+ min,
17
+ value,
18
+ children: [/* @__PURE__ */ jsxs("div", {
19
+ className: "gap-2 flex items-center justify-between",
20
+ children: [/* @__PURE__ */ jsx(Meter$1.Label, {
21
+ className: "text-sm font-medium",
22
+ "data-slot": "meter-label",
23
+ children: label
24
+ }), /* @__PURE__ */ jsx(Meter$1.Value, {
25
+ className: "text-text-primary text-xs tabular-nums",
26
+ "data-slot": "meter-value"
27
+ })]
28
+ }), /* @__PURE__ */ jsx(Meter$1.Track, {
29
+ className: "h-2 w-full flex items-center overflow-hidden rounded-full bg-meter-track",
30
+ "data-slot": "meter-track",
31
+ children: /* @__PURE__ */ jsx(Meter$1.Indicator, {
32
+ className: "h-full origin-left transform-gpu transition-transform duration-150 ease-out bg-meter-indicator",
33
+ "data-slot": "meter-indicator",
34
+ style: {
35
+ transform: `scaleX(${scale})`,
36
+ width: "100%"
37
+ }
38
+ })
39
+ })]
40
+ });
41
+ };
42
+
43
+ //#endregion
44
+ export { Meter };
45
+ //# sourceMappingURL=Meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Meter.js","names":[],"sources":["../src/components/Meter/Meter.tsx"],"sourcesContent":["import { Meter as MeterPrimitive } from '@base-ui/react/meter'\nimport { cn } from '@utils/twUtils'\nimport { type ComponentProps, type ReactNode } from 'react'\n\nexport interface MeterProps extends ComponentProps<typeof MeterPrimitive.Root> {\n dataTestId?: string\n label: ReactNode\n value: number\n}\n\nexport const Meter = ({ className, dataTestId = 'spectral-meter', label, max = 100, min = 0, value, ...props }: MeterProps) => {\n const scale = max === min ? 0 : Math.min(1, Math.max(0, (value - min) / (max - min)))\n\n return (\n <MeterPrimitive.Root aria-valuetext={`${value}%`} {...props} className={cn('w-full flex flex-col gap-2', className)} data-slot='meter' data-testid={dataTestId} max={max} min={min} value={value}>\n <div className='gap-2 flex items-center justify-between'>\n <MeterPrimitive.Label className='text-sm font-medium' data-slot='meter-label'>\n {label}\n </MeterPrimitive.Label>\n <MeterPrimitive.Value className='text-text-primary text-xs tabular-nums' data-slot='meter-value' />\n </div>\n <MeterPrimitive.Track className='h-2 w-full flex items-center overflow-hidden rounded-full bg-meter-track' data-slot='meter-track'>\n <MeterPrimitive.Indicator className='h-full origin-left transform-gpu transition-transform duration-150 ease-out bg-meter-indicator' data-slot='meter-indicator' style={{ transform: `scaleX(${scale})`, width: '100%' }} />\n </MeterPrimitive.Track>\n </MeterPrimitive.Root>\n )\n}\n"],"mappings":";;;;;;;AAUA,MAAa,SAAS,EAAE,WAAW,aAAa,kBAAkB,OAAO,MAAM,KAAK,MAAM,GAAG,OAAO,GAAG,YAAwB;CAC7H,MAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,QAAQ,MAAM,KAAK,CAAA;AAEpF,QACE,qBAAC,QAAe,MAAhB;EAAqB,kBAAgB,GAAG,MAAM;EAAI,GAAI;EAAO,WAAW,GAAG,8BAA8B,UAAU;EAAE,aAAU;EAAsC;EAAU;EAAY;YAA3L,CACE,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,QAAe,OAAhB;IAAsB,WAAU;IAAsB,aAAU;cAC7D;IACmB,GACtB,oBAAC,QAAe,OAAhB;IAAsB,WAAU;IAAyC,aAAU;IAAe,EAC/F;MACL,oBAAC,QAAe,OAAhB;GAAsB,WAAU;GAA2E,aAAU;aACnH,oBAAC,QAAe,WAAhB;IAA0B,WAAU;IAAiG,aAAU;IAAkB,OAAO;KAAE,WAAW,UAAU,MAAM;KAAI,OAAO;KAAQ;IAAG;GACvM,EACH"}
@@ -235,7 +235,6 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
235
235
  }), /* @__PURE__ */ jsx("button", {
236
236
  "aria-label": `Remove ${option.label}`,
237
237
  className: "hover:text-danger rounded-sm cursor-pointer",
238
- "data-testid": "spectral-multiselect-remove-item-button",
239
238
  onClick: (e) => {
240
239
  e.preventDefault();
241
240
  e.stopPropagation();
@@ -271,7 +270,6 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
271
270
  role: "option",
272
271
  type: "button",
273
272
  children: [/* @__PURE__ */ jsx("div", {
274
- "data-testid": "spectral-multiselect-selected-indicator",
275
273
  className: cn("w-4 h-4 rounded flex items-center justify-center border border-input-border", isSelected && "bg-primary border-primary"),
276
274
  children: isSelected && /* @__PURE__ */ jsx(CheckmarkIcon, { size: 12 })
277
275
  }), /* @__PURE__ */ jsx("span", { children: option.label })]
@@ -284,11 +282,9 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
284
282
  });
285
283
  return /* @__PURE__ */ jsxs("div", {
286
284
  className: "w-full",
287
- "data-testid": "spectral-multiselect-root",
288
285
  children: [
289
286
  label && /* @__PURE__ */ jsx(Label, {
290
287
  className: cn("mb-2 block text-text-primary", isDisabled && "text-text-secondary"),
291
- "data-testid": "spectral-multiselect-label",
292
288
  htmlFor: multiSelectId,
293
289
  children: label
294
290
  }),
@@ -297,7 +293,6 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
297
293
  onOpenChange: setIsOpen,
298
294
  children: /* @__PURE__ */ jsxs("div", {
299
295
  className: "relative",
300
- "data-testid": "spectral-multiselect-wrapper",
301
296
  onKeyDown: isOpen ? handleKeyDown : void 0,
302
297
  role: "none",
303
298
  children: [
@@ -310,7 +305,6 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
310
305
  "aria-label": ariaLabel ?? label,
311
306
  className: cn(getTriggerClasses(isOpen, state, className), "max-h-22 py-2 text-sm"),
312
307
  "data-state": state,
313
- "data-testid": "spectral-multiselect-trigger",
314
308
  disabled: isDisabled,
315
309
  id: multiSelectId,
316
310
  name,
@@ -322,7 +316,6 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
322
316
  ...props,
323
317
  children: [/* @__PURE__ */ jsx("div", {
324
318
  className: "min-w-0 flex-1 overflow-hidden",
325
- "data-testid": "spectral-multiselect-selected-items",
326
319
  children: renderSelectedItems()
327
320
  }), /* @__PURE__ */ jsx("div", {
328
321
  className: "gap-2 ml-2 flex shrink-0 items-center",
@@ -336,7 +329,6 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
336
329
  showClearAll && value.length > 0 && /* @__PURE__ */ jsx("button", {
337
330
  "aria-label": "Clear all selections",
338
331
  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",
339
- "data-testid": "spectral-multiselect-clear-all-button",
340
332
  disabled: isDisabled,
341
333
  onClick: (e) => {
342
334
  e.stopPropagation();
@@ -353,7 +345,6 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
353
345
  collisionPadding: 10,
354
346
  "data-dropdown-width-mode": dropdownWidthMode,
355
347
  "data-dropdown-width-value": dropdownWidthMode === "custom" ? dropdownWidth : void 0,
356
- "data-testid": "spectral-multiselect-dropdown",
357
348
  onOpenAutoFocus: (e) => {
358
349
  e.preventDefault();
359
350
  if (showSearch) searchInputRef.current?.focus();
@@ -373,7 +364,6 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
373
364
  children: [/* @__PURE__ */ jsx(SearchIcon, { className: cn(ICON_SIZE, "left-3 text-input-icon absolute top-1/2 -translate-y-1/2") }), /* @__PURE__ */ jsx("input", {
374
365
  "aria-label": "Search options",
375
366
  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",
376
- "data-testid": "spectral-multiselect-search-input",
377
367
  onChange: handleSearchChange,
378
368
  placeholder: searchPlaceholder,
379
369
  ref: searchInputRef,
@@ -387,18 +377,15 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
387
377
  role: "listbox",
388
378
  children: isLoading ? /* @__PURE__ */ jsx(LoadingState, {
389
379
  className: "text-sm",
390
- message: loadingMessage,
391
- "data-testid": "spectral-multiselect-loading"
380
+ message: loadingMessage
392
381
  }) : filteredOptions.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
393
382
  className: "text-sm",
394
- "data-testid": "spectral-multiselect-empty-message",
395
383
  message: emptyMessage
396
384
  }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
397
385
  showSelectAll && /* @__PURE__ */ jsxs("div", {
398
386
  className: "mb-1",
399
387
  children: [/* @__PURE__ */ jsx("button", {
400
388
  className: cn("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", isSelectAllFocused && "bg-input-bg--hover"),
401
- "data-testid": "spectral-multiselect-select-all-button",
402
389
  onClick: handleSelectAll,
403
390
  type: "button",
404
391
  children: isAllSelected ? clearAllLabel : selectAllLabel
@@ -410,11 +397,9 @@ const MultiSelectBase = ({ className, clearAllLabel = "Clear all", closeOnSelect
410
397
  }),
411
398
  Object.entries(groupedOptions.groups).map(([groupName, groupOptions]) => /* @__PURE__ */ jsxs("div", {
412
399
  className: "mb-1",
413
- "data-testid": "spectral-multiselect-group",
414
400
  children: [
415
401
  (groupedOptions.ungrouped.length > 0 || Object.keys(groupedOptions.groups).indexOf(groupName) > 0) && /* @__PURE__ */ jsx("div", { className: "mx-3 my-1 h-px bg-input-border" }),
416
402
  /* @__PURE__ */ jsx("div", {
417
- "data-testid": "spectral-multiselect-group-name",
418
403
  className: "px-3 py-1 text-xs font-semibold text-input-text-secondary tracking-wide uppercase",
419
404
  children: groupName
420
405
  }),
@@ -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 <button\n aria-label={`Remove ${option.label}`}\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 type='button'\n >\n <CloseIcon size={12} />\n </button>\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' // 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 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 <div aria-multiselectable='true' className='max-h-64 overflow-y-auto' id={listboxId} 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 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\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":"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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 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 <button\n aria-label={`Remove ${option.label}`}\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 type='button'\n >\n <CloseIcon size={12} />\n </button>\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' // 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 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 <div aria-multiselectable='true' className='max-h-64 overflow-y-auto' id={listboxId} 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 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\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":"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"}
package/dist/Popover.js CHANGED
@@ -8,14 +8,12 @@ import * as PopoverPrimitive from "@radix-ui/react-popover";
8
8
  const Popover = ({ ...props }) => {
9
9
  return /* @__PURE__ */ jsx(PopoverPrimitive.Root, {
10
10
  "data-slot": "popover",
11
- "data-testid": "spectral-popover",
12
11
  ...props
13
12
  });
14
13
  };
15
14
  const PopoverTrigger = ({ ...props }) => {
16
15
  return /* @__PURE__ */ jsx(PopoverPrimitive.Trigger, {
17
16
  "data-slot": "popover-trigger",
18
- "data-testid": "spectral-popover-trigger",
19
17
  ...props
20
18
  });
21
19
  };
@@ -24,7 +22,6 @@ const PopoverContent = ({ align = "center", className, collisionPadding = 8, sid
24
22
  align,
25
23
  collisionPadding,
26
24
  "data-slot": "popover-content",
27
- "data-testid": "spectral-popover-content",
28
25
  side,
29
26
  sideOffset,
30
27
  className: cn("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", "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", "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", "rounded-lg p-4 max-h-(--radix-popover-content-available-height) origin-[--radix-popover-content-transform-origin] border-none", "shadow-md w-(--popover-width) overflow-x-hidden overflow-y-auto outline-none", className),
@@ -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 <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;EAAyC,GAAI;EAAQ;;AAG/F,MAAa,kBAAkB,EAAE,GAAG,YAAuE;AACzG,QAAO,oBAAC,iBAAiB,SAAlB;EAA0B,aAAU;EAAyD,GAAI;EAAQ;;AAGlH,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;EAEJ;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;EACL,GACsB;;AAI7B,MAAa,iBAAiB,EAAE,GAAG,YAAsE;AACvG,QAAO,oBAAC,iBAAiB,QAAlB;EAAyB,aAAU;EAAiB,GAAI;EAAQ"}
@@ -229,7 +229,6 @@ const RadialMenu = ({ ariaLabel, backLabel = "Back", className, dataTestId, item
229
229
  "aria-haspopup": hasSubmenu ? "menu" : void 0,
230
230
  "aria-label": item.label,
231
231
  className: cn("size-12 absolute flex -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full", "border border-border-primary bg-popover-bg text-popover-text shadow-elevation-3", "hover:scale-110 hover:cursor-pointer hover:border-toggle-border hover:bg-level-three", "motion-safe:transition-[left,top,opacity,transform] motion-safe:duration-300", BOUNCE_EASE, (isHighlighted || isActive) && "border-toggle-border bg-level-three", isHighlighted && "scale-110", item.destructive === true && "text-danger-300", item.disabled === true && "pointer-events-none hover:scale-100", nonInteractive && "pointer-events-none"),
232
- "data-testid": testId,
233
232
  disabled: item.disabled,
234
233
  onClick,
235
234
  role: "menuitem",
@@ -243,7 +242,6 @@ const RadialMenu = ({ ariaLabel, backLabel = "Back", className, dataTestId, item
243
242
  children: [item.icon, hasSubmenu && /* @__PURE__ */ jsx("span", {
244
243
  "aria-hidden": true,
245
244
  className: "size-4 -right-0.5 -top-0.5 font-semibold absolute flex items-center justify-center rounded-full border border-border-primary bg-level-four text-[9px] leading-none text-text-primary tabular-nums",
246
- "data-testid": `${testId}-submenu-count`,
247
245
  children: item.items?.length
248
246
  })]
249
247
  });
@@ -256,7 +254,6 @@ const RadialMenu = ({ ariaLabel, backLabel = "Back", className, dataTestId, item
256
254
  }) : circle, /* @__PURE__ */ jsx("span", {
257
255
  "aria-hidden": true,
258
256
  className: cn("w-20 font-medium leading-tight pointer-events-none absolute -translate-x-1/2 text-center text-[10px]", LABEL_SHADOW, "motion-safe:transition-[left,top,opacity] motion-safe:duration-300", BOUNCE_EASE, item.destructive === true ? "text-danger-300" : "text-text-primary"),
259
- "data-testid": `${testId}-label`,
260
257
  style: {
261
258
  left: x,
262
259
  opacity,
@@ -268,7 +265,6 @@ const RadialMenu = ({ ariaLabel, backLabel = "Back", className, dataTestId, item
268
265
  };
269
266
  return createPortal(/* @__PURE__ */ jsx("div", {
270
267
  className: "inset-0 fixed z-50 motion-safe:duration-150 motion-safe:animate-in motion-safe:fade-in-0",
271
- "data-testid": resolvedTestId,
272
268
  onContextMenu: (event) => {
273
269
  event.preventDefault();
274
270
  },
@@ -279,7 +275,6 @@ const RadialMenu = ({ ariaLabel, backLabel = "Back", className, dataTestId, item
279
275
  children: /* @__PURE__ */ jsxs("div", {
280
276
  "aria-label": ariaLabel,
281
277
  className: cn("focus-visible:ring-ring absolute origin-center focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none", className),
282
- "data-testid": `${resolvedTestId}-menu`,
283
278
  onKeyDown: handleKeyDown,
284
279
  ref: containerRef,
285
280
  role: "menu",
@@ -291,8 +286,7 @@ const RadialMenu = ({ ariaLabel, backLabel = "Back", className, dataTestId, item
291
286
  children: [
292
287
  !inSubmenu && /* @__PURE__ */ jsx("span", {
293
288
  "aria-hidden": true,
294
- className: "size-1.5 absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-text-secondary opacity-60",
295
- "data-testid": `${resolvedTestId}-center`
289
+ className: "size-1.5 absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-text-secondary opacity-60"
296
290
  }),
297
291
  backSkin !== null && renderSpoke({
298
292
  isActive: true,
@@ -1 +1 @@
1
- {"version":3,"file":"RadialMenu.js","names":[],"sources":["../src/components/RadialMenu/RadialMenu.tsx"],"sourcesContent":["import { ChevronDownIcon } from '@components/Icons'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@components/Tooltip/Tooltip'\nimport { cn } from '@utils/twUtils'\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState, type ReactNode, type Ref } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport interface RadialMenuItem {\n /** Optional longer description shown in a tooltip on hover, since labels are kept short. */\n description?: string\n destructive?: boolean\n disabled?: boolean\n icon: ReactNode\n id: string\n items?: RadialMenuItem[]\n label: string\n onSelect?: () => void\n}\n\nexport interface RadialMenuProps {\n ariaLabel: string\n backLabel?: string\n className?: string\n dataTestId?: string\n items: RadialMenuItem[]\n onClose: () => void\n position: { x: number; y: number } | null\n}\n\nconst BOUNCE_EASE = 'motion-safe:ease-[cubic-bezier(0.34,1.56,0.64,1)]'\n// Stacked soft shadows form a dense-but-soft halo so labels stay legible over any background.\nconst LABEL_SHADOW =\n '[text-shadow:0_0_5px_var(--color-text-inverted),0_0_5px_var(--color-text-inverted),0_0_10px_var(--color-text-inverted),0_0_10px_var(--color-text-inverted),0_0_16px_var(--color-text-inverted),0_0_16px_var(--color-text-inverted),0_0_22px_var(--color-text-inverted)]'\nconst CLOSE_MS = 260\nconst LEVEL_MS = 200\nconst LABEL_OFFSET = 30\nconst PRIMARY_RADIUS = 70\nconst SUB_RADIUS = 96\nconst ITEM_REACH = 44\nconst VIEWPORT_MARGIN = 16\n\n// When the menu is larger than the viewport (min > max), center it; otherwise clamp normally.\nconst clamp = (value: number, min: number, max: number) => (min > max ? (min + max) / 2 : Math.min(Math.max(value, min), max))\n\n// Double rAF so the collapsed \"from\" state paints before the transition target is applied.\nconst runAfterPaint = (callback: () => void) => {\n let inner = 0\n const outer = requestAnimationFrame(() => {\n inner = requestAnimationFrame(callback)\n })\n return () => {\n cancelAnimationFrame(outer)\n cancelAnimationFrame(inner)\n }\n}\n\nconst positionForIndex = (index: number, count: number, radius: number) => {\n const angle = -Math.PI / 2 + (index * 2 * Math.PI) / count\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius }\n}\n\n// Fanned across an arc centered straight up, so a sub-ring arches above its parent.\nconst SUB_ARC_CENTER = -Math.PI / 2\nconst SUB_ARC_SPAN = Math.PI * 0.7\nconst positionOnArc = (index: number, count: number, radius: number) => {\n const angle = count <= 1 ? SUB_ARC_CENTER : SUB_ARC_CENTER - SUB_ARC_SPAN / 2 + (index / (count - 1)) * SUB_ARC_SPAN\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius }\n}\n\nexport const RadialMenu = ({ ariaLabel, backLabel = 'Back', className, dataTestId, items, onClose, position, ref }: RadialMenuProps & { ref?: Ref<HTMLDivElement> }) => {\n const resolvedTestId = dataTestId ?? 'spectral-radial-menu'\n const containerRef = useRef<HTMLDivElement>(null)\n const levelTimersRef = useRef<number[]>([])\n const [activeSubmenuId, setActiveSubmenuId] = useState<string | null>(null)\n const [highlightIndex, setHighlightIndex] = useState(-1)\n // Items animate between `spreadOrigin` (collapsed) and `base + ring offset` (expanded).\n const [renderCenter, setRenderCenter] = useState(position)\n const [expanded, setExpanded] = useState(false)\n const [spreadOrigin, setSpreadOrigin] = useState<{ x: number; y: number }>({ x: 0, y: 0 })\n // Parent re-skinned as a back control, kept mounted so it swaps cleanly with the real item.\n const [backSkin, setBackSkin] = useState<{ item: RadialMenuItem; offset: { x: number; y: number } } | null>(null)\n // The returned item appears instantly at its spot (swapping in for the chevron) rather than fading.\n const [returningId, setReturningId] = useState<string | null>(null)\n\n const clearLevelTimers = useCallback(() => {\n levelTimersRef.current.forEach((id) => clearTimeout(id))\n levelTimersRef.current = []\n }, [])\n\n useEffect(() => {\n // Cancel any in-flight level-change timers so a stale callback can't mutate a new cycle.\n clearLevelTimers()\n if (position !== null) {\n setRenderCenter(position)\n setActiveSubmenuId(null)\n setHighlightIndex(-1)\n setSpreadOrigin({ x: 0, y: 0 })\n setBackSkin(null)\n setReturningId(null)\n setExpanded(false)\n return runAfterPaint(() => {\n setExpanded(true)\n containerRef.current?.focus()\n })\n }\n setExpanded(false)\n // Collapse to the center, not the lingering sub-ring origin, so close mirrors open.\n setSpreadOrigin({ x: 0, y: 0 })\n const timer = setTimeout(() => {\n setRenderCenter(null)\n setBackSkin(null)\n }, CLOSE_MS)\n return () => {\n clearTimeout(timer)\n }\n }, [clearLevelTimers, position])\n\n useEffect(() => {\n if (position === null) {\n return () => {}\n }\n window.addEventListener('scroll', onClose, true)\n window.addEventListener('resize', onClose)\n return () => {\n window.removeEventListener('scroll', onClose, true)\n window.removeEventListener('resize', onClose)\n }\n }, [position, onClose])\n\n const renderCenterRef = useRef(renderCenter)\n renderCenterRef.current = renderCenter\n useEffect(() => {\n if (renderCenterRef.current === null) {\n return () => {}\n }\n return runAfterPaint(() => {\n setExpanded(true)\n })\n }, [activeSubmenuId])\n\n useEffect(() => clearLevelTimers, [clearLevelTimers])\n\n const activeItems = useMemo(() => {\n if (activeSubmenuId === null) return items\n return items.find((item) => item.id === activeSubmenuId)?.items ?? items\n }, [activeSubmenuId, items])\n\n const activate = useCallback(\n (item: RadialMenuItem) => {\n if (item.disabled === true) return\n if (item.items && item.items.length > 0) {\n clearLevelTimers()\n const index = items.findIndex((candidate) => candidate.id === item.id)\n const offset = positionForIndex(index, items.length, PRIMARY_RADIUS)\n setSpreadOrigin(offset)\n setReturningId(null)\n setBackSkin({\n item: {\n ...item,\n description: undefined,\n icon: (\n <ChevronDownIcon\n className='rotate-90'\n size={20}\n />\n ),\n id: `${item.id}__back`,\n items: undefined,\n label: backLabel,\n },\n offset,\n })\n setActiveSubmenuId(item.id)\n setHighlightIndex(-1)\n setExpanded(false)\n return\n }\n item.onSelect?.()\n onClose()\n },\n [backLabel, clearLevelTimers, items, onClose],\n )\n\n const goBack = useCallback(() => {\n const returningSubmenuId = activeSubmenuId\n const index = items.findIndex((candidate) => candidate.id === returningSubmenuId)\n setSpreadOrigin(index >= 0 ? positionForIndex(index, items.length, PRIMARY_RADIUS) : { x: 0, y: 0 })\n setExpanded(false)\n setHighlightIndex(-1)\n clearLevelTimers()\n levelTimersRef.current = [\n window.setTimeout(() => {\n if (renderCenterRef.current === null) return\n setActiveSubmenuId(null)\n setBackSkin(null)\n setReturningId(returningSubmenuId)\n }, LEVEL_MS),\n window.setTimeout(() => {\n if (renderCenterRef.current !== null) {\n setReturningId(null)\n }\n }, LEVEL_MS + 350),\n ]\n }, [activeSubmenuId, clearLevelTimers, items])\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n const count = activeItems.length\n if (count === 0) return\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown': {\n event.preventDefault()\n setHighlightIndex((index) => (index < 0 ? 0 : (index + 1) % count))\n break\n }\n case 'ArrowLeft':\n case 'ArrowUp': {\n event.preventDefault()\n setHighlightIndex((index) => (index < 0 ? count - 1 : (index - 1 + count) % count))\n break\n }\n case 'Enter':\n case ' ': {\n event.preventDefault()\n const item = activeItems[highlightIndex]\n if (item) activate(item)\n break\n }\n case 'Escape': {\n event.preventDefault()\n if (activeSubmenuId === null) {\n onClose()\n } else {\n goBack()\n }\n break\n }\n case 'Tab': {\n event.preventDefault()\n break\n }\n default: {\n break\n }\n }\n },\n [activate, activeItems, activeSubmenuId, goBack, highlightIndex, onClose],\n )\n\n if (renderCenter === null || typeof document === 'undefined') {\n return null\n }\n\n const inSubmenu = activeSubmenuId !== null\n const parentIndex = inSubmenu ? items.findIndex((item) => item.id === activeSubmenuId) : -1\n const parentItem = parentIndex >= 0 ? (items[parentIndex] ?? null) : null\n const base = parentItem === null ? { x: 0, y: 0 } : positionForIndex(parentIndex, items.length, PRIMARY_RADIUS)\n const ringRadius = inSubmenu ? SUB_RADIUS : PRIMARY_RADIUS\n\n const reach = (inSubmenu ? PRIMARY_RADIUS + SUB_RADIUS : PRIMARY_RADIUS) + ITEM_REACH\n const cx = clamp(renderCenter.x, reach + VIEWPORT_MARGIN, window.innerWidth - reach - VIEWPORT_MARGIN)\n const cy = clamp(renderCenter.y, reach + VIEWPORT_MARGIN, window.innerHeight - reach - VIEWPORT_MARGIN)\n\n const renderSpoke = (options: { delay?: number; isActive?: boolean; isHighlighted?: boolean; item: RadialMenuItem; nonInteractive?: boolean; onClick: () => void; opacity: number; testId: string; x: number; y: number }) => {\n const { delay = 0, isActive = false, isHighlighted = false, item, nonInteractive = false, onClick, opacity, testId, x, y } = options\n const hasSubmenu = Boolean(item.items && item.items.length > 0)\n const circle = (\n <button\n aria-disabled={item.disabled}\n aria-haspopup={hasSubmenu ? 'menu' : undefined}\n aria-label={item.label}\n className={cn(\n 'size-12 absolute flex -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full',\n 'border border-border-primary bg-popover-bg text-popover-text shadow-elevation-3',\n 'hover:scale-110 hover:cursor-pointer hover:border-toggle-border hover:bg-level-three',\n 'motion-safe:transition-[left,top,opacity,transform] motion-safe:duration-300',\n BOUNCE_EASE,\n (isHighlighted || isActive) && 'border-toggle-border bg-level-three',\n isHighlighted && 'scale-110',\n item.destructive === true && 'text-danger-300',\n item.disabled === true && 'pointer-events-none hover:scale-100',\n nonInteractive && 'pointer-events-none',\n )}\n data-testid={testId}\n disabled={item.disabled}\n onClick={onClick}\n role='menuitem'\n style={{ left: x, opacity, top: y, transitionDelay: `${delay}ms` }}\n type='button'\n >\n {item.icon}\n {hasSubmenu && (\n <span\n aria-hidden\n className='size-4 -right-0.5 -top-0.5 font-semibold absolute flex items-center justify-center rounded-full border border-border-primary bg-level-four text-[9px] leading-none text-text-primary tabular-nums'\n data-testid={`${testId}-submenu-count`}\n >\n {item.items?.length}\n </span>\n )}\n </button>\n )\n return (\n <Fragment key={item.id}>\n {item.description ? (\n <Tooltip delayDuration={400}>\n <TooltipTrigger asChild>{circle}</TooltipTrigger>\n <TooltipContent>{item.description}</TooltipContent>\n </Tooltip>\n ) : (\n circle\n )}\n <span\n aria-hidden\n className={cn(\n 'w-20 font-medium leading-tight pointer-events-none absolute -translate-x-1/2 text-center text-[10px]',\n LABEL_SHADOW,\n 'motion-safe:transition-[left,top,opacity] motion-safe:duration-300',\n BOUNCE_EASE,\n item.destructive === true ? 'text-danger-300' : 'text-text-primary',\n )}\n data-testid={`${testId}-label`}\n style={{ left: x, opacity, top: y + LABEL_OFFSET, transitionDelay: `${delay}ms` }}\n >\n {item.label}\n </span>\n </Fragment>\n )\n }\n\n return createPortal(\n <div\n className='inset-0 fixed z-50 motion-safe:duration-150 motion-safe:animate-in motion-safe:fade-in-0'\n data-testid={resolvedTestId}\n onContextMenu={(event) => {\n event.preventDefault()\n }}\n onPointerDown={(event) => {\n if (event.target === event.currentTarget) {\n onClose()\n }\n }}\n ref={ref}\n >\n <div\n aria-label={ariaLabel}\n className={cn('focus-visible:ring-ring absolute origin-center focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none', className)}\n data-testid={`${resolvedTestId}-menu`}\n onKeyDown={handleKeyDown}\n ref={containerRef}\n role='menu'\n style={{ left: cx, top: cy }}\n tabIndex={-1}\n >\n {!inSubmenu && (\n <span\n aria-hidden\n className='size-1.5 absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-text-secondary opacity-60'\n data-testid={`${resolvedTestId}-center`}\n />\n )}\n {backSkin !== null &&\n renderSpoke({\n isActive: true,\n item: backSkin.item,\n nonInteractive: !inSubmenu,\n onClick: goBack,\n opacity: inSubmenu ? 1 : 0,\n testId: `${resolvedTestId}-back`,\n x: backSkin.offset.x,\n y: backSkin.offset.y,\n })}\n {activeItems.map((item, index) => {\n const offset = inSubmenu ? positionOnArc(index, activeItems.length, ringRadius) : positionForIndex(index, activeItems.length, ringRadius)\n const settled = expanded || item.id === returningId\n return renderSpoke({\n delay: item.id === returningId ? 0 : expanded ? index * 25 : 0,\n isHighlighted: index === highlightIndex,\n item,\n onClick: () => {\n activate(item)\n },\n opacity: settled ? (item.disabled === true ? 0.4 : 1) : 0,\n testId: `${resolvedTestId}-item`,\n x: settled ? base.x + offset.x : spreadOrigin.x,\n y: settled ? base.y + offset.y : spreadOrigin.y,\n })\n })}\n </div>\n </div>,\n document.body,\n )\n}\n\nRadialMenu.displayName = 'RadialMenu'\n"],"mappings":"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"}
1
+ {"version":3,"file":"RadialMenu.js","names":[],"sources":["../src/components/RadialMenu/RadialMenu.tsx"],"sourcesContent":["import { ChevronDownIcon } from '@components/Icons'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@components/Tooltip/Tooltip'\nimport { cn } from '@utils/twUtils'\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState, type ReactNode, type Ref } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport interface RadialMenuItem {\n /** Optional longer description shown in a tooltip on hover, since labels are kept short. */\n description?: string\n destructive?: boolean\n disabled?: boolean\n icon: ReactNode\n id: string\n items?: RadialMenuItem[]\n label: string\n onSelect?: () => void\n}\n\nexport interface RadialMenuProps {\n ariaLabel: string\n backLabel?: string\n className?: string\n dataTestId?: string\n items: RadialMenuItem[]\n onClose: () => void\n position: { x: number; y: number } | null\n}\n\nconst BOUNCE_EASE = 'motion-safe:ease-[cubic-bezier(0.34,1.56,0.64,1)]'\n// Stacked soft shadows form a dense-but-soft halo so labels stay legible over any background.\nconst LABEL_SHADOW =\n '[text-shadow:0_0_5px_var(--color-text-inverted),0_0_5px_var(--color-text-inverted),0_0_10px_var(--color-text-inverted),0_0_10px_var(--color-text-inverted),0_0_16px_var(--color-text-inverted),0_0_16px_var(--color-text-inverted),0_0_22px_var(--color-text-inverted)]'\nconst CLOSE_MS = 260\nconst LEVEL_MS = 200\nconst LABEL_OFFSET = 30\nconst PRIMARY_RADIUS = 70\nconst SUB_RADIUS = 96\nconst ITEM_REACH = 44\nconst VIEWPORT_MARGIN = 16\n\n// When the menu is larger than the viewport (min > max), center it; otherwise clamp normally.\nconst clamp = (value: number, min: number, max: number) => (min > max ? (min + max) / 2 : Math.min(Math.max(value, min), max))\n\n// Double rAF so the collapsed \"from\" state paints before the transition target is applied.\nconst runAfterPaint = (callback: () => void) => {\n let inner = 0\n const outer = requestAnimationFrame(() => {\n inner = requestAnimationFrame(callback)\n })\n return () => {\n cancelAnimationFrame(outer)\n cancelAnimationFrame(inner)\n }\n}\n\nconst positionForIndex = (index: number, count: number, radius: number) => {\n const angle = -Math.PI / 2 + (index * 2 * Math.PI) / count\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius }\n}\n\n// Fanned across an arc centered straight up, so a sub-ring arches above its parent.\nconst SUB_ARC_CENTER = -Math.PI / 2\nconst SUB_ARC_SPAN = Math.PI * 0.7\nconst positionOnArc = (index: number, count: number, radius: number) => {\n const angle = count <= 1 ? SUB_ARC_CENTER : SUB_ARC_CENTER - SUB_ARC_SPAN / 2 + (index / (count - 1)) * SUB_ARC_SPAN\n return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius }\n}\n\nexport const RadialMenu = ({ ariaLabel, backLabel = 'Back', className, dataTestId, items, onClose, position, ref }: RadialMenuProps & { ref?: Ref<HTMLDivElement> }) => {\n const resolvedTestId = dataTestId ?? 'spectral-radial-menu'\n const containerRef = useRef<HTMLDivElement>(null)\n const levelTimersRef = useRef<number[]>([])\n const [activeSubmenuId, setActiveSubmenuId] = useState<string | null>(null)\n const [highlightIndex, setHighlightIndex] = useState(-1)\n // Items animate between `spreadOrigin` (collapsed) and `base + ring offset` (expanded).\n const [renderCenter, setRenderCenter] = useState(position)\n const [expanded, setExpanded] = useState(false)\n const [spreadOrigin, setSpreadOrigin] = useState<{ x: number; y: number }>({ x: 0, y: 0 })\n // Parent re-skinned as a back control, kept mounted so it swaps cleanly with the real item.\n const [backSkin, setBackSkin] = useState<{ item: RadialMenuItem; offset: { x: number; y: number } } | null>(null)\n // The returned item appears instantly at its spot (swapping in for the chevron) rather than fading.\n const [returningId, setReturningId] = useState<string | null>(null)\n\n const clearLevelTimers = useCallback(() => {\n levelTimersRef.current.forEach((id) => clearTimeout(id))\n levelTimersRef.current = []\n }, [])\n\n useEffect(() => {\n // Cancel any in-flight level-change timers so a stale callback can't mutate a new cycle.\n clearLevelTimers()\n if (position !== null) {\n setRenderCenter(position)\n setActiveSubmenuId(null)\n setHighlightIndex(-1)\n setSpreadOrigin({ x: 0, y: 0 })\n setBackSkin(null)\n setReturningId(null)\n setExpanded(false)\n return runAfterPaint(() => {\n setExpanded(true)\n containerRef.current?.focus()\n })\n }\n setExpanded(false)\n // Collapse to the center, not the lingering sub-ring origin, so close mirrors open.\n setSpreadOrigin({ x: 0, y: 0 })\n const timer = setTimeout(() => {\n setRenderCenter(null)\n setBackSkin(null)\n }, CLOSE_MS)\n return () => {\n clearTimeout(timer)\n }\n }, [clearLevelTimers, position])\n\n useEffect(() => {\n if (position === null) {\n return () => {}\n }\n window.addEventListener('scroll', onClose, true)\n window.addEventListener('resize', onClose)\n return () => {\n window.removeEventListener('scroll', onClose, true)\n window.removeEventListener('resize', onClose)\n }\n }, [position, onClose])\n\n const renderCenterRef = useRef(renderCenter)\n renderCenterRef.current = renderCenter\n useEffect(() => {\n if (renderCenterRef.current === null) {\n return () => {}\n }\n return runAfterPaint(() => {\n setExpanded(true)\n })\n }, [activeSubmenuId])\n\n useEffect(() => clearLevelTimers, [clearLevelTimers])\n\n const activeItems = useMemo(() => {\n if (activeSubmenuId === null) return items\n return items.find((item) => item.id === activeSubmenuId)?.items ?? items\n }, [activeSubmenuId, items])\n\n const activate = useCallback(\n (item: RadialMenuItem) => {\n if (item.disabled === true) return\n if (item.items && item.items.length > 0) {\n clearLevelTimers()\n const index = items.findIndex((candidate) => candidate.id === item.id)\n const offset = positionForIndex(index, items.length, PRIMARY_RADIUS)\n setSpreadOrigin(offset)\n setReturningId(null)\n setBackSkin({\n item: {\n ...item,\n description: undefined,\n icon: (\n <ChevronDownIcon\n className='rotate-90'\n size={20}\n />\n ),\n id: `${item.id}__back`,\n items: undefined,\n label: backLabel,\n },\n offset,\n })\n setActiveSubmenuId(item.id)\n setHighlightIndex(-1)\n setExpanded(false)\n return\n }\n item.onSelect?.()\n onClose()\n },\n [backLabel, clearLevelTimers, items, onClose],\n )\n\n const goBack = useCallback(() => {\n const returningSubmenuId = activeSubmenuId\n const index = items.findIndex((candidate) => candidate.id === returningSubmenuId)\n setSpreadOrigin(index >= 0 ? positionForIndex(index, items.length, PRIMARY_RADIUS) : { x: 0, y: 0 })\n setExpanded(false)\n setHighlightIndex(-1)\n clearLevelTimers()\n levelTimersRef.current = [\n window.setTimeout(() => {\n if (renderCenterRef.current === null) return\n setActiveSubmenuId(null)\n setBackSkin(null)\n setReturningId(returningSubmenuId)\n }, LEVEL_MS),\n window.setTimeout(() => {\n if (renderCenterRef.current !== null) {\n setReturningId(null)\n }\n }, LEVEL_MS + 350),\n ]\n }, [activeSubmenuId, clearLevelTimers, items])\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n const count = activeItems.length\n if (count === 0) return\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown': {\n event.preventDefault()\n setHighlightIndex((index) => (index < 0 ? 0 : (index + 1) % count))\n break\n }\n case 'ArrowLeft':\n case 'ArrowUp': {\n event.preventDefault()\n setHighlightIndex((index) => (index < 0 ? count - 1 : (index - 1 + count) % count))\n break\n }\n case 'Enter':\n case ' ': {\n event.preventDefault()\n const item = activeItems[highlightIndex]\n if (item) activate(item)\n break\n }\n case 'Escape': {\n event.preventDefault()\n if (activeSubmenuId === null) {\n onClose()\n } else {\n goBack()\n }\n break\n }\n case 'Tab': {\n event.preventDefault()\n break\n }\n default: {\n break\n }\n }\n },\n [activate, activeItems, activeSubmenuId, goBack, highlightIndex, onClose],\n )\n\n if (renderCenter === null || typeof document === 'undefined') {\n return null\n }\n\n const inSubmenu = activeSubmenuId !== null\n const parentIndex = inSubmenu ? items.findIndex((item) => item.id === activeSubmenuId) : -1\n const parentItem = parentIndex >= 0 ? (items[parentIndex] ?? null) : null\n const base = parentItem === null ? { x: 0, y: 0 } : positionForIndex(parentIndex, items.length, PRIMARY_RADIUS)\n const ringRadius = inSubmenu ? SUB_RADIUS : PRIMARY_RADIUS\n\n const reach = (inSubmenu ? PRIMARY_RADIUS + SUB_RADIUS : PRIMARY_RADIUS) + ITEM_REACH\n const cx = clamp(renderCenter.x, reach + VIEWPORT_MARGIN, window.innerWidth - reach - VIEWPORT_MARGIN)\n const cy = clamp(renderCenter.y, reach + VIEWPORT_MARGIN, window.innerHeight - reach - VIEWPORT_MARGIN)\n\n const renderSpoke = (options: { delay?: number; isActive?: boolean; isHighlighted?: boolean; item: RadialMenuItem; nonInteractive?: boolean; onClick: () => void; opacity: number; testId: string; x: number; y: number }) => {\n const { delay = 0, isActive = false, isHighlighted = false, item, nonInteractive = false, onClick, opacity, testId, x, y } = options\n const hasSubmenu = Boolean(item.items && item.items.length > 0)\n const circle = (\n <button\n aria-disabled={item.disabled}\n aria-haspopup={hasSubmenu ? 'menu' : undefined}\n aria-label={item.label}\n className={cn(\n 'size-12 absolute flex -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full',\n 'border border-border-primary bg-popover-bg text-popover-text shadow-elevation-3',\n 'hover:scale-110 hover:cursor-pointer hover:border-toggle-border hover:bg-level-three',\n 'motion-safe:transition-[left,top,opacity,transform] motion-safe:duration-300',\n BOUNCE_EASE,\n (isHighlighted || isActive) && 'border-toggle-border bg-level-three',\n isHighlighted && 'scale-110',\n item.destructive === true && 'text-danger-300',\n item.disabled === true && 'pointer-events-none hover:scale-100',\n nonInteractive && 'pointer-events-none',\n )}\n data-testid={testId}\n disabled={item.disabled}\n onClick={onClick}\n role='menuitem'\n style={{ left: x, opacity, top: y, transitionDelay: `${delay}ms` }}\n type='button'\n >\n {item.icon}\n {hasSubmenu && (\n <span\n aria-hidden\n className='size-4 -right-0.5 -top-0.5 font-semibold absolute flex items-center justify-center rounded-full border border-border-primary bg-level-four text-[9px] leading-none text-text-primary tabular-nums'\n data-testid={`${testId}-submenu-count`}\n >\n {item.items?.length}\n </span>\n )}\n </button>\n )\n return (\n <Fragment key={item.id}>\n {item.description ? (\n <Tooltip delayDuration={400}>\n <TooltipTrigger asChild>{circle}</TooltipTrigger>\n <TooltipContent>{item.description}</TooltipContent>\n </Tooltip>\n ) : (\n circle\n )}\n <span\n aria-hidden\n className={cn(\n 'w-20 font-medium leading-tight pointer-events-none absolute -translate-x-1/2 text-center text-[10px]',\n LABEL_SHADOW,\n 'motion-safe:transition-[left,top,opacity] motion-safe:duration-300',\n BOUNCE_EASE,\n item.destructive === true ? 'text-danger-300' : 'text-text-primary',\n )}\n data-testid={`${testId}-label`}\n style={{ left: x, opacity, top: y + LABEL_OFFSET, transitionDelay: `${delay}ms` }}\n >\n {item.label}\n </span>\n </Fragment>\n )\n }\n\n return createPortal(\n <div\n className='inset-0 fixed z-50 motion-safe:duration-150 motion-safe:animate-in motion-safe:fade-in-0'\n data-testid={resolvedTestId}\n onContextMenu={(event) => {\n event.preventDefault()\n }}\n onPointerDown={(event) => {\n if (event.target === event.currentTarget) {\n onClose()\n }\n }}\n ref={ref}\n >\n <div\n aria-label={ariaLabel}\n className={cn('focus-visible:ring-ring absolute origin-center focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none', className)}\n data-testid={`${resolvedTestId}-menu`}\n onKeyDown={handleKeyDown}\n ref={containerRef}\n role='menu'\n style={{ left: cx, top: cy }}\n tabIndex={-1}\n >\n {!inSubmenu && (\n <span\n aria-hidden\n className='size-1.5 absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-text-secondary opacity-60'\n data-testid={`${resolvedTestId}-center`}\n />\n )}\n {backSkin !== null &&\n renderSpoke({\n isActive: true,\n item: backSkin.item,\n nonInteractive: !inSubmenu,\n onClick: goBack,\n opacity: inSubmenu ? 1 : 0,\n testId: `${resolvedTestId}-back`,\n x: backSkin.offset.x,\n y: backSkin.offset.y,\n })}\n {activeItems.map((item, index) => {\n const offset = inSubmenu ? positionOnArc(index, activeItems.length, ringRadius) : positionForIndex(index, activeItems.length, ringRadius)\n const settled = expanded || item.id === returningId\n return renderSpoke({\n delay: item.id === returningId ? 0 : expanded ? index * 25 : 0,\n isHighlighted: index === highlightIndex,\n item,\n onClick: () => {\n activate(item)\n },\n opacity: settled ? (item.disabled === true ? 0.4 : 1) : 0,\n testId: `${resolvedTestId}-item`,\n x: settled ? base.x + offset.x : spreadOrigin.x,\n y: settled ? base.y + offset.y : spreadOrigin.y,\n })\n })}\n </div>\n </div>,\n document.body,\n )\n}\n\nRadialMenu.displayName = 'RadialMenu'\n"],"mappings":";;;;;;;;;AA4BA,MAAM,cAAc;AAEpB,MAAM,eACJ;AACF,MAAM,WAAW;AACjB,MAAM,WAAW;AACjB,MAAM,eAAe;AACrB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AAGxB,MAAM,SAAS,OAAe,KAAa,QAAiB,MAAM,OAAO,MAAM,OAAO,IAAI,KAAK,IAAI,KAAK,IAAI,OAAO,IAAI,EAAE,IAAI;AAG7H,MAAM,iBAAiB,aAAyB;CAC9C,IAAI,QAAQ;CACZ,MAAM,QAAQ,4BAA4B;AACxC,UAAQ,sBAAsB,SAAQ;GACvC;AACD,cAAa;AACX,uBAAqB,MAAK;AAC1B,uBAAqB,MAAK;;;AAI9B,MAAM,oBAAoB,OAAe,OAAe,WAAmB;CACzE,MAAM,QAAQ,CAAC,KAAK,KAAK,IAAK,QAAQ,IAAI,KAAK,KAAM;AACrD,QAAO;EAAE,GAAG,KAAK,IAAI,MAAM,GAAG;EAAQ,GAAG,KAAK,IAAI,MAAM,GAAG;EAAO;;AAIpE,MAAM,iBAAiB,CAAC,KAAK,KAAK;AAClC,MAAM,eAAe,KAAK,KAAK;AAC/B,MAAM,iBAAiB,OAAe,OAAe,WAAmB;CACtE,MAAM,QAAQ,SAAS,IAAI,iBAAiB,iBAAiB,eAAe,IAAK,SAAS,QAAQ,KAAM;AACxG,QAAO;EAAE,GAAG,KAAK,IAAI,MAAM,GAAG;EAAQ,GAAG,KAAK,IAAI,MAAM,GAAG;EAAO;;AAGpE,MAAa,cAAc,EAAE,WAAW,YAAY,QAAQ,WAAW,YAAY,OAAO,SAAS,UAAU,UAA2D;CACtK,MAAM,iBAAiB,cAAc;CACrC,MAAM,eAAe,OAAuB,KAAI;CAChD,MAAM,iBAAiB,OAAiB,EAAE,CAAA;CAC1C,MAAM,CAAC,iBAAiB,sBAAsB,SAAwB,KAAI;CAC1E,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,GAAE;CAEvD,MAAM,CAAC,cAAc,mBAAmB,SAAS,SAAQ;CACzD,MAAM,CAAC,UAAU,eAAe,SAAS,MAAK;CAC9C,MAAM,CAAC,cAAc,mBAAmB,SAAmC;EAAE,GAAG;EAAG,GAAG;EAAG,CAAA;CAEzF,MAAM,CAAC,UAAU,eAAe,SAA4E,KAAI;CAEhH,MAAM,CAAC,aAAa,kBAAkB,SAAwB,KAAI;CAElE,MAAM,mBAAmB,kBAAkB;AACzC,iBAAe,QAAQ,SAAS,OAAO,aAAa,GAAG,CAAA;AACvD,iBAAe,UAAU,EAAC;IACzB,EAAE,CAAA;AAEL,iBAAgB;AAEd,oBAAiB;AACjB,MAAI,aAAa,MAAM;AACrB,mBAAgB,SAAQ;AACxB,sBAAmB,KAAI;AACvB,qBAAkB,GAAE;AACpB,mBAAgB;IAAE,GAAG;IAAG,GAAG;IAAG,CAAA;AAC9B,eAAY,KAAI;AAChB,kBAAe,KAAI;AACnB,eAAY,MAAK;AACjB,UAAO,oBAAoB;AACzB,gBAAY,KAAI;AAChB,iBAAa,SAAS,OAAM;KAC7B;;AAEH,cAAY,MAAK;AAEjB,kBAAgB;GAAE,GAAG;GAAG,GAAG;GAAG,CAAA;EAC9B,MAAM,QAAQ,iBAAiB;AAC7B,mBAAgB,KAAI;AACpB,eAAY,KAAI;KACf,SAAQ;AACX,eAAa;AACX,gBAAa,MAAK;;IAEnB,CAAC,kBAAkB,SAAS,CAAA;AAE/B,iBAAgB;AACd,MAAI,aAAa,KACf,cAAa;AAEf,SAAO,iBAAiB,UAAU,SAAS,KAAI;AAC/C,SAAO,iBAAiB,UAAU,QAAO;AACzC,eAAa;AACX,UAAO,oBAAoB,UAAU,SAAS,KAAI;AAClD,UAAO,oBAAoB,UAAU,QAAO;;IAE7C,CAAC,UAAU,QAAQ,CAAA;CAEtB,MAAM,kBAAkB,OAAO,aAAY;AAC3C,iBAAgB,UAAU;AAC1B,iBAAgB;AACd,MAAI,gBAAgB,YAAY,KAC9B,cAAa;AAEf,SAAO,oBAAoB;AACzB,eAAY,KAAI;IACjB;IACA,CAAC,gBAAgB,CAAA;AAEpB,iBAAgB,kBAAkB,CAAC,iBAAiB,CAAA;CAEpD,MAAM,cAAc,cAAc;AAChC,MAAI,oBAAoB,KAAM,QAAO;AACrC,SAAO,MAAM,MAAM,SAAS,KAAK,OAAO,gBAAgB,EAAE,SAAS;IAClE,CAAC,iBAAiB,MAAM,CAAA;CAE3B,MAAM,WAAW,aACd,SAAyB;AACxB,MAAI,KAAK,aAAa,KAAM;AAC5B,MAAI,KAAK,SAAS,KAAK,MAAM,SAAS,GAAG;AACvC,qBAAiB;GAEjB,MAAM,SAAS,iBADD,MAAM,WAAW,cAAc,UAAU,OAAO,KAAK,GAC9B,EAAE,MAAM,QAAQ,eAAc;AACnE,mBAAgB,OAAM;AACtB,kBAAe,KAAI;AACnB,eAAY;IACV,MAAM;KACJ,GAAG;KACH,aAAa;KACb,MACE,oBAAC,iBAAD;MACE,WAAU;MACV,MAAM;MACP;KAEH,IAAI,GAAG,KAAK,GAAG;KACf,OAAO;KACP,OAAO;KACR;IACD;IACD,CAAA;AACD,sBAAmB,KAAK,GAAE;AAC1B,qBAAkB,GAAE;AACpB,eAAY,MAAK;AACjB;;AAEF,OAAK,YAAW;AAChB,WAAQ;IAEV;EAAC;EAAW;EAAkB;EAAO;EAAQ,CAC/C;CAEA,MAAM,SAAS,kBAAkB;EAC/B,MAAM,qBAAqB;EAC3B,MAAM,QAAQ,MAAM,WAAW,cAAc,UAAU,OAAO,mBAAkB;AAChF,kBAAgB,SAAS,IAAI,iBAAiB,OAAO,MAAM,QAAQ,eAAe,GAAG;GAAE,GAAG;GAAG,GAAG;GAAG,CAAA;AACnG,cAAY,MAAK;AACjB,oBAAkB,GAAE;AACpB,oBAAiB;AACjB,iBAAe,UAAU,CACvB,OAAO,iBAAiB;AACtB,OAAI,gBAAgB,YAAY,KAAM;AACtC,sBAAmB,KAAI;AACvB,eAAY,KAAI;AAChB,kBAAe,mBAAkB;KAChC,SAAS,EACZ,OAAO,iBAAiB;AACtB,OAAI,gBAAgB,YAAY,KAC9B,gBAAe,KAAI;KAEpB,WAAW,IAAI,CACpB;IACC;EAAC;EAAiB;EAAkB;EAAM,CAAA;CAE7C,MAAM,gBAAgB,aACnB,UAA+C;EAC9C,MAAM,QAAQ,YAAY;AAC1B,MAAI,UAAU,EAAG;AAEjB,UAAQ,MAAM,KAAd;GACE,KAAK;GACL,KAAK;AACH,UAAM,gBAAe;AACrB,uBAAmB,UAAW,QAAQ,IAAI,KAAK,QAAQ,KAAK,MAAM;AAClE;GAEF,KAAK;GACL,KAAK;AACH,UAAM,gBAAe;AACrB,uBAAmB,UAAW,QAAQ,IAAI,QAAQ,KAAK,QAAQ,IAAI,SAAS,MAAM;AAClF;GAEF,KAAK;GACL,KAAK,KAAK;AACR,UAAM,gBAAe;IACrB,MAAM,OAAO,YAAY;AACzB,QAAI,KAAM,UAAS,KAAI;AACvB;;GAEF,KAAK;AACH,UAAM,gBAAe;AACrB,QAAI,oBAAoB,KACtB,UAAQ;QAER,SAAO;AAET;GAEF,KAAK;AACH,UAAM,gBAAe;AACrB;GAEF,QACE;;IAIN;EAAC;EAAU;EAAa;EAAiB;EAAQ;EAAgB;EAAQ,CAC3E;AAEA,KAAI,iBAAiB,QAAQ,OAAO,aAAa,YAC/C,QAAO;CAGT,MAAM,YAAY,oBAAoB;CACtC,MAAM,cAAc,YAAY,MAAM,WAAW,SAAS,KAAK,OAAO,gBAAgB,GAAG;CAEzF,MAAM,QADa,eAAe,IAAK,MAAM,gBAAgB,OAAQ,UACzC,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,GAAG,iBAAiB,aAAa,MAAM,QAAQ,eAAc;CAC9G,MAAM,aAAa,YAAY,aAAa;CAE5C,MAAM,SAAS,YAAY,iBAAiB,aAAa,kBAAkB;CAC3E,MAAM,KAAK,MAAM,aAAa,GAAG,QAAQ,iBAAiB,OAAO,aAAa,QAAQ,gBAAe;CACrG,MAAM,KAAK,MAAM,aAAa,GAAG,QAAQ,iBAAiB,OAAO,cAAc,QAAQ,gBAAe;CAEtG,MAAM,eAAe,YAAyM;EAC5N,MAAM,EAAE,QAAQ,GAAG,WAAW,OAAO,gBAAgB,OAAO,MAAM,iBAAiB,OAAO,SAAS,SAAS,QAAQ,GAAG,MAAM;EAC7H,MAAM,aAAa,QAAQ,KAAK,SAAS,KAAK,MAAM,SAAS,EAAC;EAC9D,MAAM,SACJ,qBAAC,UAAD;GACE,iBAAe,KAAK;GACpB,iBAAe,aAAa,SAAS;GACrC,cAAY,KAAK;GACjB,WAAW,GACT,oGACA,mFACA,wFACA,gFACA,cACC,iBAAiB,aAAa,uCAC/B,iBAAiB,aACjB,KAAK,gBAAgB,QAAQ,mBAC7B,KAAK,aAAa,QAAQ,uCAC1B,kBAAkB,sBACnB;GAED,UAAU,KAAK;GACN;GACT,MAAK;GACL,OAAO;IAAE,MAAM;IAAG;IAAS,KAAK;IAAG,iBAAiB,GAAG,MAAM;IAAK;GAClE,MAAK;aArBP,CAuBG,KAAK,MACL,cACC,oBAAC,QAAD;IACE;IACA,WAAU;cAGT,KAAK,OAAO;IACT,EAEF;;AAEV,SACE,qBAAC,UAAD,aACG,KAAK,cACJ,qBAAC,SAAD;GAAS,eAAe;aAAxB,CACE,oBAAC,gBAAD;IAAgB;cAAS;IAAuB,GAChD,oBAAC,gBAAD,YAAiB,KAAK,aAA4B,EAC3C;OAET,QAEF,oBAAC,QAAD;GACE;GACA,WAAW,GACT,wGACA,cACA,sEACA,aACA,KAAK,gBAAgB,OAAO,oBAAoB,oBACjD;GAED,OAAO;IAAE,MAAM;IAAG;IAAS,KAAK,IAAI;IAAc,iBAAiB,GAAG,MAAM;IAAK;aAEhF,KAAK;GACF,EACE,IAvBK,KAAK,GAuBV;;AAId,QAAO,aACL,oBAAC,OAAD;EACE,WAAU;EAEV,gBAAgB,UAAU;AACxB,SAAM,gBAAe;;EAEvB,gBAAgB,UAAU;AACxB,OAAI,MAAM,WAAW,MAAM,cACzB,UAAQ;;EAGP;YAEL,qBAAC,OAAD;GACE,cAAY;GACZ,WAAW,GAAG,8HAA8H,UAAU;GAEtJ,WAAW;GACX,KAAK;GACL,MAAK;GACL,OAAO;IAAE,MAAM;IAAI,KAAK;IAAI;GAC5B,UAAU;aARZ;IAUG,CAAC,aACA,oBAAC,QAAD;KACE;KACA,WAAU;KAEX;IAEF,aAAa,QACZ,YAAY;KACV,UAAU;KACV,MAAM,SAAS;KACf,gBAAgB,CAAC;KACjB,SAAS;KACT,SAAS,YAAY,IAAI;KACzB,QAAQ,GAAG,eAAe;KAC1B,GAAG,SAAS,OAAO;KACnB,GAAG,SAAS,OAAO;KACpB,CAAC;IACH,YAAY,KAAK,MAAM,UAAU;KAChC,MAAM,SAAS,YAAY,cAAc,OAAO,YAAY,QAAQ,WAAW,GAAG,iBAAiB,OAAO,YAAY,QAAQ,WAAU;KACxI,MAAM,UAAU,YAAY,KAAK,OAAO;AACxC,YAAO,YAAY;MACjB,OAAO,KAAK,OAAO,cAAc,IAAI,WAAW,QAAQ,KAAK;MAC7D,eAAe,UAAU;MACzB;MACA,eAAe;AACb,gBAAS,KAAI;;MAEf,SAAS,UAAW,KAAK,aAAa,OAAO,KAAM,IAAK;MACxD,QAAQ,GAAG,eAAe;MAC1B,GAAG,UAAU,KAAK,IAAI,OAAO,IAAI,aAAa;MAC9C,GAAG,UAAU,KAAK,IAAI,OAAO,IAAI,aAAa;MAC/C,CAAA;MACD;IACC;;EACD,GACN,SAAS,KACX;;AAGF,WAAW,cAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"RadioButtonGroupBase.d.ts","names":[],"sources":["../../src/components/RadioButtonGroup/RadioButtonGroupBase.tsx"],"mappings":";;;;;;;UAuCiB,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,GAA+P,qBAAA,KAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA;EAgHnT,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
+ {"version":3,"file":"RadioButtonGroupBase.d.ts","names":[],"sources":["../../src/components/RadioButtonGroup/RadioButtonGroupBase.tsx"],"mappings":";;;;;;;UAuCiB,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,GAA+P,qBAAA,KAAqB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA;EA+GnT,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"}
@@ -94,12 +94,9 @@ const RadioButtonGroupBase = ({ "aria-label": ariaLabel, "aria-labelledby": aria
94
94
  children: /* @__PURE__ */ jsx("div", {
95
95
  "aria-label": ariaLabel,
96
96
  "aria-labelledby": ariaLabelledby,
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)]
98
- data-[variant=divided]:gap-0 data-[variant=divided]:[--color-toggle-border:var(--color-toggle-outline-border)] data-[variant=divided]:[&_button:not(:last-of-type)]:[border-right-color:var(--color-toggle-outline-divider)]
99
- data-[variant=divided]:[&_button]:border-y-0 data-[variant=divided]:[&_button:first-of-type]:border-l-0 data-[variant=divided]:[&_button:last-of-type]:border-r-0`, className),
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)]", "data-[variant=divided]:gap-0 data-[variant=divided]:[--color-toggle-border:var(--color-toggle-outline-border)] data-[variant=divided]:[&_button]:border-y-0", "data-[variant=divided]:[&_button:not(:last-of-type)]:[border-right-color:var(--color-toggle-outline-divider)] data-[variant=divided]:[&_button:first-of-type]:border-l-0 data-[variant=divided]:[&_button:last-of-type]:border-r-0", className),
100
98
  "data-expanded": expanded,
101
99
  "data-orientation": orientation,
102
- "data-testid": "spectral-radio-button-group",
103
100
  "data-variant": variant,
104
101
  "aria-orientation": orientation,
105
102
  role: "radiogroup",