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
@@ -18,6 +18,12 @@ export declare const Menu: {
18
18
  borderColor: string;
19
19
  };
20
20
  item: {
21
+ minH: string;
22
+ px: string;
23
+ py: string;
24
+ mx: string;
25
+ fontSize: string;
26
+ borderRadius: string;
21
27
  bg: string;
22
28
  _focus: {
23
29
  [$itemBg.variable]: string;
@@ -4,7 +4,9 @@ exports.Menu = void 0;
4
4
  const react_1 = require("@chakra-ui/react");
5
5
  const anatomy_1 = require("@chakra-ui/anatomy");
6
6
  const rowStates_1 = require("../tokens/builders/rowStates");
7
+ const optionRowSizes_1 = require("../tokens/primitives/optionRowSizes");
7
8
  const paletteFromTheme_1 = require("./paletteFromTheme");
9
+ const itemRowSpec = optionRowSizes_1.OPTION_ROW_SIZES.sm;
8
10
  const { defineMultiStyleConfig, definePartsStyle } = (0, react_1.createMultiStyleConfigHelpers)(anatomy_1.menuAnatomy.keys);
9
11
  const $bg = (0, react_1.cssVar)("menu-bg");
10
12
  // NOTE: fixes Chakra's MenuList/MenuItem both hardcoding --menu-bg to white via dead _dark — item needs its own var too, or hover snaps back to white.
@@ -24,6 +26,12 @@ const baseStyle = definePartsStyle(({ theme }) => {
24
26
  borderColor: colors.boxborder[200],
25
27
  },
26
28
  item: {
29
+ minH: itemRowSpec.minH,
30
+ px: itemRowSpec.px,
31
+ py: itemRowSpec.py,
32
+ mx: itemRowSpec.mx,
33
+ fontSize: itemRowSpec.fontSize,
34
+ borderRadius: optionRowSizes_1.OPTION_ROW_RADIUS,
27
35
  bg: "transparent",
28
36
  _focus: { [$itemBg.variable]: hoverBg, bg: $itemBg.reference },
29
37
  _hover: { [$itemBg.variable]: hoverBg, bg: $itemBg.reference },
@@ -176,6 +176,12 @@ export declare const componentStyles: {
176
176
  borderColor: string;
177
177
  };
178
178
  item: {
179
+ minH: string;
180
+ px: string;
181
+ py: string;
182
+ mx: string;
183
+ fontSize: string;
184
+ borderRadius: string;
179
185
  bg: string;
180
186
  _focus: {
181
187
  [x: string]: string;
@@ -4,6 +4,8 @@ export declare const AA_NON_TEXT = 3;
4
4
  export declare const CHROME_BORDER = 1.5;
5
5
  export declare const CHROME_RULE = 1.31;
6
6
  export declare function ruleInk(hex: string, surfaces: readonly string[], target: number): string;
7
+ export declare const STACKED_STATE_STEP = 1.08;
8
+ export declare function tintStep(seed: string, backdrop: string, target: number): string;
7
9
  export declare function liftToContrast(hex: string, backdrop: string, target?: number): string;
8
10
  export declare function softenToContrast(hex: string, backdrop: string, target: number): string;
9
11
  export declare function withOutlineRung(palette: PaletteProps): PaletteProps;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CHROME_RULE = exports.CHROME_BORDER = exports.AA_NON_TEXT = exports.MIN_PERCEPTIBLE_STEP = void 0;
3
+ exports.STACKED_STATE_STEP = exports.CHROME_RULE = exports.CHROME_BORDER = exports.AA_NON_TEXT = exports.MIN_PERCEPTIBLE_STEP = void 0;
4
4
  exports.ruleInk = ruleInk;
5
+ exports.tintStep = tintStep;
5
6
  exports.liftToContrast = liftToContrast;
6
7
  exports.softenToContrast = softenToContrast;
7
8
  exports.withOutlineRung = withOutlineRung;
@@ -20,6 +21,18 @@ function ruleInk(hex, surfaces, target) {
20
21
  ? softenToContrast(hex, loudest, target)
21
22
  : hex;
22
23
  }
24
+ exports.STACKED_STATE_STEP = 1.08;
25
+ // NOTE: walks out from the backdrop, so it settles a too-loud seed down as readily as it lifts a faint one.
26
+ function tintStep(seed, backdrop, target) {
27
+ const { s } = (0, color_1.hexToHsl)(seed);
28
+ const away = (0, color_1.relativeLuminance)(backdrop) < 0.5 ? 1 : -1;
29
+ for (let l = (0, color_1.hexToHsl)(backdrop).l; l >= 0 && l <= 100; l += away) {
30
+ const next = (0, color_1.withLightness)(seed, l, s);
31
+ if ((0, color_1.contrastRatio)(next, backdrop) >= target)
32
+ return next;
33
+ }
34
+ return (0, color_1.withLightness)(seed, away > 0 ? 100 : 0, s);
35
+ }
23
36
  function liftToContrast(hex, backdrop, target = exports.AA_NON_TEXT) {
24
37
  if ((0, color_1.contrastRatio)(hex, backdrop) >= target)
25
38
  return hex;
@@ -2,14 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listRowBackgrounds = void 0;
4
4
  const outlineRung_1 = require("./outlineRung");
5
- // NOTE: rungs stay neutral/pale — seeding from primary[500] instead once painted SelectV2's hover solid brand fill.
6
5
  const listRowBackgrounds = (colors) => {
7
- // Dark aliases backgroundColor.tertiary to the panel, leaving the selected row at 1.025:1.
8
- const selected = (0, outlineRung_1.liftToContrast)(colors.backgroundColor.tertiary, colors.surfaceRaised, outlineRung_1.MIN_PERCEPTIBLE_STEP);
6
+ // Raw tertiary reaches 1.29:1 and reads as pressed; dark aliases it to the panel at 1.025:1.
7
+ const selected = (0, outlineRung_1.tintStep)(colors.backgroundColor.tertiary, colors.surfaceRaised, outlineRung_1.MIN_PERCEPTIBLE_STEP);
9
8
  return {
10
9
  active: (0, outlineRung_1.resolveHoverTint)(colors.gray[50], colors.surfaceRaised),
11
10
  selected,
12
- selectedActive: (0, outlineRung_1.resolveHoverTint)(selected, selected),
11
+ selectedActive: (0, outlineRung_1.tintStep)(selected, selected, outlineRung_1.STACKED_STATE_STEP),
13
12
  };
14
13
  };
15
14
  exports.listRowBackgrounds = listRowBackgrounds;
@@ -0,0 +1,20 @@
1
+ import type { ControlSize } from "./controlSizes";
2
+ export type OptionRowSpec = {
3
+ minH: string;
4
+ px: string;
5
+ py: string;
6
+ gap: string;
7
+ /** Inset so the row's rounded fill stops short of the popup edge. */
8
+ mx: string;
9
+ fontSize: string;
10
+ descriptionFontSize: string;
11
+ iconSize: number;
12
+ /** Avatar token for rows that lead with one — an `sm` avatar alone outgrows an `sm` row. */
13
+ avatarSize: "2xs" | "xs" | "sm";
14
+ /** Fixed row height the virtualiser scrolls by; must match a single-line row. */
15
+ virtualRowHeightPx: number;
16
+ };
17
+ /** One density for every option row — Select, SearchSelect, SelectSearch, table filters and Chakra's MenuItem. */
18
+ export declare const OPTION_ROW_SIZES: Readonly<Record<ControlSize, OptionRowSpec>>;
19
+ export declare const OPTION_ROW_RADIUS = "md";
20
+ export declare const optionRowSpec: (size: ControlSize) => OptionRowSpec;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.optionRowSpec = exports.OPTION_ROW_RADIUS = exports.OPTION_ROW_SIZES = void 0;
4
+ /** One density for every option row — Select, SearchSelect, SelectSearch, table filters and Chakra's MenuItem. */
5
+ exports.OPTION_ROW_SIZES = {
6
+ xs: {
7
+ minH: "1.5rem", px: "0.5rem", py: "0.125rem", gap: "0.375rem", mx: "0.25rem",
8
+ fontSize: "0.75rem", descriptionFontSize: "0.6875rem", iconSize: 12,
9
+ avatarSize: "2xs", virtualRowHeightPx: 24,
10
+ },
11
+ sm: {
12
+ minH: "1.875rem", px: "0.625rem", py: "0.25rem", gap: "0.5rem", mx: "0.375rem",
13
+ fontSize: "0.8125rem", descriptionFontSize: "0.75rem", iconSize: 14,
14
+ avatarSize: "xs", virtualRowHeightPx: 30,
15
+ },
16
+ md: {
17
+ minH: "2.125rem", px: "0.75rem", py: "0.3125rem", gap: "0.5rem", mx: "0.375rem",
18
+ fontSize: "0.875rem", descriptionFontSize: "0.75rem", iconSize: 16,
19
+ avatarSize: "xs", virtualRowHeightPx: 34,
20
+ },
21
+ lg: {
22
+ minH: "2.5rem", px: "0.875rem", py: "0.375rem", gap: "0.625rem", mx: "0.5rem",
23
+ fontSize: "1rem", descriptionFontSize: "0.8125rem", iconSize: 18,
24
+ avatarSize: "sm", virtualRowHeightPx: 40,
25
+ },
26
+ };
27
+ exports.OPTION_ROW_RADIUS = "md";
28
+ const optionRowSpec = (size) => exports.OPTION_ROW_SIZES[size];
29
+ exports.optionRowSpec = optionRowSpec;
@@ -9,16 +9,17 @@ import { useExclusiveOverlay } from "../../Hooks/useExclusiveOverlay.js";
9
9
  import { ChevronDown } from "lucide-react";
10
10
  import { rafThrottle } from "../../Utils/rafThrottle.js";
11
11
  import { CONTROL_SIZES } from "../../Theme/tokens/primitives/controlSizes.js";
12
+ import { optionRowSpec, OPTION_ROW_RADIUS } from "../../Theme/tokens/primitives/optionRowSizes.js";
12
13
  import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
13
14
  import { reducedMotionSx } from "../../Theme/chakra/motion.js";
14
15
  import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
15
16
  const MotionBox = motion(forwardRef((props, ref) => (_jsx(Box, { ref: ref, ...props }))));
16
17
  const SIZE_MAP = {
17
- xs: { btn: "xs", fontSize: CONTROL_SIZES.xs.fontSize, py: 1, px: 2.5, img: "1rem", chevron: CONTROL_SIZES.xs.iconSize, minW: "8rem", gap: 1.5 },
18
- sm: { btn: "sm", fontSize: CONTROL_SIZES.sm.fontSize, py: 1.5, px: 3, img: "1rem", chevron: CONTROL_SIZES.sm.iconSize, minW: "9rem", gap: 2 },
19
- md: { btn: "md", fontSize: CONTROL_SIZES.md.fontSize, py: 2, px: 3, img: "1.125rem", chevron: CONTROL_SIZES.md.iconSize, minW: "11rem", gap: 2 },
20
- lg: { btn: "lg", fontSize: CONTROL_SIZES.lg.fontSize, py: 2.5, px: 3.5, img: "1.25rem", chevron: CONTROL_SIZES.lg.iconSize, minW: "12rem", gap: 2.5 },
21
- xl: { btn: "lg", fontSize: "1.0625rem", py: 3, px: 4, img: "1.4rem", chevron: 20, minW: "13rem", gap: 3 },
18
+ xs: { btn: "xs", img: "1rem", chevron: CONTROL_SIZES.xs.iconSize, minW: "8rem", gap: 1.5, rowSize: "xs" },
19
+ sm: { btn: "sm", img: "1rem", chevron: CONTROL_SIZES.sm.iconSize, minW: "9rem", gap: 2, rowSize: "sm" },
20
+ md: { btn: "md", img: "1.125rem", chevron: CONTROL_SIZES.md.iconSize, minW: "11rem", gap: 2, rowSize: "md" },
21
+ lg: { btn: "lg", img: "1.25rem", chevron: CONTROL_SIZES.lg.iconSize, minW: "12rem", gap: 2.5, rowSize: "lg" },
22
+ xl: { btn: "lg", img: "1.4rem", chevron: 20, minW: "13rem", gap: 3, rowSize: "lg" },
22
23
  };
23
24
  const TRIGGER_TRANSITION = transitionFor(["background-color", "border-color", "color", "box-shadow"]);
24
25
  const ROW_TRANSITION = transitionFor(["background-color", "color"]);
@@ -35,6 +36,7 @@ const Dropdown = forwardRef(({ dropDownButtonStyle, ButtonText, options, handleO
35
36
  const menuRef = useRef(null);
36
37
  const [menuPos, setMenuPos] = useState({ top: 0, left: 0, width: 0 });
37
38
  const s = SIZE_MAP[(_a = size !== null && size !== void 0 ? size : optionsSize) !== null && _a !== void 0 ? _a : "sm"];
39
+ const rowSpec = optionRowSpec(s.rowSize);
38
40
  const prefersReducedMotion = useReducedMotion();
39
41
  const menuId = useId();
40
42
  // Roving-tabindex keyboard nav for the menu (ARIA APG menu-button pattern):
@@ -230,7 +232,7 @@ const Dropdown = forwardRef(({ dropDownButtonStyle, ButtonText, options, handleO
230
232
  const Chevron = isVisibleIconShow ? (_jsx(Box, { as: "span", display: "inline-flex", transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", sx: reducedMotionSx(), children: _jsx(ChevronDown, { size: s.chevron }) })) : undefined;
231
233
  const dropDownToggleOption = () => {
232
234
  const ButtonToggle = (_jsx(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, ...reducedMotionSx(), ...dropDownButtonStyle }, ...buttonProps, children: _jsxs(Box, { as: "span", display: "inline-flex", alignItems: "center", gap: DropdownIcon && ButtonText ? s.gap : 0, children: [DropdownIcon, ButtonText] }) }));
233
- const TextToggle = (_jsxs(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: { ...focusVisibleStyles(theme.colors), ...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] }));
235
+ const TextToggle = (_jsxs(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: { ...focusVisibleStyles(theme.colors), ...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] }));
234
236
  return dropdownType === "button" ? ButtonToggle : TextToggle;
235
237
  };
236
238
  const DropdownContentRender = () => {
@@ -240,7 +242,7 @@ const Dropdown = forwardRef(({ dropDownButtonStyle, ButtonText, options, handleO
240
242
  }, role: "menuitem", tabIndex: index === activeIndex ? 0 : -1, onClick: () => {
241
243
  handleListItemClick(option.id, option.label);
242
244
  focusTrigger();
243
- }, 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: {
245
+ }, onKeyDown: (e) => handleItemKeyDown(e, option.id, option.label), mx: rowSpec.mx, px: rowSpec.px, py: rowSpec.py, minH: rowSpec.minH, borderRadius: OPTION_ROW_RADIUS, display: "flex", alignItems: "center", gap: rowSpec.gap, fontSize: rowSpec.fontSize, cursor: "pointer", color: theme.colors.text[700], transition: ROW_TRANSITION, _hover: {
244
246
  bg: hoverTint,
245
247
  color: theme.colors.accentText,
246
248
  }, _focus: {
@@ -253,7 +255,7 @@ const Dropdown = forwardRef(({ dropDownButtonStyle, ButtonText, options, handleO
253
255
  boxShadow: `inset 0 0 0 2px ${theme.colors.focusRing.ring}`,
254
256
  }, borderBottom: divider
255
257
  ? `0.063rem solid ${theme.colors.boxborder[200]}`
256
- : undefined, sx: { "&:last-child": { borderBottom: "none" }, ...reducedMotionSx() }, style: ItemStyle, fontWeight: 500, children: [option.image && (_jsx(Box, { as: option.image, boxSize: s.img, flexShrink: 0, color: theme.colors.textMuted, style: ImageStyle })), _jsx(Box, { as: "span", fontSize: s.fontSize, isTruncated: true, style: LabelStyle, children: option.label })] }, option.id))) }));
258
+ : undefined, sx: { "&:last-child": { borderBottom: "none" }, ...reducedMotionSx() }, style: ItemStyle, fontWeight: 500, children: [option.image && (_jsx(Box, { as: option.image, boxSize: s.img, flexShrink: 0, color: theme.colors.textMuted, style: ImageStyle })), _jsx(Box, { as: "span", fontSize: rowSpec.fontSize, isTruncated: true, style: LabelStyle, children: option.label })] }, option.id))) }));
257
259
  };
258
260
  return (_jsxs(Box, { ref: dropdownRef, position: "relative", display: "inline-block", children: [dropDownToggleOption(), isOpen && (_jsx(Portal, { children: _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() }) }))] }));
259
261
  });
