pixelize-design-library 2.4.2-beta.46 → 2.4.2-beta.47

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 (37) hide show
  1. package/CHANGELOG.md +23 -3
  2. package/COMPONENT-GOTCHAS.md +37 -1
  3. package/dist/Components/Dropdown/DropDown.js +10 -8
  4. package/dist/Components/SearchSelect/SearchSelect.js +20 -15
  5. package/dist/Components/SearchSelect/searchSelectSize.d.ts +0 -5
  6. package/dist/Components/SearchSelect/searchSelectSize.js +0 -12
  7. package/dist/Components/Select/SelectPopup.js +15 -15
  8. package/dist/Components/Select/optionRowInk.d.ts +4 -0
  9. package/dist/Components/Select/optionRowInk.js +12 -1
  10. package/dist/Components/SelectSearch/SelectSearch.js +8 -4
  11. package/dist/Components/Switch/Switch.d.ts +1 -1
  12. package/dist/Components/Switch/Switch.js +4 -2
  13. package/dist/Components/Table/filters/CompactSelect.js +6 -3
  14. package/dist/Theme/chakra/Menu.styles.d.ts +6 -0
  15. package/dist/Theme/chakra/Menu.styles.js +8 -0
  16. package/dist/Theme/chakra/componentStyles.d.ts +6 -0
  17. package/dist/Theme/tokens/builders/outlineRung.d.ts +2 -0
  18. package/dist/Theme/tokens/builders/outlineRung.js +14 -1
  19. package/dist/Theme/tokens/builders/rowStates.js +3 -4
  20. package/dist/Theme/tokens/primitives/optionRowSizes.d.ts +20 -0
  21. package/dist/Theme/tokens/primitives/optionRowSizes.js +29 -0
  22. package/dist/esm/Components/Dropdown/DropDown.js +10 -8
  23. package/dist/esm/Components/SearchSelect/SearchSelect.js +20 -15
  24. package/dist/esm/Components/SearchSelect/searchSelectSize.js +0 -12
  25. package/dist/esm/Components/Select/SelectPopup.js +16 -16
  26. package/dist/esm/Components/Select/optionRowInk.js +9 -0
  27. package/dist/esm/Components/SelectSearch/SelectSearch.js +9 -5
  28. package/dist/esm/Components/Switch/Switch.js +4 -2
  29. package/dist/esm/Components/Table/filters/CompactSelect.js +7 -4
  30. package/dist/esm/Theme/chakra/Menu.styles.js +8 -0
  31. package/dist/esm/Theme/tokens/builders/outlineRung.js +12 -0
  32. package/dist/esm/Theme/tokens/builders/rowStates.js +4 -5
  33. package/dist/esm/Theme/tokens/primitives/optionRowSizes.js +25 -0
  34. package/dist/esm/index.js +2 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +9 -4
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -70,9 +70,29 @@ next major.
70
70
  on a `primary[700]` bar was unreadable. The track is now an opaque plate on every backdrop: rest
71
71
  ink 7.58–18.39:1, plate-vs-bar 6.31–19.0:1, with no change required at the call site. `surface`
72
72
  is now only a refinement for the plate's hairline edge, and a test gates the invariant.
73
- - **A selected `Select` option stacked brand ink on a brand-tinted row.** Bold `accentText` on
74
- `listRowBackgrounds().selected` read as muddy, and the row cleared the panel by only 1.16–1.29:1.
75
- The label is now `text[900]` with the brand carried by a trailing check, matching `OrgSwitcher`.
73
+ - **A selected `Select`/`SearchSelect`/`SelectSearch`/`CompactSelect` option stacked brand ink on a
74
+ brand-tinted row** — `CompactSelect` had no selected styling at all. The label is now `text[900]`
75
+ at weight 500 with the brand carried by a trailing check, matching `OrgSwitcher`. The row tint
76
+ itself is also settled to one perceptible step off the panel instead of taking the raw token
77
+ (`selected` 1.16–1.29:1 → 1.151–1.191:1; the pressed-and-selected state above it 1.34–1.52:1 →
78
+ 1.259–1.340:1), so a too-saturated tint is pulled down as reliably as a too-faint one is lifted.
79
+ - **Every dropdown row is now one size.** `Select`, `SearchSelect`, `SelectSearch`,
80
+ `CompactSelect`'s filter, `DropDown`'s menu items and every themed `Menu` item shared six
81
+ different paddings for the same row; `sm` is now 30px tall everywhere (`padding: 4px 10px`,
82
+ `font-size: 13px`) instead of `SearchSelect` alone rendering ~34px with a 16px font. Row
83
+ content — including a `SearchSelect` `option.view` or `Select` `renderOption` node — now inherits
84
+ its font size from the row, so consumer-rendered row content is no longer stuck at the page's
85
+ 16px body font.
86
+ - **A dropdown row's description sub-line fell under AA in dark mode.** Settling the selected rung
87
+ left `textMuted` at **4.00–4.22:1** on the hovered-selected row across 6 of 9 brands (dark
88
+ `surfaceRaised` carries only ~0.86 of headroom, so any lighter rung eats it). The sub-line ink is
89
+ now derived once per palette against that busiest rung, clearing 4.5:1 everywhere; light mode is
90
+ unchanged because it already cleared.
91
+ - **`Switch` no longer forces a 200px-wide track when it has no labels.** The width floor now only
92
+ applies once a label is set, so an unlabelled instance — the wrong component for a boolean field,
93
+ but one that shipped live in a 120px table cell — shrinks to fit instead of overflowing by 80px.
94
+ Use `ToggleSwitch` for an on/off field; swapping it in trades `role="radiogroup"` for
95
+ `role="switch"`, the same `getByRole` break the `InputSwitch` → `ToggleSwitch` migration caused.
76
96
 
77
97
  - **`SegmentedControl`'s pill bulged past the plate's corners.** The pill and the segments carried
78
98
  the track's own `lg` radius while sitting 2px inside it, so the plate's band stayed even along the
@@ -127,7 +127,7 @@ is now the library's own themed component, with a real dual-thumb mode and a `ma
127
127
  | One of 2-5 named options | `SegmentedControl` | `role="radiogroup"`, keyboard-navigable, supports per-option disabling |
128
128
  | Switching between views or panels | `Tabs` | `role="tablist"` + `aria-controls`, count badges, lazy panels, vertical orientation |
129
129
  | *(avoid)* | `InputSwitch` | `@deprecated` — now a thin wrapper that renders `ToggleSwitch` |
130
- | *(avoid)* | `Switch` | `@deprecated` — a mouse-only two-label segmented control (`leftText`/`rightText`); use `SegmentedControl` |
130
+ | *(avoid)* | `Switch` | `@deprecated` — a two-label segmented control (`leftText`/`rightText`), not a boolean; for a single on/off field use `ToggleSwitch` instead |
131
131
 
132
132
  **Apply:** "on vs off" is always `ToggleSwitch`; 2+ named options is always `SegmentedControl`.
133
133
 
@@ -1343,3 +1343,39 @@ Not just the columns — the select/edit/create header bar disappears too, since
1343
1343
  returns `null` before rendering anything.
1344
1344
 
1345
1345
  **Apply:** there's no prop for hiding only the board body while keeping header actions visible.
1346
+
1347
+ ### `Switch` was forced to 200px even with no labels — it overflowed a narrow cell
1348
+
1349
+ `Switch` (`leftText`/`rightText`) kept a 12.5rem minimum width regardless of whether any label was
1350
+ set. Used as a boolean toggle — the wrong component for that, but the mistake shipped live in
1351
+ tickets' project custom-fields page — an unlabelled instance rendered as two unlabelled brand-filled
1352
+ pills at 200px inside a 120px table cell, overflowing by 80px. The width floor now only applies once
1353
+ `leftText`/`rightText` is actually set; an unlabelled instance shrinks to fit (~58px measured).
1354
+
1355
+ **Apply:** for a single on/off field use `ToggleSwitch`, not `Switch`, even now that it no longer
1356
+ overflows — a two-option segmented control still announces as `role="radiogroup"`, not
1357
+ `role="switch"`. Swapping `Switch` for `ToggleSwitch` changes `getByRole("radio", …)` queries to
1358
+ `getByRole("switch", …)` — the same test break the `InputSwitch` → `ToggleSwitch` migration caused.
1359
+
1360
+ ### A `SearchSelect`/`Select` custom row node inherits the row's font size and padding — don't restate them
1361
+
1362
+ `SearchSelect`'s `option.view` and `Select`'s `renderOption` render straight inside the option row,
1363
+ which now carries its own `fontSize`/`px`/`py` so a plain-text row is legible with no extra markup.
1364
+ A custom node that also sets its own `px`/`py`/`fontSize` stacks on top of the row's rather than
1365
+ replacing it — this is what made rows in crm-frontend render 57–75px tall instead of the ~30px every
1366
+ other row uses.
1367
+
1368
+ **Apply:** size a custom `option.view`/`renderOption` node yourself only when it needs something
1369
+ *different* from the row default — inherit by default, don't restate it.
1370
+
1371
+ ### A custom option row's sub-line must not use `colors.textMuted` directly
1372
+
1373
+ A selected row sits on a tinted fill, and the hovered-selected rung is the busiest one in the
1374
+ ladder. In dark mode `surfaceRaised` carries only ~0.86 of contrast headroom above AA, so raw
1375
+ `textMuted` on that rung measures **4.00–4.22:1** in 6 of 9 brands — under the 4.5:1 a 12px
1376
+ sub-line needs. The library's own rows no longer use it.
1377
+
1378
+ **Apply:** in a `SearchSelect` `option.view` or `Select` `renderOption` node, read the sub-line ink
1379
+ from `optionRowDescriptionInk(theme.colors)` (exported from the package root) rather than
1380
+ `theme.colors.textMuted`. It is a no-op in light mode. The row's main label is
1381
+ `optionRowInk(theme.colors, isSelected)` with `optionRowWeight(isSelected)` — never brand ink.
@@ -11,16 +11,17 @@ const useExclusiveOverlay_1 = require("../../Hooks/useExclusiveOverlay");
11
11
  const lucide_react_1 = require("lucide-react");
