pixelize-design-library 2.4.2-beta.45 → 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 (44) hide show
  1. package/CHANGELOG.md +34 -4
  2. package/COMPONENT-GOTCHAS.md +47 -15
  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/SegmentedControl/SegmentedControl.js +4 -2
  8. package/dist/Components/SegmentedControl/SegmentedControlProps.d.ts +1 -1
  9. package/dist/Components/SegmentedControl/segmentedInk.d.ts +3 -3
  10. package/dist/Components/SegmentedControl/segmentedInk.js +8 -11
  11. package/dist/Components/Select/SelectPopup.js +16 -15
  12. package/dist/Components/Select/optionRowInk.d.ts +7 -0
  13. package/dist/Components/Select/optionRowInk.js +17 -0
  14. package/dist/Components/SelectSearch/SelectSearch.js +8 -4
  15. package/dist/Components/Switch/Switch.d.ts +1 -1
  16. package/dist/Components/Switch/Switch.js +4 -2
  17. package/dist/Components/Switch/SwitchProps.d.ts +1 -1
  18. package/dist/Components/Table/filters/CompactSelect.js +6 -3
  19. package/dist/Theme/chakra/Menu.styles.d.ts +6 -0
  20. package/dist/Theme/chakra/Menu.styles.js +8 -0
  21. package/dist/Theme/chakra/componentStyles.d.ts +6 -0
  22. package/dist/Theme/tokens/builders/outlineRung.d.ts +2 -0
  23. package/dist/Theme/tokens/builders/outlineRung.js +14 -1
  24. package/dist/Theme/tokens/builders/rowStates.js +3 -4
  25. package/dist/Theme/tokens/primitives/optionRowSizes.d.ts +20 -0
  26. package/dist/Theme/tokens/primitives/optionRowSizes.js +29 -0
  27. package/dist/esm/Components/Dropdown/DropDown.js +10 -8
  28. package/dist/esm/Components/SearchSelect/SearchSelect.js +20 -15
  29. package/dist/esm/Components/SearchSelect/searchSelectSize.js +0 -12
  30. package/dist/esm/Components/SegmentedControl/SegmentedControl.js +4 -2
  31. package/dist/esm/Components/SegmentedControl/segmentedInk.js +8 -11
  32. package/dist/esm/Components/Select/SelectPopup.js +17 -16
  33. package/dist/esm/Components/Select/optionRowInk.js +11 -0
  34. package/dist/esm/Components/SelectSearch/SelectSearch.js +9 -5
  35. package/dist/esm/Components/Switch/Switch.js +4 -2
  36. package/dist/esm/Components/Table/filters/CompactSelect.js +7 -4
  37. package/dist/esm/Theme/chakra/Menu.styles.js +8 -0
  38. package/dist/esm/Theme/tokens/builders/outlineRung.js +12 -0
  39. package/dist/esm/Theme/tokens/builders/rowStates.js +4 -5
  40. package/dist/esm/Theme/tokens/primitives/optionRowSizes.js +25 -0
  41. package/dist/esm/index.js +2 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.js +9 -4
  44. package/package.json +1 -1
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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");
7
+ /** Selection is carried by weight and a trailing brand check, so the label itself stays neutral. */
8
+ const optionRowInk = (colors, isSingleSelected) => isSingleSelected ? colors.text[900] : colors.text[700];
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
  }
@@ -8,7 +8,7 @@ export type SwitchProps = {
8
8
  onSwitchToggle: (value: "left" | "right") => void;
9
9
  /** Controlled selected side; the pill only moves when this changes. */
10
10
  switchActive: "left" | "right";
11
- /** Background this sits on. Pass it off the page canvas — on a brand-filled bar the track sinks into the bar and the inactive label is unreadable without it. */
11
+ /** Background this sits on. Optional: the track is opaque on any backdrop; pass it only to aim the plate's hairline edge at the real backdrop. */
12
12
  surface?: string;
13
13
  /** Names the radiogroup; pass it, as there is no visible group label. */
14
14
  "aria-label"?: string;
@@ -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;
@@ -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
  };