@@ -6,6 +6,8 @@ import { TextLabel } from "../Common/FormLabel.js";
6
6
  import Tag from "../Tag/Tag.js";
7
7
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
8
8
  import { listRowBackgrounds } from "../../Theme/tokens/builders/rowStates.js";
9
+ import { optionRowSpec } from "../../Theme/tokens/primitives/optionRowSizes.js";
10
+ import { optionRowInk, optionRowWeight } from "../Select/optionRowInk.js";
9
11
  import { useExclusiveOverlay } from "../../Hooks/useExclusiveOverlay.js";
10
12
  import FieldMessage from "../Common/FieldMessage.js";
11
13
  import { useFieldIds } from "../Common/useFieldIds.js";
@@ -37,6 +39,7 @@ const normalizeMultiValue = (val) => {
37
39
  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, }) => {
38
40
  var _a, _b, _c, _d, _e;
39
41
  const s = getSearchSelectSizeStyles(size);
42
+ const spec = optionRowSpec(size);
40
43
  const emitChange = onChange !== null && onChange !== void 0 ? onChange : onSelect;
41
44
  const insideSelectBoxHeight = height !== null && height !== void 0 ? height : s.insideSelectDefaultHeight;
42
45
  const [inputValue, setInputValue] = useState(searchQuery);
@@ -456,16 +459,14 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
456
459
  }, children: [_jsxs(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 ? (_jsx(SelectTruncatedLabel, { label: insideLabel, maxWidth: insideSelect.width || "6rem", fontSize: s.fieldFontSize })) : (_jsx(Text, { fontSize: s.fieldFontSize, fontWeight: 500, color: theme.colors.textMuted, isTruncated: true, maxW: insideSelect.width || "6rem", children: insidePlaceholder })), _jsx(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: _jsx(ChevronDown, { size: s.chevronSize }) })] }), customSelectOpen && (_jsx(Portal, { children: _jsx(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) => {
457
460
  var _a;
458
461
  const selected = ((_a = insideSelect.value) === null || _a === void 0 ? void 0 : _a.id) === item.id;
459
- return (_jsxs(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
460
- ? theme.colors.accentText
461
- : theme.colors.text[700], bg: selected ? rowBackgrounds.selected : "transparent", _hover: {
462
+ return (_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: spec.gap, minH: spec.minH, py: spec.py, px: spec.px, fontSize: spec.fontSize, fontWeight: optionRowWeight(selected), color: optionRowInk(theme.colors, selected), bg: selected ? rowBackgrounds.selected : "transparent", _hover: {
462
463
  bg: selected
463
464
  ? rowBackgrounds.selectedActive
464
465
  : rowBackgrounds.active,
465
466
  }, cursor: "pointer", onClick: (e) => {
466
467
  e.stopPropagation();
467
468
  handleCustomSelect(item);
468
- }, children: [_jsx(Text, { isTruncated: true, children: item.label }), selected && (_jsx(Check, { size: s.chevronSize - 2, color: theme.colors.accentText }))] }, item.id));
469
+ }, children: [_jsx(Text, { isTruncated: true, children: item.label }), selected && (_jsx(Check, { size: spec.iconSize, color: theme.colors.accentText }))] }, item.id));
469
470
  }) }) }))] }));
