pixelize-design-library 2.4.2-beta.45 → 2.4.2-beta.46
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.
- package/CHANGELOG.md +14 -4
- package/COMPONENT-GOTCHAS.md +10 -14
- package/dist/Components/SegmentedControl/SegmentedControl.js +4 -2
- package/dist/Components/SegmentedControl/SegmentedControlProps.d.ts +1 -1
- package/dist/Components/SegmentedControl/segmentedInk.d.ts +3 -3
- package/dist/Components/SegmentedControl/segmentedInk.js +8 -11
- package/dist/Components/Select/SelectPopup.js +2 -1
- package/dist/Components/Select/optionRowInk.d.ts +3 -0
- package/dist/Components/Select/optionRowInk.js +6 -0
- package/dist/Components/Switch/SwitchProps.d.ts +1 -1
- package/dist/esm/Components/SegmentedControl/SegmentedControl.js +4 -2
- package/dist/esm/Components/SegmentedControl/segmentedInk.js +8 -11
- package/dist/esm/Components/Select/SelectPopup.js +3 -2
- package/dist/esm/Components/Select/optionRowInk.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,10 +14,9 @@ next major.
|
|
|
14
14
|
|
|
15
15
|
### Added
|
|
16
16
|
|
|
17
|
-
- **`SegmentedControl` / `Switch` — `surface`.** The background the control is drawn on.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
canvas, so existing call sites are unchanged.
|
|
17
|
+
- **`SegmentedControl` / `Switch` — `surface`.** The background the control is drawn on. Optional:
|
|
18
|
+
the track is an opaque plate on any backdrop, so the labels read without it. Pass it to aim the
|
|
19
|
+
plate's hairline edge at the real backdrop rather than at the page canvas.
|
|
21
20
|
- **`MultiSelect` — `addOnBlur`.** Commits the typed address on focus-out as well as on Enter.
|
|
22
21
|
Defaults to `false`, so existing call sites keep Enter-only behaviour.
|
|
23
22
|
|
|
@@ -64,6 +63,17 @@ next major.
|
|
|
64
63
|
|
|
65
64
|
### Fixed
|
|
66
65
|
|
|
66
|
+
- **`SegmentedControl` / `Switch` guessed the backdrop it was drawn on.** `brandSlide` painted an
|
|
67
|
+
8% brand tint composited over `backgroundColor.main` and lifted its unselected-label ink against
|
|
68
|
+
that same assumed canvas, so on a brand-filled bar the tint sank into the bar and the label
|
|
69
|
+
measured **1.03–2.03:1 across all 9 brands x 2 modes** — crm's `Overview / Timeline` view switcher
|
|
70
|
+
on a `primary[700]` bar was unreadable. The track is now an opaque plate on every backdrop: rest
|
|
71
|
+
ink 7.58–18.39:1, plate-vs-bar 6.31–19.0:1, with no change required at the call site. `surface`
|
|
72
|
+
is now only a refinement for the plate's hairline edge, and a test gates the invariant.
|
|
73
|
+
- **A selected `Select` option stacked brand ink on a brand-tinted row.** Bold `accentText` on
|
|
74
|
+
`listRowBackgrounds().selected` read as muddy, and the row cleared the panel by only 1.16–1.29:1.
|
|
75
|
+
The label is now `text[900]` with the brand carried by a trailing check, matching `OrgSwitcher`.
|
|
76
|
+
|
|
67
77
|
- **`SegmentedControl`'s pill bulged past the plate's corners.** The pill and the segments carried
|
|
68
78
|
the track's own `lg` radius while sitting 2px inside it, so the plate's band stayed even along the
|
|
69
79
|
straight edges but pinched to nothing at the corners. They now round concentrically at
|
package/COMPONENT-GOTCHAS.md
CHANGED
|
@@ -49,27 +49,23 @@ opt-in, never assume a non-default look).
|
|
|
49
49
|
Pick by density and emphasis, not by taste. When unsure, or migrating an existing call site, leave
|
|
50
50
|
it on `"default"` — it's the only variant guaranteed to match previously-shipped screenshots.
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
**`brandSlide` no longer guesses its backdrop — and `surface` is now optional.** Until
|
|
53
|
+
2.4.2-beta.46 the track was an 8% brand tint composited over the **page canvas**, and the inactive
|
|
54
|
+
label's ink was lifted against that same assumed canvas. Dropped into a brand-filled header both
|
|
55
|
+
went wrong at once: the tint sank into the bar, and the label landed at **1.03–2.03:1** — rendered,
|
|
56
|
+
effectively invisible. The track is now an opaque plate (`colors.surfaceRaised`) whatever it sits
|
|
57
|
+
on, so a control on a coloured bar is readable with no prop passed (rest ink 7.58–18.39:1).
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
lightness (as low as 1.56:1 on radiant), so hue alone would not separate them.
|
|
59
|
+
Pass `surface` only to aim the plate's 1px edge at the real backdrop instead of the page canvas.
|
|
60
|
+
The edge is what carries the boundary: in dark mode the plate and a brand bar can sit at almost the
|
|
61
|
+
same lightness (as low as 1.56:1 on radiant), so hue alone would not separate them.
|
|
62
62
|
|
|
63
63
|
```tsx
|
|
64
64
|
<Box bg={theme.colors.primary[700]}>
|
|
65
|
-
<Switch ... surface={theme.colors.primary[700]} /> //
|
|
65
|
+
<Switch ... surface={theme.colors.primary[700]} /> // optional: sharpens the plate's edge
|
|
66
66
|
</Box>
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
Pass the same value you gave the wrapper's `bg`. It re-aims the unselected, selected and hover inks
|
|
70
|
-
together, and flips direction correctly in dark mode. `HeaderActions` takes `surface` for the same
|
|
71
|
-
reason.
|
|
72
|
-
|
|
73
69
|
### Which "select" component — `Select` is the only one to reach for now
|
|
74
70
|
|
|
75
71
|
There is now **one** picker. `Select` does both modes off the `native` prop:
|
|
@@ -41,7 +41,9 @@ function SegmentedControl({ options, value, onChange, size = "sm", isFullWidth =
|
|
|
41
41
|
const innerRadius = isUnderlineSlide ? "none" : `calc(${radii.lg} - ${TRACK_PADDING})`;
|
|
42
42
|
const focusShadow = (0, segmentedInk_1.segmentedFocusShadow)(colors, trackSurface);
|
|
43
43
|
// Inset, never a border: a border consumes the content box and the absolute pill stops centring.
|
|
44
|
-
const plateEdge = isBrandSlide
|
|
44
|
+
const plateEdge = isBrandSlide
|
|
45
|
+
? `inset 0 0 0 1px ${(0, segmentedInk_1.segmentedPlateEdge)(colors, surface)}`
|
|
46
|
+
: undefined;
|
|
45
47
|
// The transparent outline replaces the UA ring and is what Windows high-contrast repaints.
|
|
46
48
|
const segmentFocus = {
|
|
47
49
|
outline: "2px solid transparent",
|
|
@@ -88,7 +90,7 @@ function SegmentedControl({ options, value, onChange, size = "sm", isFullWidth =
|
|
|
88
90
|
selectAt(enabled[enabled.length - 1]);
|
|
89
91
|
}
|
|
90
92
|
};
|
|
91
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.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:
|
|
93
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.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 && ((0, jsx_runtime_1.jsx)(react_2.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: (0, motion_2.reducedMotionSx)() })), isUnderlineSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.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: (0, motion_2.reducedMotionSx)() })), options.map((option, index) => {
|
|
92
94
|
const isSelected = option.value === value;
|
|
93
95
|
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { as: "button", type: "button", ref: (node) => {
|
|
94
96
|
refs.current[index] = node;
|
|
@@ -27,7 +27,7 @@ export type SegmentedControlProps<T extends string = string> = {
|
|
|
27
27
|
isFullWidth?: boolean;
|
|
28
28
|
/** brandSlide = primary view switcher; underlineSlide = dense toolbars/headers. */
|
|
29
29
|
variant?: SegmentedControlVariant;
|
|
30
|
-
/** Background this sits on.
|
|
30
|
+
/** 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. */
|
|
31
31
|
surface?: string;
|
|
32
32
|
"aria-label": string;
|
|
33
33
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { PaletteProps } from "../../Theme/tokens/types";
|
|
2
2
|
import type { SegmentedControlVariant } from "./SegmentedControlProps";
|
|
3
|
-
/** The surface a segment's label really sits on
|
|
3
|
+
/** The surface a segment's label really sits on. */
|
|
4
4
|
export declare const segmentedTrackSurface: (variant: SegmentedControlVariant, colors: PaletteProps, surface?: string) => string;
|
|
5
5
|
/** Unselected label ink, lifted to AA against the track it is drawn on. */
|
|
6
6
|
export declare const segmentedRestInk: (colors: PaletteProps, trackSurface: string) => string;
|
|
7
7
|
/** A radiogroup is one tab stop, so the group wears the ring: a brand hairline plus the field bloom. */
|
|
8
8
|
export declare const segmentedFocusShadow: (colors: PaletteProps, trackSurface: string) => string;
|
|
9
|
-
/**
|
|
9
|
+
/** The plate can match its backdrop's lightness, so its edge carries the boundary.
|
|
10
10
|
* Drawn inset by the caller: a real border consumes the content box and decentres the sliding pill. */
|
|
11
|
-
export declare const segmentedPlateEdge: (colors: PaletteProps, surface
|
|
11
|
+
export declare const segmentedPlateEdge: (colors: PaletteProps, surface?: string) => string;
|
|
@@ -2,19 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.segmentedPlateEdge = exports.segmentedFocusShadow = exports.segmentedRestInk = exports.segmentedTrackSurface = void 0;
|
|
4
4
|
const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
|
|
5
|
-
const color_1 = require("../../Theme/tokens/builders/color");
|
|
6
5
|
const outlineRung_1 = require("../../Theme/tokens/builders/outlineRung");
|
|
7
6
|
const accentText_1 = require("../../Theme/tokens/builders/accentText");
|
|
8
|
-
/** The surface a segment's label really sits on
|
|
7
|
+
/** The surface a segment's label really sits on. */
|
|
9
8
|
const segmentedTrackSurface = (variant, colors, surface) => {
|
|
10
|
-
const canvas = surface !== null && surface !== void 0 ? surface : colors.backgroundColor.main;
|
|
11
9
|
if (variant === "underlineSlide")
|
|
12
|
-
return
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
if (variant === "brandSlide")
|
|
16
|
-
return
|
|
17
|
-
}
|
|
10
|
+
return surface !== null && surface !== void 0 ? surface : colors.backgroundColor.main;
|
|
11
|
+
// brandSlide paints an opaque plate on every backdrop: a tint composites into an unknown
|
|
12
|
+
// parent, so its ink was derived from a canvas the control was not actually on.
|
|
13
|
+
if (variant === "brandSlide")
|
|
14
|
+
return colors.surfaceRaised;
|
|
18
15
|
return colors.background[600];
|
|
19
16
|
};
|
|
20
17
|
exports.segmentedTrackSurface = segmentedTrackSurface;
|
|
@@ -24,7 +21,7 @@ exports.segmentedRestInk = segmentedRestInk;
|
|
|
24
21
|
/** A radiogroup is one tab stop, so the group wears the ring: a brand hairline plus the field bloom. */
|
|
25
22
|
const segmentedFocusShadow = (colors, trackSurface) => `inset 0 0 0 1px ${(0, outlineRung_1.liftToContrast)(colors.primary[500], trackSurface, outlineRung_1.AA_NON_TEXT)}, ${(0, focusRing_styles_1.focusGlowShadow)(colors)}`;
|
|
26
23
|
exports.segmentedFocusShadow = segmentedFocusShadow;
|
|
27
|
-
/**
|
|
24
|
+
/** The plate can match its backdrop's lightness, so its edge carries the boundary.
|
|
28
25
|
* Drawn inset by the caller: a real border consumes the content box and decentres the sliding pill. */
|
|
29
|
-
const segmentedPlateEdge = (colors, surface) => (0, outlineRung_1.liftToContrast)(colors.boxborder[500], surface, outlineRung_1.AA_NON_TEXT);
|
|
26
|
+
const segmentedPlateEdge = (colors, surface) => (0, outlineRung_1.liftToContrast)(colors.boxborder[500], surface !== null && surface !== void 0 ? surface : colors.backgroundColor.main, outlineRung_1.AA_NON_TEXT);
|
|
30
27
|
exports.segmentedPlateEdge = segmentedPlateEdge;
|
|
@@ -43,6 +43,7 @@ const lucide_react_1 = require("lucide-react");
|
|
|
43
43
|
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
44
44
|
const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
|
|
45
45
|
const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
|
|
46
|
+
const optionRowInk_1 = require("./optionRowInk");
|
|
46
47
|
const scrollbar_1 = require("../../Theme/tokens/builders/scrollbar");
|
|
47
48
|
const controlSizes_1 = require("../../Theme/tokens/primitives/controlSizes");
|
|
48
49
|
const motion_1 = require("../../Theme/tokens/primitives/motion");
|
|
@@ -80,7 +81,7 @@ const OptionRow = react_1.default.memo(function OptionRow({ option, index, listb
|
|
|
80
81
|
const isSingleSelected = isSelected && !isMulti;
|
|
81
82
|
const selectedBg = isActive ? backgrounds.selectedActive : backgrounds.selected;
|
|
82
83
|
const restBg = isActive ? backgrounds.active : "transparent";
|
|
83
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { id: optionDomId(listboxId, option.id), "data-index": index, role: "option", "aria-selected": isSelected, "aria-disabled": option.isDisabled, onMouseEnter: () => onActivate(index), onClick: () => onSelect(option), mx: 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:
|
|
84
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { id: optionDomId(listboxId, option.id), "data-index": index, role: "option", "aria-selected": isSelected, "aria-disabled": option.isDisabled, onMouseEnter: () => onActivate(index), onClick: () => onSelect(option), mx: 1.5, px: 2.5, py: 1.5, borderRadius: "md", cursor: option.isDisabled ? "not-allowed" : "pointer", opacity: option.isDisabled ? 0.4 : 1, display: "flex", alignItems: "center", gap: 2, fontSize: "0.8125rem", fontWeight: isSingleSelected ? 600 : 400, color: (0, optionRowInk_1.optionRowInk)(theme.colors, isSingleSelected), bg: isSingleSelected ? selectedBg : restBg, transition: ROW_TRANSITION, sx: (0, motion_2.reducedMotionSx)(), children: [isMulti ? ((0, jsx_runtime_1.jsx)(react_2.Checkbox, { id: `${optionDomId(listboxId, option.id)}-checkbox`, isChecked: isSelected, isDisabled: option.isDisabled, pointerEvents: "none", tabIndex: -1 })) : null, renderOption ? ((0, jsx_runtime_1.jsx)(react_2.Box, { flex: 1, minW: 0, children: renderOption(option, { isSelected, isActive }) })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [option.icon ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, children: option.icon })) : null, (0, jsx_runtime_1.jsxs)(react_2.Box, { flex: 1, minW: 0, isTruncated: true, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", isTruncated: true, children: option.label }), option.description ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "block", fontSize: "0.75rem", color: theme.colors.textMuted, isTruncated: true, children: option.description })) : null] })] })), isSingleSelected ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: lucide_react_1.Check, size: 16, color: theme.colors.accentText, flexShrink: 0 })) : null] }));
|
|
84
85
|
});
|
|
85
86
|
const buildGroupedRows = (options) => {
|
|
86
87
|
const rows = [];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.optionRowInk = void 0;
|
|
4
|
+
/** Selection is carried by weight and a trailing brand check, so the label itself stays neutral. */
|
|
5
|
+
const optionRowInk = (colors, isSingleSelected) => isSingleSelected ? colors.text[900] : colors.text[700];
|
|
6
|
+
exports.optionRowInk = optionRowInk;
|
|
@@ -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.
|
|
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;
|
|
@@ -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
|
|
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:
|
|
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
|
|
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
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
if (variant === "brandSlide")
|
|
13
|
-
return
|
|
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
|
-
/**
|
|
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);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Box, Checkbox, FormControl, Input, Popover, PopoverAnchor, PopoverContent, Portal, Spinner, useDisclosure, useMergeRefs, } from "@chakra-ui/react";
|
|
4
|
-
import { ChevronDown, X } from "lucide-react";
|
|
4
|
+
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
9
|
import { scrollbarStyles } from "../../Theme/tokens/builders/scrollbar.js";
|
|
9
10
|
import { CONTROL_SIZES, DEFAULT_CONTROL_SIZE } from "../../Theme/tokens/primitives/controlSizes.js";
|
|
10
11
|
import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
|
|
@@ -42,7 +43,7 @@ const OptionRow = React.memo(function OptionRow({ option, index, listboxId, isSe
|
|
|
42
43
|
const isSingleSelected = isSelected && !isMulti;
|
|
43
44
|
const selectedBg = isActive ? backgrounds.selectedActive : backgrounds.selected;
|
|
44
45
|
const restBg = isActive ? backgrounds.active : "transparent";
|
|
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: 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:
|
|
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] }));
|
|
46
47
|
});
|
|
47
48
|
const buildGroupedRows = (options) => {
|
|
48
49
|
const rows = [];
|
package/package.json
CHANGED