@@ -35,7 +35,9 @@ export default function SegmentedControl({ options, value, onChange, size = "sm"
35
35
  const innerRadius = isUnderlineSlide ? "none" : `calc(${radii.lg} - ${TRACK_PADDING})`;
36
36
  const focusShadow = segmentedFocusShadow(colors, trackSurface);
37
37
  // Inset, never a border: a border consumes the content box and the absolute pill stops centring.
38
- const plateEdge = isBrandSlide && surface ? `inset 0 0 0 1px ${segmentedPlateEdge(colors, surface)}` : undefined;
38
+ const plateEdge = isBrandSlide
39
+ ? `inset 0 0 0 1px ${segmentedPlateEdge(colors, surface)}`
40
+ : undefined;
39
41
  // The transparent outline replaces the UA ring and is what Windows high-contrast repaints.
40
42
  const segmentFocus = {
41
43
  outline: "2px solid transparent",
@@ -82,7 +84,7 @@ export default function SegmentedControl({ options, value, onChange, size = "sm"
82
84
  selectAt(enabled[enabled.length - 1]);
83
85
  }
84
86
  };
85
- return (_jsxs(Flex, { ref: trackRef, role: "radiogroup", "aria-label": ariaLabel, onKeyDown: handleKeyDown, onFocus: (event) => setShowFocus(event.target.matches(":focus-visible")), onBlur: () => setShowFocus(false), boxShadow: showFocus && !isUnderlineSlide ? focusShadow : plateEdge, position: "relative", display: "inline-flex", align: "center", gap: isUnderlineSlide ? "0" : TRACK_PADDING, p: isUnderlineSlide ? "0" : TRACK_PADDING, h: control.height, boxSizing: "border-box", borderRadius: isUnderlineSlide ? "none" : "lg", borderBottom: isUnderlineSlide ? `${UNDERLINE_WIDTH} solid ${colors.boxborder[200]}` : undefined, bg: isBrandSlide ? (surface ? trackSurface : colors.primary.opacity[8]) : isUnderlineSlide ? "transparent" : colors.background[600], w: isFullWidth ? "100%" : "fit-content", minW: "max-content", children: [isBrandSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", top: TRACK_PADDING, left: 0, h: segmentH, w: `${pill.width}px`, borderRadius: innerRadius, bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: SLIDE, sx: reducedMotionSx() })), isUnderlineSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", bottom: "-0.0625rem", left: 0, h: "0.125rem", w: `${pill.width}px`, borderRadius: "full", bg: colors.primary[500], transform: `translateX(${pill.left}px)`, transition: SLIDE, sx: reducedMotionSx() })), options.map((option, index) => {
87
+ return (_jsxs(Flex, { ref: trackRef, role: "radiogroup", "aria-label": ariaLabel, onKeyDown: handleKeyDown, onFocus: (event) => setShowFocus(event.target.matches(":focus-visible")), onBlur: () => setShowFocus(false), boxShadow: showFocus && !isUnderlineSlide ? focusShadow : plateEdge, position: "relative", display: "inline-flex", align: "center", gap: isUnderlineSlide ? "0" : TRACK_PADDING, p: isUnderlineSlide ? "0" : TRACK_PADDING, h: control.height, boxSizing: "border-box", borderRadius: isUnderlineSlide ? "none" : "lg", borderBottom: isUnderlineSlide ? `${UNDERLINE_WIDTH} solid ${colors.boxborder[200]}` : undefined, bg: isUnderlineSlide ? "transparent" : trackSurface, w: isFullWidth ? "100%" : "fit-content", minW: "max-content", children: [isBrandSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", top: TRACK_PADDING, left: 0, h: segmentH, w: `${pill.width}px`, borderRadius: innerRadius, bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: SLIDE, sx: reducedMotionSx() })), isUnderlineSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", bottom: "-0.0625rem", left: 0, h: "0.125rem", w: `${pill.width}px`, borderRadius: "full", bg: colors.primary[500], transform: `translateX(${pill.left}px)`, transition: SLIDE, sx: reducedMotionSx() })), options.map((option, index) => {
86
88
  const isSelected = option.value === value;
87
89
  return (_jsxs(Box, { as: "button", type: "button", ref: (node) => {
88
90
  refs.current[index] = node;
@@ -1,23 +1,20 @@
1
1
  import { focusGlowShadow } from "../../Theme/chakra/focusRing.styles.js";
2
- import { compositeOver } from "../../Theme/tokens/builders/color.js";
3
2
  import { AA_NON_TEXT, liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
4
3
  import { AA_NORMAL_TEXT } from "../../Theme/tokens/builders/accentText.js";
5
- /** The surface a segment's label really sits on; brandSlide's track is a translucent tint, so composite it. */
4
+ /** The surface a segment's label really sits on. */
6
5
  export const segmentedTrackSurface = (variant, colors, surface) => {
7
- const canvas = surface !== null && surface !== void 0 ? surface : colors.backgroundColor.main;
8
6
  if (variant === "underlineSlide")
9
- return canvas;
10
- // An 8% tint reads on the page canvas; on a coloured bar it composites into the bar and the
11
- // control stops looking like a control, so there the track becomes an opaque plate.
12
- if (variant === "brandSlide") {
13
- return surface ? colors.surfaceRaised : compositeOver(colors.primary.opacity[8], canvas);
14
- }
7
+ return surface !== null && surface !== void 0 ? surface : colors.backgroundColor.main;
8
+ // brandSlide paints an opaque plate on every backdrop: a tint composites into an unknown
9
+ // parent, so its ink was derived from a canvas the control was not actually on.
10
+ if (variant === "brandSlide")
11
+ return colors.surfaceRaised;
15
12
  return colors.background[600];
16
13
  };
17
14
  /** Unselected label ink, lifted to AA against the track it is drawn on. */
18
15
  export const segmentedRestInk = (colors, trackSurface) => liftToContrast(colors.text[600], trackSurface, AA_NORMAL_TEXT);
19
16
  /** A radiogroup is one tab stop, so the group wears the ring: a brand hairline plus the field bloom. */
20
17
  export const segmentedFocusShadow = (colors, trackSurface) => `inset 0 0 0 1px ${liftToContrast(colors.primary[500], trackSurface, AA_NON_TEXT)}, ${focusGlowShadow(colors)}`;
21
- /** On a coloured bar the plate can match the bar's lightness, so its edge carries the boundary.
18
+ /** The plate can match its backdrop's lightness, so its edge carries the boundary.
22
19
  * Drawn inset by the caller: a real border consumes the content box and decentres the sliding pill. */
23
- export const segmentedPlateEdge = (colors, surface) => liftToContrast(colors.boxborder[500], surface, AA_NON_TEXT);
20
+ export const segmentedPlateEdge = (colors, surface) => liftToContrast(colors.boxborder[500], surface !== null && surface !== void 0 ? surface : colors.backgroundColor.main, AA_NON_TEXT);