470
471
  })(), isMultiple
471
472
  ? selectedValues
@@ -497,16 +498,20 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
497
498
  else {
498
499
  openMenu();
499
500
  }
500
- }, "aria-label": "toggle-dropdown", display: "flex", alignItems: "center", borderRadius: "sm", color: theme.colors.textMuted, cursor: isDisabled ? "not-allowed" : "pointer", sx: focusVisibleStyles(theme.colors), children: isOpen ? (_jsx(ChevronUp, { size: s.chevronSize })) : (_jsx(ChevronDown, { size: s.chevronSize })) }))] })] }) }), isOpen && (_jsx(Portal, { children: _jsxs(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 && (_jsxs(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: [_jsx(Checkbox, { isChecked: allFilteredSelected, sx: { pointerEvents: "none" }, size: s.checkboxSize }), _jsx(Text, { fontSize: s.dropdownTextFontSize, children: allFilteredSelected ? "Unselect All" : "Select All" })] })), _jsx(Box, { flex: "1", overflowY: "auto", onScroll: handleScroll, maxH: "15rem", borderBottom: "0.063rem solid", borderColor: theme.colors.boxborder[200], children: isOptionLoading && filteredOptions.length === 0 ? (_jsxs(Box, { py: s.optionRowPy, px: 3, display: "flex", alignItems: "center", gap: 2, minH: s.optionRowMinH, color: theme.colors.textMuted, children: [_jsx(Spinner, { size: s.spinnerSize }), _jsx(Text, { fontSize: s.dropdownTextFontSize, children: loadingText })] })) : !isOptionLoading && filteredOptions.length === 0 ? (_jsx(Text, { p: 3, fontSize: s.dropdownTextFontSize, color: theme.colors.textMuted, children: "No results found" })) : (_jsxs(_Fragment, { children: [filteredOptions.map((option, index) => (_jsxs(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)
501
- ? index === activeIndex
502
- ? rowBackgrounds.selectedActive
503
- : rowBackgrounds.selected
504
- : index === activeIndex
505
- ? rowBackgrounds.active
506
- : "transparent", _hover: {
507
- bg: isSelected(option.id)
508
- ? rowBackgrounds.selectedActive
509
- : rowBackgrounds.active,
510
- }, 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 && (_jsx(Checkbox, { isChecked: isSelected(option.id), sx: { pointerEvents: "none" }, size: s.checkboxSize })), option.view ? (_jsx(_Fragment, { children: option.view })) : (_jsx(Text, { fontSize: s.dropdownTextFontSize, children: option.label }))] }, option.id))), ((pagination === null || pagination === void 0 ? void 0 : pagination.scrollLoading) || (isOptionLoading && filteredOptions.length > 0)) && (_jsxs(Box, { py: s.optionRowPy, px: 3, display: "flex", alignItems: "center", justifyContent: "center", gap: 2, minH: s.optionRowMinH, color: theme.colors.textMuted, children: [_jsx(Spinner, { size: s.spinnerSize }), _jsx(Text, { fontSize: s.dropdownTextFontSize, children: loadingText })] }))] })) }), (addNew === null || addNew === void 0 ? void 0 : addNew.enabled) && (_jsxs(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 || _jsx(Plus, { size: s.addNewIconSize }), _jsx(Text, { fontSize: s.dropdownTextFontSize, children: addNew.text || "Add New" })] }))] }) })), _jsx(FieldMessage, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
501
+ }, "aria-label": "toggle-dropdown", display: "flex", alignItems: "center", borderRadius: "sm", color: theme.colors.textMuted, cursor: isDisabled ? "not-allowed" : "pointer", sx: focusVisibleStyles(theme.colors), children: isOpen ? (_jsx(ChevronUp, { size: s.chevronSize })) : (_jsx(ChevronDown, { size: s.chevronSize })) }))] })] }) }), isOpen && (_jsx(Portal, { children: _jsxs(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 && (_jsxs(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: [_jsx(Checkbox, { isChecked: allFilteredSelected, sx: { pointerEvents: "none" }, size: s.checkboxSize }), _jsx(Text, { fontSize: spec.fontSize, children: allFilteredSelected ? "Unselect All" : "Select All" })] })), _jsx(Box, { flex: "1", overflowY: "auto", onScroll: handleScroll, maxH: "15rem", borderBottom: "0.063rem solid", borderColor: theme.colors.boxborder[200], children: isOptionLoading && filteredOptions.length === 0 ? (_jsxs(Box, { py: spec.py, px: spec.px, display: "flex", alignItems: "center", gap: spec.gap, minH: spec.minH, color: theme.colors.textMuted, children: [_jsx(Spinner, { size: s.spinnerSize }), _jsx(Text, { fontSize: spec.fontSize, children: loadingText })] })) : !isOptionLoading && filteredOptions.length === 0 ? (_jsx(Text, { p: 3, fontSize: spec.fontSize, color: theme.colors.textMuted, children: "No results found" })) : (_jsxs(_Fragment, { children: [filteredOptions.map((option, index) => {
502
+ const selected = isSelected(option.id);
503
+ const singleSelected = selected && !isMultiple;
504
+ return (_jsxs(Box, { id: getOptionId(index), role: "option", "aria-selected": selected, py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", bg: selected
505
+ ? index === activeIndex
506
+ ? rowBackgrounds.selectedActive
507
+ : rowBackgrounds.selected
508
+ : index === activeIndex
509
+ ? rowBackgrounds.active
510
+ : "transparent", _hover: {
511
+ bg: selected ? rowBackgrounds.selectedActive : rowBackgrounds.active,
512
+ }, onClick: () => handleSelect(option), onMouseEnter: () => setActiveIndex(index), display: "flex", alignItems: "center", gap: spec.gap, color: optionRowInk(theme.colors, singleSelected),
513
+ // `view` is consumer JSX with no size of its own; it inherited the 16px body font.
514
+ fontSize: spec.fontSize, fontWeight: optionRowWeight(singleSelected), borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [isMultiple && (_jsx(Checkbox, { isChecked: selected, sx: { pointerEvents: "none" }, size: s.checkboxSize })), option.view ? (_jsx(_Fragment, { children: option.view })) : (_jsx(Text, { isTruncated: true, children: option.label })), singleSelected && (_jsx(Box, { as: Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 }))] }, option.id));
515
+ }), ((pagination === null || pagination === void 0 ? void 0 : pagination.scrollLoading) || (isOptionLoading && filteredOptions.length > 0)) && (_jsxs(Box, { py: spec.py, px: spec.px, display: "flex", alignItems: "center", justifyContent: "center", gap: spec.gap, minH: spec.minH, color: theme.colors.textMuted, children: [_jsx(Spinner, { size: s.spinnerSize }), _jsx(Text, { fontSize: spec.fontSize, children: loadingText })] }))] })) }), (addNew === null || addNew === void 0 ? void 0 : addNew.enabled) && (_jsxs(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 || _jsx(Plus, { size: s.addNewIconSize }), _jsx(Text, { fontSize: spec.fontSize, children: addNew.text || "Add New" })] }))] }) })), _jsx(FieldMessage, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
511
516
  };