12
12
  const rafThrottle_1 = require("../../Utils/rafThrottle");
13
13
  const controlSizes_1 = require("../../Theme/tokens/primitives/controlSizes");
14
+ const optionRowSizes_1 = require("../../Theme/tokens/primitives/optionRowSizes");
14
15
  const motion_1 = require("../../Theme/tokens/primitives/motion");
15
16
  const motion_2 = require("../../Theme/chakra/motion");
16
17
  const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
17
18
  const MotionBox = (0, framer_motion_1.motion)((0, react_1.forwardRef)((props, ref) => ((0, jsx_runtime_1.jsx)(react_2.Box, { ref: ref, ...props }))));
18
19
  const SIZE_MAP = {
19
- xs: { btn: "xs", fontSize: controlSizes_1.CONTROL_SIZES.xs.fontSize, py: 1, px: 2.5, img: "1rem", chevron: controlSizes_1.CONTROL_SIZES.xs.iconSize, minW: "8rem", gap: 1.5 },
20
- sm: { btn: "sm", fontSize: controlSizes_1.CONTROL_SIZES.sm.fontSize, py: 1.5, px: 3, img: "1rem", chevron: controlSizes_1.CONTROL_SIZES.sm.iconSize, minW: "9rem", gap: 2 },
21
- md: { btn: "md", fontSize: controlSizes_1.CONTROL_SIZES.md.fontSize, py: 2, px: 3, img: "1.125rem", chevron: controlSizes_1.CONTROL_SIZES.md.iconSize, minW: "11rem", gap: 2 },
22
- lg: { btn: "lg", fontSize: controlSizes_1.CONTROL_SIZES.lg.fontSize, py: 2.5, px: 3.5, img: "1.25rem", chevron: controlSizes_1.CONTROL_SIZES.lg.iconSize, minW: "12rem", gap: 2.5 },
23
- xl: { btn: "lg", fontSize: "1.0625rem", py: 3, px: 4, img: "1.4rem", chevron: 20, minW: "13rem", gap: 3 },
20
+ xs: { btn: "xs", img: "1rem", chevron: controlSizes_1.CONTROL_SIZES.xs.iconSize, minW: "8rem", gap: 1.5, rowSize: "xs" },
21
+ sm: { btn: "sm", img: "1rem", chevron: controlSizes_1.CONTROL_SIZES.sm.iconSize, minW: "9rem", gap: 2, rowSize: "sm" },
22
+ md: { btn: "md", img: "1.125rem", chevron: controlSizes_1.CONTROL_SIZES.md.iconSize, minW: "11rem", gap: 2, rowSize: "md" },
23
+ lg: { btn: "lg", img: "1.25rem", chevron: controlSizes_1.CONTROL_SIZES.lg.iconSize, minW: "12rem", gap: 2.5, rowSize: "lg" },
24
+ xl: { btn: "lg", img: "1.4rem", chevron: 20, minW: "13rem", gap: 3, rowSize: "lg" },
24
25
  };
25
26
  const TRIGGER_TRANSITION = (0, motion_1.transitionFor)(["background-color", "border-color", "color", "box-shadow"]);
26
27
  const ROW_TRANSITION = (0, motion_1.transitionFor)(["background-color", "color"]);
@@ -37,6 +38,7 @@ const Dropdown = (0, react_1.forwardRef)(({ dropDownButtonStyle, ButtonText, opt
37
38
  const menuRef = (0, react_1.useRef)(null);
38
39
  const [menuPos, setMenuPos] = (0, react_1.useState)({ top: 0, left: 0, width: 0 });
39
40
  const s = SIZE_MAP[(_a = size !== null && size !== void 0 ? size : optionsSize) !== null && _a !== void 0 ? _a : "sm"];
41
+ const rowSpec = (0, optionRowSizes_1.optionRowSpec)(s.rowSize);
40
42
  const prefersReducedMotion = (0, framer_motion_1.useReducedMotion)();
41
43
  const menuId = (0, react_1.useId)();
42
44
  // Roving-tabindex keyboard nav for the menu (ARIA APG menu-button pattern):
@@ -232,7 +234,7 @@ const Dropdown = (0, react_1.forwardRef)(({ dropDownButtonStyle, ButtonText, opt
232
234
  const Chevron = isVisibleIconShow ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", sx: (0, motion_2.reducedMotionSx)(), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: s.chevron }) })) : undefined;
233
235
  const dropDownToggleOption = () => {
234
236
  const ButtonToggle = ((0, jsx_runtime_1.jsx)(react_2.Button, { ref: buttonTriggerRef, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, size: s.btn, colorScheme: "primary", borderRadius: "lg", fontWeight: 600, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (ButtonText ? undefined : "Open menu"), rightIcon: Chevron, sx: { transition: TRIGGER_TRANSITION, ...(0, motion_2.reducedMotionSx)(), ...dropDownButtonStyle }, ...buttonProps, children: (0, jsx_runtime_1.jsxs)(react_2.Box, { as: "span", display: "inline-flex", alignItems: "center", gap: DropdownIcon && ButtonText ? s.gap : 0, children: [DropdownIcon, ButtonText] }) }));
235
- const TextToggle = ((0, jsx_runtime_1.jsxs)(react_2.Flex, { ref: textTriggerRef, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, align: "center", gap: 1.5, cursor: "pointer", fontSize: s.fontSize, fontWeight: 500, color: theme.colors.text[700], borderRadius: "md", transition: ROW_TRANSITION, _hover: { color: theme.colors.accentText }, sx: { ...(0, focusRing_styles_1.focusVisibleStyles)(theme.colors), ...(0, motion_2.reducedMotionSx)() }, style: dropDownButtonStyle, role: "button", tabIndex: 0, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (text ? undefined : "Open menu"), children: [DropdownIcon, text, Chevron] }));
237
+ const TextToggle = ((0, jsx_runtime_1.jsxs)(react_2.Flex, { ref: textTriggerRef, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, align: "center", gap: 1.5, cursor: "pointer", fontSize: rowSpec.fontSize, fontWeight: 500, color: theme.colors.text[700], borderRadius: "md", transition: ROW_TRANSITION, _hover: { color: theme.colors.accentText }, sx: { ...(0, focusRing_styles_1.focusVisibleStyles)(theme.colors), ...(0, motion_2.reducedMotionSx)() }, style: dropDownButtonStyle, role: "button", tabIndex: 0, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (text ? undefined : "Open menu"), children: [DropdownIcon, text, Chevron] }));
236
238
  return dropdownType === "button" ? ButtonToggle : TextToggle;
237
239
  };
238
240
  const DropdownContentRender = () => {
@@ -242,7 +244,7 @@ const Dropdown = (0, react_1.forwardRef)(({ dropDownButtonStyle, ButtonText, opt
242
244
  }, role: "menuitem", tabIndex: index === activeIndex ? 0 : -1, onClick: () => {
243
245
  handleListItemClick(option.id, option.label);
244
246
  focusTrigger();
245
- }, onKeyDown: (e) => handleItemKeyDown(e, option.id, option.label), mx: 1.5, px: s.px, py: s.py, borderRadius: "md", display: "flex", alignItems: "center", gap: 2, cursor: "pointer", color: theme.colors.text[700], transition: ROW_TRANSITION, _hover: {
247
+ }, onKeyDown: (e) => handleItemKeyDown(e, option.id, option.label), mx: rowSpec.mx, px: rowSpec.px, py: rowSpec.py, minH: rowSpec.minH, borderRadius: optionRowSizes_1.OPTION_ROW_RADIUS, display: "flex", alignItems: "center", gap: rowSpec.gap, fontSize: rowSpec.fontSize, cursor: "pointer", color: theme.colors.text[700], transition: ROW_TRANSITION, _hover: {
246
248
  bg: hoverTint,
247
249
  color: theme.colors.accentText,
248
250
  }, _focus: {
@@ -255,7 +257,7 @@ const Dropdown = (0, react_1.forwardRef)(({ dropDownButtonStyle, ButtonText, opt
255
257
  boxShadow: `inset 0 0 0 2px ${theme.colors.focusRing.ring}`,
256
258
  }, borderBottom: divider
257
259
  ? `0.063rem solid ${theme.colors.boxborder[200]}`
258
- : undefined, sx: { "&:last-child": { borderBottom: "none" }, ...(0, motion_2.reducedMotionSx)() }, style: ItemStyle, fontWeight: 500, children: [option.image && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: option.image, boxSize: s.img, flexShrink: 0, color: theme.colors.textMuted, style: ImageStyle })), (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", fontSize: s.fontSize, isTruncated: true, style: LabelStyle, children: option.label })] }, option.id))) }));
260
+ : undefined, sx: { "&:last-child": { borderBottom: "none" }, ...(0, motion_2.reducedMotionSx)() }, style: ItemStyle, fontWeight: 500, children: [option.image && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: option.image, boxSize: s.img, flexShrink: 0, color: theme.colors.textMuted, style: ImageStyle })), (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", fontSize: rowSpec.fontSize, isTruncated: true, style: LabelStyle, children: option.label })] }, option.id))) }));
259
261
  };
260
262
  return ((0, jsx_runtime_1.jsxs)(react_2.Box, { ref: dropdownRef, position: "relative", display: "inline-block", children: [dropDownToggleOption(), isOpen && ((0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsx)(MotionBox, { ref: setMenuRef, position: "fixed", top: `${menuPos.top}px`, left: `${menuPos.left}px`, minW: `${menuPos.width}px`, zIndex: 1500, initial: prefersReducedMotion ? false : { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -6 }, style: headStyle, children: DropdownContentRender() }) }))] }));
261
263
  });
@@ -44,6 +44,8 @@ const FormLabel_1 = require("../Common/FormLabel");
44
44
  const Tag_1 = __importDefault(require("../Tag/Tag"));
45
45
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
46
46
  const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
47
+ const optionRowSizes_1 = require("../../Theme/tokens/primitives/optionRowSizes");
48
+ const optionRowInk_1 = require("../Select/optionRowInk");
47
49
  const useExclusiveOverlay_1 = require("../../Hooks/useExclusiveOverlay");
48
50
  const FieldMessage_1 = __importDefault(require("../Common/FieldMessage"));
49
51
  const useFieldIds_1 = require("../Common/useFieldIds");
@@ -75,6 +77,7 @@ const normalizeMultiValue = (val) => {
75
77
  const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOptionLoading = false, isLoading = false, loadingText = "Loading...", placeholder = "Select", value, searchQuery = "", isMultiple = false, isRequired = false, error = false, errorMessage, helperText, width = "100%", height, size = "sm", isDisabled = false, chip, information, isInformation, informationMessage, rightIcon, addNew, isSelectAll = false, pagination, insideSelect, isRemoveAllIcon, "aria-label": ariaLabel, }) => {
76
78
  var _a, _b, _c, _d, _e;
77
79
  const s = (0, searchSelectSize_1.getSearchSelectSizeStyles)(size);
80
+ const spec = (0, optionRowSizes_1.optionRowSpec)(size);
78
81
  const emitChange = onChange !== null && onChange !== void 0 ? onChange : onSelect;
79
82
  const insideSelectBoxHeight = height !== null && height !== void 0 ? height : s.insideSelectDefaultHeight;
80
83
  const [inputValue, setInputValue] = (0, react_1.useState)(searchQuery);
@@ -494,16 +497,14 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
494
497
  }, children: [(0, jsx_runtime_1.jsxs)(react_2.Box, { minW: "4.5rem", cursor: "pointer", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 1, width: insideSelect.width, height: insideSelectBoxHeight, pl: "0.5rem", pr: "0.375rem", children: [insideLabel ? ((0, jsx_runtime_1.jsx)(SelectTruncatedLabel_1.default, { label: insideLabel, maxWidth: insideSelect.width || "6rem", fontSize: s.fieldFontSize })) : ((0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: s.fieldFontSize, fontWeight: 500, color: theme.colors.textMuted, isTruncated: true, maxW: insideSelect.width || "6rem", children: insidePlaceholder })), (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, color: customSelectOpen ? theme.colors.accentText : theme.colors.textMuted, transition: CHEVRON_TRANSITION, transform: customSelectOpen ? "rotate(180deg)" : "rotate(0deg)", children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: s.chevronSize }) })] }), customSelectOpen && ((0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsx)(react_2.Box, { ref: setInsideSelectMenuRef, id: insideSelectPortalId, position: "fixed", top: `${customSelectPos.top}px`, left: `${customSelectPos.left}px`, zIndex: 1510, bg: theme.colors.surfaceRaised, border: "0.063rem solid", borderColor: theme.colors.boxborder[200], borderRadius: "xl", boxShadow: theme.shadows.lg, minW: `${customSelectPos.width}px`, maxW: "20rem", py: 1, overflow: "hidden", children: (_c = insideSelect.option) === null || _c === void 0 ? void 0 : _c.map((item) => {
495
498
  var _a;
496
499
  const selected = ((_a = insideSelect.value) === null || _a === void 0 ? void 0 : _a.id) === item.id;
497
- return ((0, jsx_runtime_1.jsxs)(react_2.Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, minH: s.optionRowMinH, py: s.optionRowPy, px: "0.75rem", fontSize: s.dropdownTextFontSize, fontWeight: selected ? 600 : 500, color: selected
498
- ? theme.colors.accentText
499
- : theme.colors.text[700], bg: selected ? rowBackgrounds.selected : "transparent", _hover: {
500
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: spec.gap, minH: spec.minH, py: spec.py, px: spec.px, fontSize: spec.fontSize, fontWeight: (0, optionRowInk_1.optionRowWeight)(selected), color: (0, optionRowInk_1.optionRowInk)(theme.colors, selected), bg: selected ? rowBackgrounds.selected : "transparent", _hover: {
500
501
  bg: selected
501
502
  ? rowBackgrounds.selectedActive
502
503
  : rowBackgrounds.active,
503
504
  }, cursor: "pointer", onClick: (e) => {
504
505
  e.stopPropagation();
505
506
  handleCustomSelect(item);
506
- }, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { isTruncated: true, children: item.label }), selected && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: s.chevronSize - 2, color: theme.colors.accentText }))] }, item.id));
507
+ }, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { isTruncated: true, children: item.label }), selected && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: spec.iconSize, color: theme.colors.accentText }))] }, item.id));
507
508
  }) }) }))] }));
508
509
  })(), isMultiple
509
510
  ? selectedValues
@@ -535,16 +536,20 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
535
536
  else {
536
537
  openMenu();
537
538
  }
538
- }, "aria-label": "toggle-dropdown", display: "flex", alignItems: "center", borderRadius: "sm", color: theme.colors.textMuted, cursor: isDisabled ? "not-allowed" : "pointer", sx: (0, focusRing_styles_1.focusVisibleStyles)(theme.colors), children: isOpen ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronUp, { size: s.chevronSize })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: s.chevronSize })) }))] })] }) }), isOpen && ((0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsxs)(react_2.Box, { ref: setDropdownMenuRef, id: listboxId, role: "listbox", "aria-multiselectable": isMultiple || undefined, position: "fixed", top: `${dropdownPos.top}px`, left: `${dropdownPos.left}px`, width: `${dropdownPos.width}px`, zIndex: 1500, maxH: "20rem", borderWidth: 1, borderColor: theme.colors.boxborder[200], borderRadius: "xl", overflow: "hidden", bg: theme.colors.surfaceRaised, boxShadow: theme.shadows.lg, display: "flex", flexDirection: "column", children: [isMultiple && isSelectAll && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: s.optionRowPy, px: 3, cursor: "pointer", onClick: handleSelectAll, display: "flex", alignItems: "center", gap: 2, color: theme.colors.text[700], borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [(0, jsx_runtime_1.jsx)(Checkbox_1.default, { isChecked: allFilteredSelected, sx: { pointerEvents: "none" }, size: s.checkboxSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: s.dropdownTextFontSize, children: allFilteredSelected ? "Unselect All" : "Select All" })] })), (0, jsx_runtime_1.jsx)(react_2.Box, { flex: "1", overflowY: "auto", onScroll: handleScroll, maxH: "15rem", borderBottom: "0.063rem solid", borderColor: theme.colors.boxborder[200], children: isOptionLoading && filteredOptions.length === 0 ? ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: s.optionRowPy, px: 3, display: "flex", alignItems: "center", gap: 2, minH: s.optionRowMinH, color: theme.colors.textMuted, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: s.spinnerSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: s.dropdownTextFontSize, children: loadingText })] })) : !isOptionLoading && filteredOptions.length === 0 ? ((0, jsx_runtime_1.jsx)(react_2.Text, { p: 3, fontSize: s.dropdownTextFontSize, color: theme.colors.textMuted, children: "No results found" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [filteredOptions.map((option, index) => ((0, jsx_runtime_1.jsxs)(react_2.Box, { id: getOptionId(index), role: "option", "aria-selected": isSelected(option.id), py: s.optionRowPy, px: 3, minH: s.optionRowMinH, cursor: "pointer", bg: isSelected(option.id)
539
- ? index === activeIndex
540
- ? rowBackgrounds.selectedActive
541
- : rowBackgrounds.selected
542
- : index === activeIndex
543
- ? rowBackgrounds.active
544
- : "transparent", _hover: {
545
- bg: isSelected(option.id)
546
- ? rowBackgrounds.selectedActive
547
- : rowBackgrounds.active,
548
- }, onClick: () => handleSelect(option), onMouseEnter: () => setActiveIndex(index), display: "flex", alignItems: "center", gap: 2, color: isSelected(option.id) ? theme.colors.accentText : theme.colors.text[700], fontWeight: isSelected(option.id) ? 600 : 400, borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [isMultiple && ((0, jsx_runtime_1.jsx)(Checkbox_1.default, { isChecked: isSelected(option.id), sx: { pointerEvents: "none" }, size: s.checkboxSize })), option.view ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: option.view })) : ((0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: s.dropdownTextFontSize, children: option.label }))] }, option.id))), ((pagination === null || pagination === void 0 ? void 0 : pagination.scrollLoading) || (isOptionLoading && filteredOptions.length > 0)) && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: s.optionRowPy, px: 3, display: "flex", alignItems: "center", justifyContent: "center", gap: 2, minH: s.optionRowMinH, color: theme.colors.textMuted, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: s.spinnerSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: s.dropdownTextFontSize, children: loadingText })] }))] })) }), (addNew === null || addNew === void 0 ? void 0 : addNew.enabled) && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: s.optionRowPy, px: 3, borderTop: "0.063rem solid", borderColor: theme.colors.boxborder[200], bg: theme.colors.surfaceRaised, display: "flex", alignItems: "center", gap: 2, color: theme.colors.accentText, fontWeight: 500, minH: s.optionRowMinH, cursor: "pointer", onClick: addNew.onClick, _hover: { bg: rowBackgrounds.active }, children: [addNew.icon || (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: s.addNewIconSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: s.dropdownTextFontSize, children: addNew.text || "Add New" })] }))] }) })), (0, jsx_runtime_1.jsx)(FieldMessage_1.default, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
539
+ }, "aria-label": "toggle-dropdown", display: "flex", alignItems: "center", borderRadius: "sm", color: theme.colors.textMuted, cursor: isDisabled ? "not-allowed" : "pointer", sx: (0, focusRing_styles_1.focusVisibleStyles)(theme.colors), children: isOpen ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronUp, { size: s.chevronSize })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: s.chevronSize })) }))] })] }) }), isOpen && ((0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsxs)(react_2.Box, { ref: setDropdownMenuRef, id: listboxId, role: "listbox", "aria-multiselectable": isMultiple || undefined, position: "fixed", top: `${dropdownPos.top}px`, left: `${dropdownPos.left}px`, width: `${dropdownPos.width}px`, zIndex: 1500, maxH: "20rem", borderWidth: 1, borderColor: theme.colors.boxborder[200], borderRadius: "xl", overflow: "hidden", bg: theme.colors.surfaceRaised, boxShadow: theme.shadows.lg, display: "flex", flexDirection: "column", children: [isMultiple && isSelectAll && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", onClick: handleSelectAll, display: "flex", alignItems: "center", gap: spec.gap, color: theme.colors.text[700], borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [(0, jsx_runtime_1.jsx)(Checkbox_1.default, { isChecked: allFilteredSelected, sx: { pointerEvents: "none" }, size: s.checkboxSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: spec.fontSize, children: allFilteredSelected ? "Unselect All" : "Select All" })] })), (0, jsx_runtime_1.jsx)(react_2.Box, { flex: "1", overflowY: "auto", onScroll: handleScroll, maxH: "15rem", borderBottom: "0.063rem solid", borderColor: theme.colors.boxborder[200], children: isOptionLoading && filteredOptions.length === 0 ? ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: spec.py, px: spec.px, display: "flex", alignItems: "center", gap: spec.gap, minH: spec.minH, color: theme.colors.textMuted, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: s.spinnerSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: spec.fontSize, children: loadingText })] })) : !isOptionLoading && filteredOptions.length === 0 ? ((0, jsx_runtime_1.jsx)(react_2.Text, { p: 3, fontSize: spec.fontSize, color: theme.colors.textMuted, children: "No results found" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [filteredOptions.map((option, index) => {
540
+ const selected = isSelected(option.id);
541
+ const singleSelected = selected && !isMultiple;
542
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { id: getOptionId(index), role: "option", "aria-selected": selected, py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", bg: selected
543
+ ? index === activeIndex
544
+ ? rowBackgrounds.selectedActive
545
+ : rowBackgrounds.selected
546
+ : index === activeIndex
547
+ ? rowBackgrounds.active
548
+ : "transparent", _hover: {
549
+ bg: selected ? rowBackgrounds.selectedActive : rowBackgrounds.active,
550
+ }, onClick: () => handleSelect(option), onMouseEnter: () => setActiveIndex(index), display: "flex", alignItems: "center", gap: spec.gap, color: (0, optionRowInk_1.optionRowInk)(theme.colors, singleSelected),
551
+ // `view` is consumer JSX with no size of its own; it inherited the 16px body font.
552
+ fontSize: spec.fontSize, fontWeight: (0, optionRowInk_1.optionRowWeight)(singleSelected), borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [isMultiple && ((0, jsx_runtime_1.jsx)(Checkbox_1.default, { isChecked: selected, sx: { pointerEvents: "none" }, size: s.checkboxSize })), option.view ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: option.view })) : ((0, jsx_runtime_1.jsx)(react_2.Text, { isTruncated: true, children: option.label })), singleSelected && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: lucide_react_1.Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 }))] }, option.id));
553
+ }), ((pagination === null || pagination === void 0 ? void 0 : pagination.scrollLoading) || (isOptionLoading && filteredOptions.length > 0)) && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: spec.py, px: spec.px, display: "flex", alignItems: "center", justifyContent: "center", gap: spec.gap, minH: spec.minH, color: theme.colors.textMuted, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: s.spinnerSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: spec.fontSize, children: loadingText })] }))] })) }), (addNew === null || addNew === void 0 ? void 0 : addNew.enabled) && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: spec.py, px: spec.px, borderTop: "0.063rem solid", borderColor: theme.colors.boxborder[200], bg: theme.colors.surfaceRaised, display: "flex", alignItems: "center", gap: spec.gap, color: theme.colors.accentText, fontWeight: 500, minH: spec.minH, cursor: "pointer", onClick: addNew.onClick, _hover: { bg: rowBackgrounds.active }, children: [addNew.icon || (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: s.addNewIconSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: spec.fontSize, children: addNew.text || "Add New" })] }))] }) })), (0, jsx_runtime_1.jsx)(FieldMessage_1.default, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
549
554
  };