512
517
  export default SearchSelect;
@@ -18,9 +18,6 @@ const SIZE_STYLES = {
18
18
  clearIconSize: 10,
19
19
  addNewIconSize: 14,
20
20
  insideSelectMarginLeft: "-0.5rem",
21
- dropdownTextFontSize: "xs",
22
- optionRowMinH: "1.5rem",
23
- optionRowPy: 1.5,
24
21
  checkboxSize: "sm",
25
22
  },
26
23
  sm: {
@@ -39,9 +36,6 @@ const SIZE_STYLES = {
39
36
  clearIconSize: 12,
40
37
  addNewIconSize: 16,
41
38
  insideSelectMarginLeft: "-0.625rem",
42
- dropdownTextFontSize: "sm",
43
- optionRowMinH: "2rem",
44
- optionRowPy: 2,
45
39
  checkboxSize: "sm",
46
40
  },
47
41
  md: {
@@ -60,9 +54,6 @@ const SIZE_STYLES = {
60
54
  clearIconSize: 12,
61
55
  addNewIconSize: 16,
62
56
  insideSelectMarginLeft: "-0.75rem",
63
- dropdownTextFontSize: "sm",
64
- optionRowMinH: "2.5rem",
65
- optionRowPy: 2,
66
57
  checkboxSize: "md",
67
58
  },
68
59
  lg: {
@@ -81,9 +72,6 @@ const SIZE_STYLES = {
81
72
  clearIconSize: 14,
82
73
  addNewIconSize: 18,
83
74
  insideSelectMarginLeft: "-0.875rem",
84
- dropdownTextFontSize: "md",
85
- optionRowMinH: "2.75rem",
86
- optionRowPy: 2.5,
87
75
  checkboxSize: "lg",
88
76
  },
89
77
  };
@@ -5,7 +5,8 @@ import { Check, ChevronDown, X } from "lucide-react";
5
5
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
6
6
  import { focusGlowShadow } from "../../Theme/chakra/focusRing.styles.js";
7
7
  import { listRowBackgrounds } from "../../Theme/tokens/builders/rowStates.js";
8
- import { optionRowInk } from "./optionRowInk.js";
8
+ import { optionRowDescriptionInk, optionRowInk, optionRowWeight } from "./optionRowInk.js";
9
+ import { optionRowSpec } from "../../Theme/tokens/primitives/optionRowSizes.js";
9
10
  import { scrollbarStyles } from "../../Theme/tokens/builders/scrollbar.js";
10
11
  import { CONTROL_SIZES, DEFAULT_CONTROL_SIZE } from "../../Theme/tokens/primitives/controlSizes.js";
11
12
  import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
@@ -21,8 +22,6 @@ const CHEVRON_TRANSITION = transitionFor(["transform"], "base");
21
22
  const PAGE_STEP = 8;
22
23
  const LOAD_MORE_THRESHOLD_PX = 8;
23
24
  const DEFAULT_MAX_VISIBLE_CHIPS = 6;
24
- // NOTE: approximate single-row height (px 2.5 + ~13px text); used only to window `virtualizeAfter`, never for layout.
25
- const VIRTUAL_ROW_HEIGHT_PX = 34;
26
25
  const VIRTUAL_OVERSCAN = 4;
27
26
  const CHIP_TAG_HEIGHT_PX = { xs: 20, sm: 24 };
28
27
  const controlHeightPx = (size) => parseFloat(CONTROL_SIZES[size].height) * 16;
@@ -39,11 +38,11 @@ const parseRemToPx = (value, fallbackPx) => {
39
38
  const match = /^(\d*\.?\d+)rem$/.exec(value);
40
39
  return match ? parseFloat(match[1]) * 16 : fallbackPx;
41
40
  };
42
- const OptionRow = React.memo(function OptionRow({ option, index, listboxId, isSelected, isActive, isMulti, theme, backgrounds, renderOption, onSelect, onActivate, }) {
41
+ const OptionRow = React.memo(function OptionRow({ option, index, listboxId, isSelected, isActive, isMulti, theme, backgrounds, spec, renderOption, onSelect, onActivate, }) {
43
42
  const isSingleSelected = isSelected && !isMulti;
44
43
  const selectedBg = isActive ? backgrounds.selectedActive : backgrounds.selected;
45
44
  const restBg = isActive ? backgrounds.active : "transparent";
46
- return (_jsxs(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: optionRowInk(theme.colors, isSingleSelected), bg: isSingleSelected ? selectedBg : restBg, transition: ROW_TRANSITION, sx: reducedMotionSx(), children: [isMulti ? (_jsx(Checkbox, { id: `${optionDomId(listboxId, option.id)}-checkbox`, isChecked: isSelected, isDisabled: option.isDisabled, pointerEvents: "none", tabIndex: -1 })) : null, renderOption ? (_jsx(Box, { flex: 1, minW: 0, children: renderOption(option, { isSelected, isActive }) })) : (_jsxs(_Fragment, { children: [option.icon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, children: option.icon })) : null, _jsxs(Box, { flex: 1, minW: 0, isTruncated: true, children: [_jsx(Box, { as: "span", isTruncated: true, children: option.label }), option.description ? (_jsx(Box, { as: "span", display: "block", fontSize: "0.75rem", color: theme.colors.textMuted, isTruncated: true, children: option.description })) : null] })] })), isSingleSelected ? (_jsx(Box, { as: Check, size: 16, color: theme.colors.accentText, flexShrink: 0 })) : null] }));
45
+ return (_jsxs(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: optionRowWeight(isSingleSelected), color: optionRowInk(theme.colors, isSingleSelected), bg: isSingleSelected ? selectedBg : restBg, transition: ROW_TRANSITION, sx: reducedMotionSx(), children: [isMulti ? (_jsx(Checkbox, { id: `${optionDomId(listboxId, option.id)}-checkbox`, isChecked: isSelected, isDisabled: option.isDisabled, pointerEvents: "none", tabIndex: -1 })) : null, renderOption ? (_jsx(Box, { flex: 1, minW: 0, children: renderOption(option, { isSelected, isActive }) })) : (_jsxs(_Fragment, { children: [option.icon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, children: option.icon })) : null, _jsxs(Box, { flex: 1, minW: 0, isTruncated: true, children: [_jsx(Box, { as: "span", isTruncated: true, children: option.label }), option.description ? (_jsx(Box, { as: "span", display: "block", fontSize: spec.descriptionFontSize, color: optionRowDescriptionInk(theme.colors), isTruncated: true, children: option.description })) : null] })] })), isSingleSelected ? (_jsx(Box, { as: Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 })) : null] }));
47
46
  });