550
555
  exports.default = SearchSelect;
@@ -19,11 +19,6 @@ export type SearchSelectSizeStyles = {
19
19
  addNewIconSize: number;
20
20
  /** Offset for the nested select segment (aligns with field padding). */
21
21
  insideSelectMarginLeft: string;
22
- /** “Select all” / option row text in the dropdown. */
23
- dropdownTextFontSize: "xs" | "sm" | "md";
24
- /** Min row height for main option list items. */
25
- optionRowMinH: string;
26
- optionRowPy: number;
27
22
  /** Chakra `Checkbox` only supports sm | md | lg. */
28
23
  checkboxSize: "sm" | "md" | "lg";
29
24
  /** Chakra `Input` font token per `size` (unstyled still needs explicit fontSize). */
@@ -21,9 +21,6 @@ const SIZE_STYLES = {
21
21
  clearIconSize: 10,
22
22
  addNewIconSize: 14,
23
23
  insideSelectMarginLeft: "-0.5rem",
24
- dropdownTextFontSize: "xs",
25
- optionRowMinH: "1.5rem",
26
- optionRowPy: 1.5,
27
24
  checkboxSize: "sm",
28
25
  },
29
26
  sm: {
@@ -42,9 +39,6 @@ const SIZE_STYLES = {
42
39
  clearIconSize: 12,
43
40
  addNewIconSize: 16,
44
41
  insideSelectMarginLeft: "-0.625rem",
45
- dropdownTextFontSize: "sm",
46
- optionRowMinH: "2rem",
47
- optionRowPy: 2,
48
42
  checkboxSize: "sm",
49
43
  },
50
44
  md: {
@@ -63,9 +57,6 @@ const SIZE_STYLES = {
63
57
  clearIconSize: 12,
64
58
  addNewIconSize: 16,
65
59
  insideSelectMarginLeft: "-0.75rem",
66
- dropdownTextFontSize: "sm",
67
- optionRowMinH: "2.5rem",
68
- optionRowPy: 2,
69
60
  checkboxSize: "md",
70
61
  },
71
62
  lg: {
@@ -84,9 +75,6 @@ const SIZE_STYLES = {
84
75
  clearIconSize: 14,
85
76
  addNewIconSize: 18,
86
77
  insideSelectMarginLeft: "-0.875rem",
87
- dropdownTextFontSize: "md",
88
- optionRowMinH: "2.75rem",
89
- optionRowPy: 2.5,
90
78
  checkboxSize: "lg",
91
79
  },
92
80
  };
@@ -44,6 +44,7 @@ const useCustomTheme_1 = require("../../Theme/useCustomTheme");
44
44
  const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
45
45
  const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
46
46
  const optionRowInk_1 = require("./optionRowInk");
47
+ const optionRowSizes_1 = require("../../Theme/tokens/primitives/optionRowSizes");
47
48
  const scrollbar_1 = require("../../Theme/tokens/builders/scrollbar");
48
49
  const controlSizes_1 = require("../../Theme/tokens/primitives/controlSizes");
49
50
  const motion_1 = require("../../Theme/tokens/primitives/motion");
@@ -59,8 +60,6 @@ const CHEVRON_TRANSITION = (0, motion_1.transitionFor)(["transform"], "base");
59
60
  const PAGE_STEP = 8;
60
61
  const LOAD_MORE_THRESHOLD_PX = 8;
61
62
  const DEFAULT_MAX_VISIBLE_CHIPS = 6;
62
- // NOTE: approximate single-row height (px 2.5 + ~13px text); used only to window `virtualizeAfter`, never for layout.
63
- const VIRTUAL_ROW_HEIGHT_PX = 34;
64
63
  const VIRTUAL_OVERSCAN = 4;
65
64
  const CHIP_TAG_HEIGHT_PX = { xs: 20, sm: 24 };
66
65
  const controlHeightPx = (size) => parseFloat(controlSizes_1.CONTROL_SIZES[size].height) * 16;
@@ -77,11 +76,11 @@ const parseRemToPx = (value, fallbackPx) => {
77
76
  const match = /^(\d*\.?\d+)rem$/.exec(value);
78
77
  return match ? parseFloat(match[1]) * 16 : fallbackPx;
79
78
  };
80
- const OptionRow = react_1.default.memo(function OptionRow({ option, index, listboxId, isSelected, isActive, isMulti, theme, backgrounds, renderOption, onSelect, onActivate, }) {
79
+ const OptionRow = react_1.default.memo(function OptionRow({ option, index, listboxId, isSelected, isActive, isMulti, theme, backgrounds, spec, renderOption, onSelect, onActivate, }) {
81
80
  const isSingleSelected = isSelected && !isMulti;
82
81
  const selectedBg = isActive ? backgrounds.selectedActive : backgrounds.selected;
83
82
  const restBg = isActive ? backgrounds.active : "transparent";
84
- return ((0, jsx_runtime_1.jsxs)(react_2.Box, { id: optionDomId(listboxId, option.id), "data-index": index, role: "option", "aria-selected": isSelected, "aria-disabled": option.isDisabled, onMouseEnter: () => onActivate(index), onClick: () => onSelect(option), mx: 1.5, px: 2.5, py: 1.5, borderRadius: "md", cursor: option.isDisabled ? "not-allowed" : "pointer", opacity: option.isDisabled ? 0.4 : 1, display: "flex", alignItems: "center", gap: 2, fontSize: "0.8125rem", fontWeight: isSingleSelected ? 600 : 400, color: (0, optionRowInk_1.optionRowInk)(theme.colors, isSingleSelected), bg: isSingleSelected ? selectedBg : restBg, transition: ROW_TRANSITION, sx: (0, motion_2.reducedMotionSx)(), children: [isMulti ? ((0, jsx_runtime_1.jsx)(react_2.Checkbox, { id: `${optionDomId(listboxId, option.id)}-checkbox`, isChecked: isSelected, isDisabled: option.isDisabled, pointerEvents: "none", tabIndex: -1 })) : null, renderOption ? ((0, jsx_runtime_1.jsx)(react_2.Box, { flex: 1, minW: 0, children: renderOption(option, { isSelected, isActive }) })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [option.icon ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, children: option.icon })) : null, (0, jsx_runtime_1.jsxs)(react_2.Box, { flex: 1, minW: 0, isTruncated: true, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", isTruncated: true, children: option.label }), option.description ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "block", fontSize: "0.75rem", color: theme.colors.textMuted, isTruncated: true, children: option.description })) : null] })] })), isSingleSelected ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: lucide_react_1.Check, size: 16, color: theme.colors.accentText, flexShrink: 0 })) : null] }));
83
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { id: optionDomId(listboxId, option.id), "data-index": index, role: "option", "aria-selected": isSelected, "aria-disabled": option.isDisabled, onMouseEnter: () => onActivate(index), onClick: () => onSelect(option), mx: spec.mx, px: spec.px, py: spec.py, minH: spec.minH, borderRadius: "md", cursor: option.isDisabled ? "not-allowed" : "pointer", opacity: option.isDisabled ? 0.4 : 1, display: "flex", alignItems: "center", gap: spec.gap, fontSize: spec.fontSize, fontWeight: (0, optionRowInk_1.optionRowWeight)(isSingleSelected), color: (0, optionRowInk_1.optionRowInk)(theme.colors, isSingleSelected), bg: isSingleSelected ? selectedBg : restBg, transition: ROW_TRANSITION, sx: (0, motion_2.reducedMotionSx)(), children: [isMulti ? ((0, jsx_runtime_1.jsx)(react_2.Checkbox, { id: `${optionDomId(listboxId, option.id)}-checkbox`, isChecked: isSelected, isDisabled: option.isDisabled, pointerEvents: "none", tabIndex: -1 })) : null, renderOption ? ((0, jsx_runtime_1.jsx)(react_2.Box, { flex: 1, minW: 0, children: renderOption(option, { isSelected, isActive }) })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [option.icon ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, children: option.icon })) : null, (0, jsx_runtime_1.jsxs)(react_2.Box, { flex: 1, minW: 0, isTruncated: true, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", isTruncated: true, children: option.label }), option.description ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "block", fontSize: spec.descriptionFontSize, color: (0, optionRowInk_1.optionRowDescriptionInk)(theme.colors), isTruncated: true, children: option.description })) : null] })] })), isSingleSelected ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: lucide_react_1.Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 })) : null] }));
85
84
  });
86
85
  const buildGroupedRows = (options) => {
87
86
  const rows = [];
@@ -215,27 +214,28 @@ const SelectPopup = (0, react_1.forwardRef)(function SelectPopup(props, ref) {
215
214
  const frame = requestAnimationFrame(() => { var _a; return (_a = filterInputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
216
215
  return () => cancelAnimationFrame(frame);
217
216
  }, [isOpen, isSearchable]);
217
+ const spec = (0, react_1.useMemo)(() => (0, optionRowSizes_1.optionRowSpec)(size), [size]);
218
218
  const maxMenuHeightPx = (0, react_1.useMemo)(() => parseRemToPx(maxMenuHeight, 256), [maxMenuHeight]);
219
219
  const shouldVirtualize = !!virtualizeAfter && !hasGroups && filteredOptions.length > virtualizeAfter;
220
220
  const [scrollTop, setScrollTop] = (0, react_1.useState)(0);
221
- const visibleCount = Math.ceil(maxMenuHeightPx / VIRTUAL_ROW_HEIGHT_PX) + VIRTUAL_OVERSCAN * 2;
221
+ const visibleCount = Math.ceil(maxMenuHeightPx / spec.virtualRowHeightPx) + VIRTUAL_OVERSCAN * 2;
222
222
  const startIndex = shouldVirtualize
223
- ? Math.max(0, Math.floor(scrollTop / VIRTUAL_ROW_HEIGHT_PX) - VIRTUAL_OVERSCAN)
223
+ ? Math.max(0, Math.floor(scrollTop / spec.virtualRowHeightPx) - VIRTUAL_OVERSCAN)
224
224
  : 0;
225
225
  const endIndex = shouldVirtualize
226
226
  ? Math.min(filteredOptions.length, startIndex + visibleCount)
227
227
  : filteredOptions.length;
228
228
  const renderedOptions = shouldVirtualize ? filteredOptions.slice(startIndex, endIndex) : filteredOptions;
229
- const topPadPx = shouldVirtualize ? startIndex * VIRTUAL_ROW_HEIGHT_PX : 0;
230
- const bottomPadPx = shouldVirtualize ? (filteredOptions.length - endIndex) * VIRTUAL_ROW_HEIGHT_PX : 0;
229
+ const topPadPx = shouldVirtualize ? startIndex * spec.virtualRowHeightPx : 0;
230
+ const bottomPadPx = shouldVirtualize ? (filteredOptions.length - endIndex) * spec.virtualRowHeightPx : 0;
231
231
  (0, react_1.useEffect)(() => {
232
232
  var _a, _b;
233
233
  if (activeIndex < 0 || !listRef.current)
234
234
  return;
235
235
  const el = listRef.current;
236
236
  if (shouldVirtualize) {
237
- const targetTop = activeIndex * VIRTUAL_ROW_HEIGHT_PX;
238
- const targetBottom = targetTop + VIRTUAL_ROW_HEIGHT_PX;
237
+ const targetTop = activeIndex * spec.virtualRowHeightPx;
238
+ const targetBottom = targetTop + spec.virtualRowHeightPx;
239
239
  if (targetTop < el.scrollTop) {
240
240
  el.scrollTop = targetTop;
241
241
  setScrollTop(targetTop);
@@ -248,7 +248,7 @@ const SelectPopup = (0, react_1.forwardRef)(function SelectPopup(props, ref) {
248
248
  return;
249
249
  }
250
250
  (_b = (_a = el.querySelector(`[data-index="${activeIndex}"]`)) === null || _a === void 0 ? void 0 : _a.scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, { block: "nearest" });
251
- }, [activeIndex, shouldVirtualize, maxMenuHeightPx]);
251
+ }, [activeIndex, shouldVirtualize, maxMenuHeightPx, spec.virtualRowHeightPx]);
252
252
  const moveActive = (delta) => {
253
253
  if (!filteredOptions.length)
254
254
  return;
@@ -411,14 +411,14 @@ const SelectPopup = (0, react_1.forwardRef)(function SelectPopup(props, ref) {
411
411
  const selectedValueIds = selectedOptions.map((option) => option.id.toString());
412
412
  const hiddenValues = !name ? [] : props.isMulti ? selectedValueIds : [(_c = selectedValueIds[0]) !== null && _c !== void 0 ? _c : ""];
413
413
  const groupedRows = (0, react_1.useMemo)(() => (hasGroups ? buildGroupedRows(filteredOptions) : []), [hasGroups, filteredOptions]);
414
- return ((0, jsx_runtime_1.jsxs)(react_2.FormControl, { isInvalid: error, isDisabled: isDisabled, isReadOnly: isReadOnly, style: formControlStyle, width: width, children: [label && ((0, jsx_runtime_1.jsx)(FormLabel_1.TextLabel, { label: label, id: ids.inputId, isRequired: isRequired, isInformation: isInformation, informationMessage: informationMessage })), (0, jsx_runtime_1.jsxs)(react_2.Popover, { isOpen: isOpen, onClose: () => resetAndClose(), placement: "bottom-start", autoFocus: false, returnFocusOnClose: false, gutter: 4, children: [(0, jsx_runtime_1.jsx)(react_2.PopoverAnchor, { children: (0, jsx_runtime_1.jsxs)(react_2.Box, { as: "div", ref: mergedTriggerRef, ...ids.controlProps, role: "combobox", tabIndex: isDisabled ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-owns": `${listboxId} ${filterInputId}`, "aria-activedescendant": activeDescendantId, "aria-disabled": isDisabled || undefined, "data-readonly": isReadOnly || undefined, "aria-required": isRequired || undefined, "aria-label": triggerAriaLabel(), "aria-labelledby": ariaLabelledBy, onClick: handleOpen, onKeyDown: handleTriggerKeyDown, display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", textAlign: "left", cursor: isDisabled ? "not-allowed" : isReadOnly ? "default" : "pointer", "data-testid": dataTestId, sx: triggerSx, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexWrap: showChips ? "wrap" : "nowrap", alignItems: "center", gap: showChips ? 1 : 1.5, minW: 0, flex: 1, isTruncated: !showChips, children: showChips ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [visibleChipOptions.map((option) => ((0, jsx_runtime_1.jsx)(Tag_1.default, { label: String(option.label), size: chipSize, variant: "subtle", icon: isInteractive ? lucide_react_1.X : undefined, onIconClick: isInteractive ? () => handleSelect(option) : undefined }, option.id))), overflowChipCount > 0 && ((0, jsx_runtime_1.jsx)(Tag_1.default, { label: `+${overflowChipCount}`, size: chipSize, variant: "subtle", colorScheme: "gray" }))] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [triggerIcon ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, children: triggerIcon })) : null, (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", isTruncated: true, children: renderValue ? renderValue(selectedOptions) : defaultTriggerContent() })] })) }), showClear && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "button", type: "button", "aria-label": "Clear selection", onClick: handleClear, display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, ml: 2, color: colors.textMuted, _hover: { color: colors.text[700] }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: controlSizes_1.CONTROL_SIZES[size].iconSize }) })), (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, ml: 2, transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", color: colors.textMuted, sx: (0, motion_2.reducedMotionSx)(), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: controlSizes_1.CONTROL_SIZES[size].iconSize }) })] }) }), (0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsxs)(react_2.PopoverContent, { width: "max-content", minWidth: triggerWidth ? `${triggerWidth}px` : undefined, maxWidth: "min(24rem, calc(100vw - 2rem))", borderRadius: "xl", boxShadow: theme.shadows.lg, overflow: "hidden", onKeyDown: handleListKeyDown, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { p: 2, borderBottom: `0.063rem solid ${colors.boxborder[200]}`, display: isSearchable ? undefined : "none", children: (0, jsx_runtime_1.jsx)(react_2.Input, { ref: filterInputRef, id: filterInputId, size: "sm", placeholder: "Type to filter...", value: currentSearch, onChange: handleFilterChange, "aria-activedescendant": activeDescendantId, "aria-controls": listboxId, "aria-label": "Filter options", tabIndex: isSearchable ? undefined : -1 }) }), (0, jsx_runtime_1.jsxs)(react_2.Box, { id: listboxId, ref: listRef, role: "listbox", "aria-label": (_d = label !== null && label !== void 0 ? label : ariaLabel) !== null && _d !== void 0 ? _d : placeholder, "aria-multiselectable": props.isMulti || undefined, "aria-busy": isLoading || undefined, maxH: maxMenuHeight, overflowY: "auto", py: 1, sx: listScrollStyles, onScroll: handleListScroll, children: [filteredOptions.length === 0 && !isLoading && !showCreateRow && ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 3, py: 2, fontSize: "0.8125rem", color: colors.textMuted, children: noOptionsMessage !== null && noOptionsMessage !== void 0 ? noOptionsMessage : "No Options" })), showCreateRow && ((0, jsx_runtime_1.jsx)(react_2.Box, { id: createRowId, role: "option", "aria-selected": false, "data-index": -1, onClick: handleCreate, mx: 1.5, px: 2.5, py: 1.5, borderRadius: "md", cursor: "pointer", fontSize: "0.8125rem", fontWeight: 600, color: colors.primary[600], bg: activeDescendantId === createRowId ? rowBackgrounds.active : "transparent", children: createLabel ? createLabel(currentSearch.trim()) : `Create "${currentSearch.trim()}"` })), hasGroups
414
+ return ((0, jsx_runtime_1.jsxs)(react_2.FormControl, { isInvalid: error, isDisabled: isDisabled, isReadOnly: isReadOnly, style: formControlStyle, width: width, children: [label && ((0, jsx_runtime_1.jsx)(FormLabel_1.TextLabel, { label: label, id: ids.inputId, isRequired: isRequired, isInformation: isInformation, informationMessage: informationMessage })), (0, jsx_runtime_1.jsxs)(react_2.Popover, { isOpen: isOpen, onClose: () => resetAndClose(), placement: "bottom-start", autoFocus: false, returnFocusOnClose: false, gutter: 4, children: [(0, jsx_runtime_1.jsx)(react_2.PopoverAnchor, { children: (0, jsx_runtime_1.jsxs)(react_2.Box, { as: "div", ref: mergedTriggerRef, ...ids.controlProps, role: "combobox", tabIndex: isDisabled ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-owns": `${listboxId} ${filterInputId}`, "aria-activedescendant": activeDescendantId, "aria-disabled": isDisabled || undefined, "data-readonly": isReadOnly || undefined, "aria-required": isRequired || undefined, "aria-label": triggerAriaLabel(), "aria-labelledby": ariaLabelledBy, onClick: handleOpen, onKeyDown: handleTriggerKeyDown, display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", textAlign: "left", cursor: isDisabled ? "not-allowed" : isReadOnly ? "default" : "pointer", "data-testid": dataTestId, sx: triggerSx, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexWrap: showChips ? "wrap" : "nowrap", alignItems: "center", gap: showChips ? 1 : 1.5, minW: 0, flex: 1, isTruncated: !showChips, children: showChips ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [visibleChipOptions.map((option) => ((0, jsx_runtime_1.jsx)(Tag_1.default, { label: String(option.label), size: chipSize, variant: "subtle", icon: isInteractive ? lucide_react_1.X : undefined, onIconClick: isInteractive ? () => handleSelect(option) : undefined }, option.id))), overflowChipCount > 0 && ((0, jsx_runtime_1.jsx)(Tag_1.default, { label: `+${overflowChipCount}`, size: chipSize, variant: "subtle", colorScheme: "gray" }))] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [triggerIcon ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, children: triggerIcon })) : null, (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", isTruncated: true, children: renderValue ? renderValue(selectedOptions) : defaultTriggerContent() })] })) }), showClear && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "button", type: "button", "aria-label": "Clear selection", onClick: handleClear, display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, ml: 2, color: colors.textMuted, _hover: { color: colors.text[700] }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: controlSizes_1.CONTROL_SIZES[size].iconSize }) })), (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, ml: 2, transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", color: colors.textMuted, sx: (0, motion_2.reducedMotionSx)(), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: controlSizes_1.CONTROL_SIZES[size].iconSize }) })] }) }), (0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsxs)(react_2.PopoverContent, { width: "max-content", minWidth: triggerWidth ? `${triggerWidth}px` : undefined, maxWidth: "min(24rem, calc(100vw - 2rem))", borderRadius: "xl", boxShadow: theme.shadows.lg, overflow: "hidden", onKeyDown: handleListKeyDown, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { p: 2, borderBottom: `0.063rem solid ${colors.boxborder[200]}`, display: isSearchable ? undefined : "none", children: (0, jsx_runtime_1.jsx)(react_2.Input, { ref: filterInputRef, id: filterInputId, size: "sm", placeholder: "Type to filter...", value: currentSearch, onChange: handleFilterChange, "aria-activedescendant": activeDescendantId, "aria-controls": listboxId, "aria-label": "Filter options", tabIndex: isSearchable ? undefined : -1 }) }), (0, jsx_runtime_1.jsxs)(react_2.Box, { id: listboxId, ref: listRef, role: "listbox", "aria-label": (_d = label !== null && label !== void 0 ? label : ariaLabel) !== null && _d !== void 0 ? _d : placeholder, "aria-multiselectable": props.isMulti || undefined, "aria-busy": isLoading || undefined, maxH: maxMenuHeight, overflowY: "auto", py: 1, sx: listScrollStyles, onScroll: handleListScroll, children: [filteredOptions.length === 0 && !isLoading && !showCreateRow && ((0, jsx_runtime_1.jsx)(react_2.Box, { mx: spec.mx, px: spec.px, py: spec.py, fontSize: spec.fontSize, color: colors.textMuted, children: noOptionsMessage !== null && noOptionsMessage !== void 0 ? noOptionsMessage : "No Options" })), showCreateRow && ((0, jsx_runtime_1.jsx)(react_2.Box, { id: createRowId, role: "option", "aria-selected": false, "data-index": -1, onClick: handleCreate, mx: spec.mx, px: spec.px, py: spec.py, minH: spec.minH, borderRadius: "md", cursor: "pointer", fontSize: spec.fontSize, fontWeight: 600, color: colors.primary[600], bg: activeDescendantId === createRowId ? rowBackgrounds.active : "transparent", children: createLabel ? createLabel(currentSearch.trim()) : `Create "${currentSearch.trim()}"` })), hasGroups
415
415
  ? groupedRows.map((row) => {
416
416
  var _a;
417
- return row.kind === "header" ? ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 3, pt: 2, pb: 1, fontSize: "0.6875rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.03em", color: colors.textMuted, children: row.group }, row.key)) : ((0, jsx_runtime_1.jsx)(OptionRow, { option: row.option, index: row.index, listboxId: listboxId, isSelected: selectedIds.has(row.option.id.toString()), isActive: row.index === activeIndex, isMulti: (_a = props.isMulti) !== null && _a !== void 0 ? _a : false, theme: theme, backgrounds: rowBackgrounds, renderOption: renderOption, onSelect: handleSelect, onActivate: setActiveIndex }, row.key));
417
+ return row.kind === "header" ? ((0, jsx_runtime_1.jsx)(react_2.Box, { mx: spec.mx, px: spec.px, pt: 2, pb: 1, fontSize: "0.6875rem", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.03em", color: colors.textMuted, children: row.group }, row.key)) : ((0, jsx_runtime_1.jsx)(OptionRow, { option: row.option, index: row.index, listboxId: listboxId, isSelected: selectedIds.has(row.option.id.toString()), isActive: row.index === activeIndex, isMulti: (_a = props.isMulti) !== null && _a !== void 0 ? _a : false, theme: theme, backgrounds: rowBackgrounds, spec: spec, renderOption: renderOption, onSelect: handleSelect, onActivate: setActiveIndex }, row.key));
418
418
  })
419
419
  : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [topPadPx > 0 && (0, jsx_runtime_1.jsx)(react_2.Box, { height: `${topPadPx}px` }), renderedOptions.map((option, i) => {
420
420
  var _a;
421
- return ((0, jsx_runtime_1.jsx)(OptionRow, { option: option, index: startIndex + i, listboxId: listboxId, isSelected: selectedIds.has(option.id.toString()), isActive: startIndex + i === activeIndex, isMulti: (_a = props.isMulti) !== null && _a !== void 0 ? _a : false, theme: theme, backgrounds: rowBackgrounds, renderOption: renderOption, onSelect: handleSelect, onActivate: setActiveIndex }, option.id));
422
- }), bottomPadPx > 0 && (0, jsx_runtime_1.jsx)(react_2.Box, { height: `${bottomPadPx}px` })] })), isLoading && ((0, jsx_runtime_1.jsxs)(react_2.Box, { px: 3, py: 2, display: "flex", alignItems: "center", gap: 2, fontSize: "0.8125rem", color: colors.textMuted, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: "xs" }), loadingText] }))] })] }) })] }), hiddenValues.map((value) => ((0, jsx_runtime_1.jsx)("input", { type: "hidden", name: name, value: value }, value))), (0, jsx_runtime_1.jsx)(FieldMessage_1.default, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
421
+ return ((0, jsx_runtime_1.jsx)(OptionRow, { option: option, index: startIndex + i, listboxId: listboxId, isSelected: selectedIds.has(option.id.toString()), isActive: startIndex + i === activeIndex, isMulti: (_a = props.isMulti) !== null && _a !== void 0 ? _a : false, theme: theme, backgrounds: rowBackgrounds, spec: spec, renderOption: renderOption, onSelect: handleSelect, onActivate: setActiveIndex }, option.id));
422
+ }), bottomPadPx > 0 && (0, jsx_runtime_1.jsx)(react_2.Box, { height: `${bottomPadPx}px` })] })), isLoading && ((0, jsx_runtime_1.jsxs)(react_2.Box, { mx: spec.mx, px: spec.px, py: spec.py, display: "flex", alignItems: "center", gap: spec.gap, fontSize: spec.fontSize, color: colors.textMuted, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: "xs" }), loadingText] }))] })] }) })] }), hiddenValues.map((value) => ((0, jsx_runtime_1.jsx)("input", { type: "hidden", name: name, value: value }, value))), (0, jsx_runtime_1.jsx)(FieldMessage_1.default, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
423
423
  });
424
424
  exports.default = SelectPopup;
@@ -1,3 +1,7 @@
1
1
  import type { PaletteProps } from "../../Theme/tokens/types";
2
2
  /** Selection is carried by weight and a trailing brand check, so the label itself stays neutral. */
3
3
  export declare const optionRowInk: (colors: PaletteProps, isSingleSelected: boolean) => string;
4
+ export declare const optionRowDescriptionInk: (colors: PaletteProps) => string;
5
+ export declare const OPTION_ROW_REST_WEIGHT = 400;
6
+ export declare const OPTION_ROW_SELECTED_WEIGHT = 500;
7
+ export declare const optionRowWeight: (isSingleSelected: boolean) => number;
@@ -1,6 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.optionRowInk = void 0;
3
+ exports.optionRowWeight = exports.OPTION_ROW_SELECTED_WEIGHT = exports.OPTION_ROW_REST_WEIGHT = exports.optionRowDescriptionInk = exports.optionRowInk = void 0;
4
+ const accentText_1 = require("../../Theme/tokens/builders/accentText");
5
+ const outlineRung_1 = require("../../Theme/tokens/builders/outlineRung");
6
+ const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
4
7
  /** Selection is carried by weight and a trailing brand check, so the label itself stays neutral. */
5
8
  const optionRowInk = (colors, isSingleSelected) => isSingleSelected ? colors.text[900] : colors.text[700];