48
47
  const buildGroupedRows = (options) => {
49
48
  const rows = [];
@@ -177,27 +176,28 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
177
176
  const frame = requestAnimationFrame(() => { var _a; return (_a = filterInputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
178
177
  return () => cancelAnimationFrame(frame);
179
178
  }, [isOpen, isSearchable]);
179
+ const spec = useMemo(() => optionRowSpec(size), [size]);
180
180
  const maxMenuHeightPx = useMemo(() => parseRemToPx(maxMenuHeight, 256), [maxMenuHeight]);
181
181
  const shouldVirtualize = !!virtualizeAfter && !hasGroups && filteredOptions.length > virtualizeAfter;
182
182
  const [scrollTop, setScrollTop] = useState(0);
183
- const visibleCount = Math.ceil(maxMenuHeightPx / VIRTUAL_ROW_HEIGHT_PX) + VIRTUAL_OVERSCAN * 2;
183
+ const visibleCount = Math.ceil(maxMenuHeightPx / spec.virtualRowHeightPx) + VIRTUAL_OVERSCAN * 2;
184
184
  const startIndex = shouldVirtualize
185
- ? Math.max(0, Math.floor(scrollTop / VIRTUAL_ROW_HEIGHT_PX) - VIRTUAL_OVERSCAN)
185
+ ? Math.max(0, Math.floor(scrollTop / spec.virtualRowHeightPx) - VIRTUAL_OVERSCAN)
186
186
  : 0;
187
187
  const endIndex = shouldVirtualize
188
188
  ? Math.min(filteredOptions.length, startIndex + visibleCount)
189
189
  : filteredOptions.length;
190
190
  const renderedOptions = shouldVirtualize ? filteredOptions.slice(startIndex, endIndex) : filteredOptions;
191
- const topPadPx = shouldVirtualize ? startIndex * VIRTUAL_ROW_HEIGHT_PX : 0;
192
- const bottomPadPx = shouldVirtualize ? (filteredOptions.length - endIndex) * VIRTUAL_ROW_HEIGHT_PX : 0;
191
+ const topPadPx = shouldVirtualize ? startIndex * spec.virtualRowHeightPx : 0;
192
+ const bottomPadPx = shouldVirtualize ? (filteredOptions.length - endIndex) * spec.virtualRowHeightPx : 0;
193
193
  useEffect(() => {
194
194
  var _a, _b;
195
195
  if (activeIndex < 0 || !listRef.current)
196
196
  return;
197
197
  const el = listRef.current;
198
198
  if (shouldVirtualize) {
199
- const targetTop = activeIndex * VIRTUAL_ROW_HEIGHT_PX;
200
- const targetBottom = targetTop + VIRTUAL_ROW_HEIGHT_PX;
199
+ const targetTop = activeIndex * spec.virtualRowHeightPx;
200
+ const targetBottom = targetTop + spec.virtualRowHeightPx;
201
201
  if (targetTop < el.scrollTop) {
202
202
  el.scrollTop = targetTop;
203
203
  setScrollTop(targetTop);
@@ -210,7 +210,7 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
210
210
  return;
211
211
  }
212
212
  (_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" });
213
- }, [activeIndex, shouldVirtualize, maxMenuHeightPx]);
213
+ }, [activeIndex, shouldVirtualize, maxMenuHeightPx, spec.virtualRowHeightPx]);
214
214
  const moveActive = (delta) => {
215
215
  if (!filteredOptions.length)
216
216
  return;
@@ -373,14 +373,14 @@ const SelectPopup = forwardRef(function SelectPopup(props, ref) {
373
373
  const selectedValueIds = selectedOptions.map((option) => option.id.toString());
374
374
  const hiddenValues = !name ? [] : props.isMulti ? selectedValueIds : [(_c = selectedValueIds[0]) !== null && _c !== void 0 ? _c : ""];
375
375
  const groupedRows = useMemo(() => (hasGroups ? buildGroupedRows(filteredOptions) : []), [hasGroups, filteredOptions]);
376
- return (_jsxs(FormControl, { isInvalid: error, isDisabled: isDisabled, isReadOnly: isReadOnly, style: formControlStyle, width: width, children: [label && (_jsx(TextLabel, { label: label, id: ids.inputId, isRequired: isRequired, isInformation: isInformation, informationMessage: informationMessage })), _jsxs(Popover, { isOpen: isOpen, onClose: () => resetAndClose(), placement: "bottom-start", autoFocus: false, returnFocusOnClose: false, gutter: 4, children: [_jsx(PopoverAnchor, { children: _jsxs(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: [_jsx(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 ? (_jsxs(_Fragment, { children: [visibleChipOptions.map((option) => (_jsx(Tag, { label: String(option.label), size: chipSize, variant: "subtle", icon: isInteractive ? X : undefined, onIconClick: isInteractive ? () => handleSelect(option) : undefined }, option.id))), overflowChipCount > 0 && (_jsx(Tag, { label: `+${overflowChipCount}`, size: chipSize, variant: "subtle", colorScheme: "gray" }))] })) : (_jsxs(_Fragment, { children: [triggerIcon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, children: triggerIcon })) : null, _jsx(Box, { as: "span", isTruncated: true, children: renderValue ? renderValue(selectedOptions) : defaultTriggerContent() })] })) }), showClear && (_jsx(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: _jsx(X, { size: CONTROL_SIZES[size].iconSize }) })), _jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, ml: 2, transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", color: colors.textMuted, sx: reducedMotionSx(), children: _jsx(ChevronDown, { size: CONTROL_SIZES[size].iconSize }) })] }) }), _jsx(Portal, { children: _jsxs(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: [_jsx(Box, { p: 2, borderBottom: `0.063rem solid ${colors.boxborder[200]}`, display: isSearchable ? undefined : "none", children: _jsx(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 }) }), _jsxs(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 && (_jsx(Box, { px: 3, py: 2, fontSize: "0.8125rem", color: colors.textMuted, children: noOptionsMessage !== null && noOptionsMessage !== void 0 ? noOptionsMessage : "No Options" })), showCreateRow && (_jsx(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
376
+ return (_jsxs(FormControl, { isInvalid: error, isDisabled: isDisabled, isReadOnly: isReadOnly, style: formControlStyle, width: width, children: [label && (_jsx(TextLabel, { label: label, id: ids.inputId, isRequired: isRequired, isInformation: isInformation, informationMessage: informationMessage })), _jsxs(Popover, { isOpen: isOpen, onClose: () => resetAndClose(), placement: "bottom-start", autoFocus: false, returnFocusOnClose: false, gutter: 4, children: [_jsx(PopoverAnchor, { children: _jsxs(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: [_jsx(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 ? (_jsxs(_Fragment, { children: [visibleChipOptions.map((option) => (_jsx(Tag, { label: String(option.label), size: chipSize, variant: "subtle", icon: isInteractive ? X : undefined, onIconClick: isInteractive ? () => handleSelect(option) : undefined }, option.id))), overflowChipCount > 0 && (_jsx(Tag, { label: `+${overflowChipCount}`, size: chipSize, variant: "subtle", colorScheme: "gray" }))] })) : (_jsxs(_Fragment, { children: [triggerIcon ? (_jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, children: triggerIcon })) : null, _jsx(Box, { as: "span", isTruncated: true, children: renderValue ? renderValue(selectedOptions) : defaultTriggerContent() })] })) }), showClear && (_jsx(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: _jsx(X, { size: CONTROL_SIZES[size].iconSize }) })), _jsx(Box, { as: "span", display: "inline-flex", flexShrink: 0, ml: 2, transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", color: colors.textMuted, sx: reducedMotionSx(), children: _jsx(ChevronDown, { size: CONTROL_SIZES[size].iconSize }) })] }) }), _jsx(Portal, { children: _jsxs(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: [_jsx(Box, { p: 2, borderBottom: `0.063rem solid ${colors.boxborder[200]}`, display: isSearchable ? undefined : "none", children: _jsx(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 }) }), _jsxs(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 && (_jsx(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 && (_jsx(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
377
377
  ? groupedRows.map((row) => {
378
378
  var _a;
379
- return row.kind === "header" ? (_jsx(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)) : (_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));
379
+ return row.kind === "header" ? (_jsx(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)) : (_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));
380
380
  })
381
381
  : (_jsxs(_Fragment, { children: [topPadPx > 0 && _jsx(Box, { height: `${topPadPx}px` }), renderedOptions.map((option, i) => {
382
382
  var _a;
383
- return (_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));
384
- }), bottomPadPx > 0 && _jsx(Box, { height: `${bottomPadPx}px` })] })), isLoading && (_jsxs(Box, { px: 3, py: 2, display: "flex", alignItems: "center", gap: 2, fontSize: "0.8125rem", color: colors.textMuted, children: [_jsx(Spinner, { size: "xs" }), loadingText] }))] })] }) })] }), hiddenValues.map((value) => (_jsx("input", { type: "hidden", name: name, value: value }, value))), _jsx(FieldMessage, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
383
+ return (_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));
384
+ }), bottomPadPx > 0 && _jsx(Box, { height: `${bottomPadPx}px` })] })), isLoading && (_jsxs(Box, { mx: spec.mx, px: spec.px, py: spec.py, display: "flex", alignItems: "center", gap: spec.gap, fontSize: spec.fontSize, color: colors.textMuted, children: [_jsx(Spinner, { size: "xs" }), loadingText] }))] })] }) })] }), hiddenValues.map((value) => (_jsx("input", { type: "hidden", name: name, value: value }, value))), _jsx(FieldMessage, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
385
385
  });