6
9
  exports.optionRowInk = optionRowInk;
10
+ // A 12px sub-line reached only 4.00:1 on the hovered selected row in dark.
11
+ const optionRowDescriptionInk = (colors) => (0, outlineRung_1.liftToContrast)(colors.textMuted, (0, rowStates_1.listRowBackgrounds)(colors).selectedActive, accentText_1.AA_NORMAL_TEXT);
12
+ exports.optionRowDescriptionInk = optionRowDescriptionInk;
13
+ exports.OPTION_ROW_REST_WEIGHT = 400;
14
+ // NOTE: 600 beside a tinted fill and a check read as three signals for one state.
15
+ exports.OPTION_ROW_SELECTED_WEIGHT = 500;
16
+ const optionRowWeight = (isSingleSelected) => isSingleSelected ? exports.OPTION_ROW_SELECTED_WEIGHT : exports.OPTION_ROW_REST_WEIGHT;
17
+ exports.optionRowWeight = optionRowWeight;
@@ -14,21 +14,25 @@ const useFieldIds_1 = require("../Common/useFieldIds");
14
14
  const FieldMessage_1 = __importDefault(require("../Common/FieldMessage"));
15
15
  const motion_1 = require("../../Theme/tokens/primitives/motion");
16
16
  const controlSizes_1 = require("../../Theme/tokens/primitives/controlSizes");