386
386
  export default SelectPopup;
@@ -1,2 +1,11 @@
1
+ import { AA_NORMAL_TEXT } from "../../Theme/tokens/builders/accentText.js";
2
+ import { liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
3
+ import { listRowBackgrounds } from "../../Theme/tokens/builders/rowStates.js";
1
4
  /** Selection is carried by weight and a trailing brand check, so the label itself stays neutral. */
2
5
  export const optionRowInk = (colors, isSingleSelected) => isSingleSelected ? colors.text[900] : colors.text[700];
6
+ // A 12px sub-line reached only 4.00:1 on the hovered selected row in dark.
7
+ export const optionRowDescriptionInk = (colors) => liftToContrast(colors.textMuted, listRowBackgrounds(colors).selectedActive, AA_NORMAL_TEXT);
8
+ export const OPTION_ROW_REST_WEIGHT = 400;
9
+ // NOTE: 600 beside a tinted fill and a check read as three signals for one state.
10
+ export const OPTION_ROW_SELECTED_WEIGHT = 500;
11
+ export const optionRowWeight = (isSingleSelected) => isSingleSelected ? OPTION_ROW_SELECTED_WEIGHT : OPTION_ROW_REST_WEIGHT;
@@ -8,21 +8,25 @@ import { useFieldIds } from "../Common/useFieldIds.js";
8
8
  import FieldMessage from "../Common/FieldMessage.js";
9
9
  import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
10
10
  import { DEFAULT_CONTROL_SIZE } from "../../Theme/tokens/primitives/controlSizes.js";
11
+ import { optionRowSpec, OPTION_ROW_RADIUS } from "../../Theme/tokens/primitives/optionRowSizes.js";
11
12
  import { reducedMotionSx } from "../../Theme/chakra/motion.js";
12
13
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
13
14
  import { listRowBackgrounds } from "../../Theme/tokens/builders/rowStates.js";
15
+ import { optionRowDescriptionInk, optionRowInk, optionRowWeight } from "../Select/optionRowInk.js";
14
16
  import { useExclusiveOverlay } from "../../Hooks/useExclusiveOverlay.js";
15
- import { PlusIcon } from "lucide-react";
17
+ import { PlusIcon, Check } from "lucide-react";
16
18
  const ROW_TRANSITION = transitionFor(["background-color"]);
17
- const RenderOptions = ({ isOptionLoading, filteredOptions, loadingText, handleOptionClick, isProfile, isColorOptions, isOptionSelected, activeIndex, optionRefs, }) => {
19
+ const RenderOptions = ({ isOptionLoading, filteredOptions, loadingText, handleOptionClick, isProfile, isColorOptions, isOptionSelected, activeIndex, optionRefs, size, isMultipleSelect, }) => {
18
20
  const theme = useCustomTheme();
19
21
  const rowBackgrounds = useMemo(() => listRowBackgrounds(theme.colors), [theme.colors]);
22
+ const spec = optionRowSpec(size);
20
23
  if (isOptionLoading) {
21
24
  return (_jsxs(VStack, { py: 2, children: [_jsx(Spinner, { size: "sm", color: theme.colors.accentText }), _jsx(Text, { color: theme.colors.accentText, children: loadingText })] }));
22
25
  }
23
26
  if (filteredOptions === null || filteredOptions === void 0 ? void 0 : filteredOptions.length) {
24
27
  return (_jsx(_Fragment, { children: filteredOptions.map((option, index) => {
25
28
  const selected = isOptionSelected(option);
29
+ const isSingleSelected = selected && !isMultipleSelect;
26
30
  const isActive = index === activeIndex;
27
31
  const rowBg = selected
28
32
  ? isActive
@@ -33,14 +37,14 @@ const RenderOptions = ({ isOptionLoading, filteredOptions, loadingText, handleOp
33
37
  : "transparent";
34
38
  return (_jsxs(Box, { as: "button", type: "button", ref: (el) => {
35
39
  optionRefs.current[index] = el;
36
- }, 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: reducedMotionSx(), _hover: {
40
+ }, 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: OPTION_ROW_RADIUS, fontSize: spec.fontSize, cursor: "pointer", color: optionRowInk(theme.colors, isSingleSelected), fontWeight: optionRowWeight(isSingleSelected), transition: ROW_TRANSITION, sx: reducedMotionSx(), _hover: {
37
41
  bg: selected ? rowBackgrounds.selectedActive : rowBackgrounds.active,
38
42
  }, onClick: () => handleOptionClick(option), onKeyDown: (e) => {
39
43
  if (e.key === "Enter" || e.key === " ") {
40
44
  e.preventDefault();
41
45
  handleOptionClick(option);
42
46
  }
43
- }, display: "flex", alignItems: "center", gap: "0.5rem", children: [isProfile && (_jsx(Box, { children: _jsx(Avatar, { size: "sm", name: option.label.toLocaleUpperCase(), src: option.profileUrl ? option.profileUrl : undefined }) })), isColorOptions && (_jsx(Box, { width: "0.8rem", height: "0.8rem", content: "", backgroundColor: option === null || option === void 0 ? void 0 : option.color, borderRadius: "50%" })), _jsxs(Box, { children: [_jsx(Box, { children: option.label }), option.email && (_jsx(Box, { fontSize: "0.75rem", color: theme.colors.textMuted, children: option.email }))] })] }, option.id));
47
+ }, display: "flex", alignItems: "center", gap: spec.gap, children: [isProfile && (_jsx(Box, { children: _jsx(Avatar, { size: spec.avatarSize, name: option.label.toLocaleUpperCase(), src: option.profileUrl ? option.profileUrl : undefined }) })), isColorOptions && (_jsx(Box, { width: "0.8rem", height: "0.8rem", content: "", backgroundColor: option === null || option === void 0 ? void 0 : option.color, borderRadius: "50%" })), _jsxs(Box, { flex: 1, minW: 0, children: [_jsx(Box, { children: option.label }), option.email && (_jsx(Box, { fontSize: spec.descriptionFontSize, color: optionRowDescriptionInk(theme.colors), children: option.email }))] }), isSingleSelected && (_jsx(Box, { as: Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 }))] }, option.id));
44
48
  }) }));
45
49
  }
46
50
  return (_jsx(Text, { px: 4, py: 2, color: theme.colors.textMuted, children: "No options found" }));
@@ -224,7 +228,7 @@ export default function SelectSearch({ options, initialSelectedOption, onOptionS
224
228
  return;
225
229
  }
226
230
  openDropdown();
227
- }, 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: { ...fieldControlStyles(theme.colors), ...controlSizeStyles(size), ...reducedMotionSx() }, style: inputStyle }), rightIcon && (_jsx(InputRightElement, { pointerEvents: "none", children: rightIcon, style: { ...rightElementStyle } }))] }), isOpen && (_jsxs(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: [_jsx(Box, { id: listboxId, role: "listbox", "aria-label": label !== null && label !== void 0 ? label : placeholder, onKeyDown: handleListKeyDown, children: _jsx(RenderOptions, { isOptionLoading: isOptionLoading, filteredOptions: filteredOptions, loadingText: loadingText, handleOptionClick: handleOptionClick, isProfile: isProfile, isColorOptions: isColorOptions, isOptionSelected: isOptionSelected, activeIndex: activeIndex, optionRefs: optionRefs }) }), _jsx("hr", {}), isBottomIcon && (_jsxs(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: [_jsx(Box, { children: BottomIcon ? BottomIcon : _jsx(PlusIcon, { size: "1rem" }) }), _jsx(Box, { fontSize: "0.875rem", color: BottomTextColor
231
+ }, 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: { ...fieldControlStyles(theme.colors), ...controlSizeStyles(size), ...reducedMotionSx() }, style: inputStyle }), rightIcon && (_jsx(InputRightElement, { pointerEvents: "none", children: rightIcon, style: { ...rightElementStyle } }))] }), isOpen && (_jsxs(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: [_jsx(Box, { id: listboxId, role: "listbox", "aria-label": label !== null && label !== void 0 ? label : placeholder, onKeyDown: handleListKeyDown, children: _jsx(RenderOptions, { isOptionLoading: isOptionLoading, filteredOptions: filteredOptions, loadingText: loadingText, handleOptionClick: handleOptionClick, isProfile: isProfile, isColorOptions: isColorOptions, isOptionSelected: isOptionSelected, activeIndex: activeIndex, optionRefs: optionRefs, size: size, isMultipleSelect: isMultipleSelect }) }), _jsx("hr", {}), isBottomIcon && (_jsxs(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: [_jsx(Box, { children: BottomIcon ? BottomIcon : _jsx(PlusIcon, { size: "1rem" }) }), _jsx(Box, { fontSize: "0.875rem", color: BottomTextColor
228
232
  ? BottomTextColor
229
233
  : theme.colors.accentText, padding: "8px 8px", children: BottomText || "Add New" })] }))] })), _jsx(FieldMessage, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }) }) }));