17
+ const optionRowSizes_1 = require("../../Theme/tokens/primitives/optionRowSizes");
17
18
  const motion_2 = require("../../Theme/chakra/motion");
18
19
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
19
20
  const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
21
+ const optionRowInk_1 = require("../Select/optionRowInk");
20
22
  const useExclusiveOverlay_1 = require("../../Hooks/useExclusiveOverlay");
21
23
  const lucide_react_1 = require("lucide-react");
22
24
  const ROW_TRANSITION = (0, motion_1.transitionFor)(["background-color"]);
23
- const RenderOptions = ({ isOptionLoading, filteredOptions, loadingText, handleOptionClick, isProfile, isColorOptions, isOptionSelected, activeIndex, optionRefs, }) => {
25
+ const RenderOptions = ({ isOptionLoading, filteredOptions, loadingText, handleOptionClick, isProfile, isColorOptions, isOptionSelected, activeIndex, optionRefs, size, isMultipleSelect, }) => {
24
26
  const theme = (0, useCustomTheme_1.useCustomTheme)();
25
27
  const rowBackgrounds = (0, react_1.useMemo)(() => (0, rowStates_1.listRowBackgrounds)(theme.colors), [theme.colors]);
28
+ const spec = (0, optionRowSizes_1.optionRowSpec)(size);
26
29
  if (isOptionLoading) {
27
30
  return ((0, jsx_runtime_1.jsxs)(react_2.VStack, { py: 2, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: "sm", color: theme.colors.accentText }), (0, jsx_runtime_1.jsx)(react_2.Text, { color: theme.colors.accentText, children: loadingText })] }));
28
31
  }
29
32
  if (filteredOptions === null || filteredOptions === void 0 ? void 0 : filteredOptions.length) {
30
33
  return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: filteredOptions.map((option, index) => {
31
34
  const selected = isOptionSelected(option);
35
+ const isSingleSelected = selected && !isMultipleSelect;
32
36
  const isActive = index === activeIndex;
33
37
  const rowBg = selected
34
38
  ? isActive
@@ -39,14 +43,14 @@ const RenderOptions = ({ isOptionLoading, filteredOptions, loadingText, handleOp
39
43
  : "transparent";
40
44
  return ((0, jsx_runtime_1.jsxs)(react_2.Box, { as: "button", type: "button", ref: (el) => {
41
45
  optionRefs.current[index] = el;
42
- }, role: "option", "aria-selected": selected, tabIndex: isActive ? 0 : -1, w: "100%", textAlign: "left", border: "none", fontFamily: "inherit", bg: rowBg, px: 4, py: 1.5, fontSize: "0.8125rem", cursor: "pointer", color: theme.colors.text[700], fontWeight: selected ? 600 : 400, transition: ROW_TRANSITION, sx: (0, motion_2.reducedMotionSx)(), _hover: {
46
+ }, role: "option", "aria-selected": selected, tabIndex: isActive ? 0 : -1, w: `calc(100% - 2 * ${spec.mx})`, minH: spec.minH, textAlign: "left", border: "none", fontFamily: "inherit", bg: rowBg, mx: spec.mx, px: spec.px, py: spec.py, borderRadius: optionRowSizes_1.OPTION_ROW_RADIUS, fontSize: spec.fontSize, cursor: "pointer", color: (0, optionRowInk_1.optionRowInk)(theme.colors, isSingleSelected), fontWeight: (0, optionRowInk_1.optionRowWeight)(isSingleSelected), transition: ROW_TRANSITION, sx: (0, motion_2.reducedMotionSx)(), _hover: {
43
47
  bg: selected ? rowBackgrounds.selectedActive : rowBackgrounds.active,
44
48
  }, onClick: () => handleOptionClick(option), onKeyDown: (e) => {
45
49
  if (e.key === "Enter" || e.key === " ") {
46
50
  e.preventDefault();
47
51
  handleOptionClick(option);
48
52
  }
49
- }, display: "flex", alignItems: "center", gap: "0.5rem", children: [isProfile && ((0, jsx_runtime_1.jsx)(react_2.Box, { children: (0, jsx_runtime_1.jsx)(Avatar_1.default, { size: "sm", name: option.label.toLocaleUpperCase(), src: option.profileUrl ? option.profileUrl : undefined }) })), isColorOptions && ((0, jsx_runtime_1.jsx)(react_2.Box, { width: "0.8rem", height: "0.8rem", content: "", backgroundColor: option === null || option === void 0 ? void 0 : option.color, borderRadius: "50%" })), (0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsx)(react_2.Box, { children: option.label }), option.email && ((0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.75rem", color: theme.colors.textMuted, children: option.email }))] })] }, option.id));
53
+ }, display: "flex", alignItems: "center", gap: spec.gap, children: [isProfile && ((0, jsx_runtime_1.jsx)(react_2.Box, { children: (0, jsx_runtime_1.jsx)(Avatar_1.default, { size: spec.avatarSize, name: option.label.toLocaleUpperCase(), src: option.profileUrl ? option.profileUrl : undefined }) })), isColorOptions && ((0, jsx_runtime_1.jsx)(react_2.Box, { width: "0.8rem", height: "0.8rem", content: "", backgroundColor: option === null || option === void 0 ? void 0 : option.color, borderRadius: "50%" })), (0, jsx_runtime_1.jsxs)(react_2.Box, { flex: 1, minW: 0, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { children: option.label }), option.email && ((0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: spec.descriptionFontSize, color: (0, optionRowInk_1.optionRowDescriptionInk)(theme.colors), children: option.email }))] }), isSingleSelected && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: lucide_react_1.Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 }))] }, option.id));
50
54
  }) }));
51
55
  }
52
56
  return ((0, jsx_runtime_1.jsx)(react_2.Text, { px: 4, py: 2, color: theme.colors.textMuted, children: "No options found" }));
@@ -230,7 +234,7 @@ function SelectSearch({ options, initialSelectedOption, onOptionSelect, inputOnc
230
234
  return;
231
235
  }
232
236
  openDropdown();
233
- }, onChange: (e) => handleInputChange(e.target.value), placeholder: placeholder, onKeyDown: handleKeyDown, name: name, role: "combobox", "aria-autocomplete": "list", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-required": isRequired || undefined, cursor: "pointer", sx: { ...(0, fieldStyles_1.fieldControlStyles)(theme.colors), ...(0, fieldStyles_1.controlSizeStyles)(size), ...(0, motion_2.reducedMotionSx)() }, style: inputStyle }), rightIcon && ((0, jsx_runtime_1.jsx)(react_2.InputRightElement, { pointerEvents: "none", children: rightIcon, style: { ...rightElementStyle } }))] }), isOpen && ((0, jsx_runtime_1.jsxs)(react_2.Box, { ref: dropdownRef, marginTop: "0.313rem", position: "absolute", top: position === "below" ? "100%" : "auto", bottom: position === "above" ? "100%" : "auto", left: 0, right: 0, border: "0.063rem solid", borderColor: theme.colors.boxborder[200], borderRadius: "xl", boxShadow: theme.shadows.lg, bg: theme.colors.surfaceRaised, maxHeight: "9.375rem", overflowY: "auto", zIndex: 10, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { id: listboxId, role: "listbox", "aria-label": label !== null && label !== void 0 ? label : placeholder, onKeyDown: handleListKeyDown, children: (0, jsx_runtime_1.jsx)(RenderOptions, { isOptionLoading: isOptionLoading, filteredOptions: filteredOptions, loadingText: loadingText, handleOptionClick: handleOptionClick, isProfile: isProfile, isColorOptions: isColorOptions, isOptionSelected: isOptionSelected, activeIndex: activeIndex, optionRefs: optionRefs }) }), (0, jsx_runtime_1.jsx)("hr", {}), isBottomIcon && ((0, jsx_runtime_1.jsxs)(react_2.Box, { position: "sticky", bottom: 0, background: theme.colors.surfaceRaised, display: "flex", alignItems: "center", pl: "1rem", cursor: "pointer", onClick: handleBottomClick, borderColor: theme.colors.boxborder[200], zIndex: 1, borderTop: `1px solid ${theme.colors.boxborder[200]}`, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { children: BottomIcon ? BottomIcon : (0, jsx_runtime_1.jsx)(lucide_react_1.PlusIcon, { size: "1rem" }) }), (0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.875rem", color: BottomTextColor
237
+ }, onChange: (e) => handleInputChange(e.target.value), placeholder: placeholder, onKeyDown: handleKeyDown, name: name, role: "combobox", "aria-autocomplete": "list", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-required": isRequired || undefined, cursor: "pointer", sx: { ...(0, fieldStyles_1.fieldControlStyles)(theme.colors), ...(0, fieldStyles_1.controlSizeStyles)(size), ...(0, motion_2.reducedMotionSx)() }, style: inputStyle }), rightIcon && ((0, jsx_runtime_1.jsx)(react_2.InputRightElement, { pointerEvents: "none", children: rightIcon, style: { ...rightElementStyle } }))] }), isOpen && ((0, jsx_runtime_1.jsxs)(react_2.Box, { ref: dropdownRef, marginTop: "0.313rem", position: "absolute", top: position === "below" ? "100%" : "auto", bottom: position === "above" ? "100%" : "auto", left: 0, right: 0, border: "0.063rem solid", borderColor: theme.colors.boxborder[200], borderRadius: "xl", boxShadow: theme.shadows.lg, bg: theme.colors.surfaceRaised, maxHeight: "9.375rem", overflowY: "auto", zIndex: 10, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { id: listboxId, role: "listbox", "aria-label": label !== null && label !== void 0 ? label : placeholder, onKeyDown: handleListKeyDown, children: (0, jsx_runtime_1.jsx)(RenderOptions, { isOptionLoading: isOptionLoading, filteredOptions: filteredOptions, loadingText: loadingText, handleOptionClick: handleOptionClick, isProfile: isProfile, isColorOptions: isColorOptions, isOptionSelected: isOptionSelected, activeIndex: activeIndex, optionRefs: optionRefs, size: size, isMultipleSelect: isMultipleSelect }) }), (0, jsx_runtime_1.jsx)("hr", {}), isBottomIcon && ((0, jsx_runtime_1.jsxs)(react_2.Box, { position: "sticky", bottom: 0, background: theme.colors.surfaceRaised, display: "flex", alignItems: "center", pl: "1rem", cursor: "pointer", onClick: handleBottomClick, borderColor: theme.colors.boxborder[200], zIndex: 1, borderTop: `1px solid ${theme.colors.boxborder[200]}`, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { children: BottomIcon ? BottomIcon : (0, jsx_runtime_1.jsx)(lucide_react_1.PlusIcon, { size: "1rem" }) }), (0, jsx_runtime_1.jsx)(react_2.Box, { fontSize: "0.875rem", color: BottomTextColor
234
238
  ? BottomTextColor
235
239
  : theme.colors.accentText, padding: "8px 8px", children: BottomText || "Add New" })] }))] })), (0, jsx_runtime_1.jsx)(FieldMessage_1.default, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }) }) }));
236
240
  }
@@ -1,3 +1,3 @@
1
1
  import { SwitchProps } from "./SwitchProps";
2
- /** @deprecated Use `SegmentedControl` — Switch renders it at 12.5rem, growing when the labels need it. */
2
+ /** @deprecated Two labelled choices; for a single on/off field use `ToggleSwitch`. Renders `SegmentedControl` at 12.5rem when labelled. */
3
3
  export default function Switch({ leftText, rightText, leftIcon, rightIcon, onSwitchToggle, switchActive, surface, "aria-label": ariaLabel, }: SwitchProps): import("react/jsx-runtime").JSX.Element;
@@ -7,11 +7,13 @@ exports.default = Switch;
7
7
  const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  const react_1 = require("@chakra-ui/react");
9
9
  const SegmentedControl_1 = __importDefault(require("../SegmentedControl/SegmentedControl"));
10
- /** @deprecated Use `SegmentedControl` — Switch renders it at 12.5rem, growing when the labels need it. */
10
+ /** @deprecated Two labelled choices; for a single on/off field use `ToggleSwitch`. Renders `SegmentedControl` at 12.5rem when labelled. */
11
11
  function Switch({ leftText, rightText, leftIcon, rightIcon, onSwitchToggle, switchActive, surface, "aria-label": ariaLabel, }) {
12
12
  const options = [
13
13
  { value: "left", label: leftText, icon: leftIcon },
14
14
  { value: "right", label: rightText, icon: rightIcon },
15
15
  ];
16
- return ((0, jsx_runtime_1.jsx)(react_1.Box, { w: "fit-content", minW: "12.5rem", children: (0, jsx_runtime_1.jsx)(SegmentedControl_1.default, { options: options, value: switchActive, onChange: onSwitchToggle, variant: "brandSlide", surface: surface, isFullWidth: true, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : `${leftText} or ${rightText}` }) }));
16
+ // Unlabelled segments have nothing to size to, so the 12.5rem floor only overflowed its cell.
17
+ const hasLabels = Boolean(leftText || rightText);
18
+ return ((0, jsx_runtime_1.jsx)(react_1.Box, { w: "fit-content", maxW: "100%", minW: hasLabels ? "12.5rem" : undefined, children: (0, jsx_runtime_1.jsx)(SegmentedControl_1.default, { options: options, value: switchActive, onChange: onSwitchToggle, variant: "brandSlide", surface: surface, isFullWidth: true, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : `${leftText} or ${rightText}` }) }));
17
19
  }
@@ -6,10 +6,13 @@ const react_2 = require("@chakra-ui/react");
6
6
  const lucide_react_1 = require("lucide-react");
7
7
  const useCustomTheme_1 = require("../../../Theme/useCustomTheme");
8
8
  const rowStates_1 = require("../../../Theme/tokens/builders/rowStates");
9
+ const optionRowSizes_1 = require("../../../Theme/tokens/primitives/optionRowSizes");
10
+ const optionRowInk_1 = require("../../Select/optionRowInk");
9
11
  const useExclusiveOverlay_1 = require("../../../Hooks/useExclusiveOverlay");
10
12
  const CompactSelect = ({ options, value, placeholder = "Select", height = "22px", onSelect, size = "xs" }) => {
11
13
  const theme = (0, useCustomTheme_1.useCustomTheme)();
12
14
  const rowBackgrounds = (0, react_1.useMemo)(() => (0, rowStates_1.listRowBackgrounds)(theme.colors), [theme.colors]);
15
+ const spec = (0, optionRowSizes_1.optionRowSpec)("xs");
13
16
  const [isOpen, setIsOpen] = (0, react_1.useState)(false);
14
17
  const requestExclusiveOpen = (0, useExclusiveOverlay_1.useExclusiveOverlay)(isOpen, () => setIsOpen(false));
15
18
  const openDropdown = () => {
@@ -63,15 +66,15 @@ const CompactSelect = ({ options, value, placeholder = "Select", height = "22px"
63
66
  : isActive
64
67
  ? rowBackgrounds.active
65
68
  : theme.colors.transparent;
66
- return ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "button", type: "button", role: "option", "aria-selected": selected, tabIndex: isActive ? 0 : -1, ref: (el) => {
69
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { as: "button", type: "button", role: "option", "aria-selected": selected, tabIndex: isActive ? 0 : -1, ref: (el) => {
67
70
  optionRefs.current[i] = el;
68
- }, w: "100%", textAlign: "left", border: "none", fontFamily: "inherit", px: 2, py: 1, cursor: "pointer", color: theme.colors.text[700], _hover: {
71
+ }, w: `calc(100% - 2 * ${spec.mx})`, minH: spec.minH, textAlign: "left", border: "none", fontFamily: "inherit", display: "flex", alignItems: "center", gap: spec.gap, mx: spec.mx, px: spec.px, py: spec.py, borderRadius: optionRowSizes_1.OPTION_ROW_RADIUS, cursor: "pointer", color: (0, optionRowInk_1.optionRowInk)(theme.colors, selected), fontSize: spec.fontSize, fontWeight: (0, optionRowInk_1.optionRowWeight)(selected), _hover: {
69
72
  bg: selected ? rowBackgrounds.selectedActive : rowBackgrounds.active,
70
73
  }, bg: rowBg, onClick: () => {
71
74
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(opt);
72
75
  setIsOpen(false);
73
76
  setQuery("");
74
- }, children: (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.75rem", children: opt.label }) }, opt.id));
77
+ }, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { flex: 1, minW: 0, isTruncated: true, children: opt.label }), selected && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: lucide_react_1.Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 }))] }, opt.id));
75
78
  }), filtered.length === 0 && ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 2, py: 2, children: (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.75rem", color: theme.colors.textMuted, children: "No results" }) }))] })] }) }) }) })] }));
76
79
  };
77
80
  exports.default = CompactSelect;