230
234
  }
@@ -1,11 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Box } from "@chakra-ui/react";
3
3
  import SegmentedControl from "../SegmentedControl/SegmentedControl.js";
4
- /** @deprecated Use `SegmentedControl` — Switch renders it at 12.5rem, growing when the labels need it. */
4
+ /** @deprecated Two labelled choices; for a single on/off field use `ToggleSwitch`. Renders `SegmentedControl` at 12.5rem when labelled. */
5
5
  export default function Switch({ leftText, rightText, leftIcon, rightIcon, onSwitchToggle, switchActive, surface, "aria-label": ariaLabel, }) {
6
6
  const options = [
7
7
  { value: "left", label: leftText, icon: leftIcon },
8
8
  { value: "right", label: rightText, icon: rightIcon },
9
9
  ];
10
- return (_jsx(Box, { w: "fit-content", minW: "12.5rem", children: _jsx(SegmentedControl, { options: options, value: switchActive, onChange: onSwitchToggle, variant: "brandSlide", surface: surface, isFullWidth: true, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : `${leftText} or ${rightText}` }) }));
10
+ // Unlabelled segments have nothing to size to, so the 12.5rem floor only overflowed its cell.
11
+ const hasLabels = Boolean(leftText || rightText);
12
+ return (_jsx(Box, { w: "fit-content", maxW: "100%", minW: hasLabels ? "12.5rem" : undefined, children: _jsx(SegmentedControl, { options: options, value: switchActive, onChange: onSwitchToggle, variant: "brandSlide", surface: surface, isFullWidth: true, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : `${leftText} or ${rightText}` }) }));
11
13
  }