pixelize-design-library 2.4.2-beta.35 → 2.4.2-beta.37

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.
@@ -0,0 +1,135 @@
1
+ # Component Gotchas & Decision Guide
2
+
3
+ For anyone — human or AI — consuming `pixelize-design-library` in an app. Read this **before**
4
+ choosing a component or a prop value, not after something looks wrong. Every entry below compiles
5
+ cleanly, accepts what you pass, and produces the wrong result with no warning — TypeScript cannot
6
+ catch any of these, and a unit suite that mocks the library won't either.
7
+
8
+ This file ships inside the published package (`node_modules/pixelize-design-library/COMPONENT-GOTCHAS.md`)
9
+ so it's available without checking out the library's source repo.
10
+
11
+ ---
12
+
13
+ ## Choosing between components
14
+
15
+ ### Segmented control look — `SegmentedControl` / `Toggle/TableToggle`
16
+
17
+ Both take `variant?: "default" | "brandSlide" | "underlineSlide"` (default `"default"` — always
18
+ opt-in, never assume a non-default look).
19
+
20
+ | Context | `variant` | Look |
21
+ | --- | --- | --- |
22
+ | Most call sites — settings rows, filters, table view switches | `"default"` | Neutral chip |
23
+ | The primary, hard-to-miss view switcher on a page (not a dense toolbar) | `"brandSlide"` | Solid brand-filled pill slides behind the active segment |
24
+ | A dense toolbar, table header, or anywhere a boxed control feels heavy | `"underlineSlide"` | Thin brand-colored bar glides under the active label, no fill/chip |
25
+
26
+ Pick by density and emphasis, not by taste. When unsure, or migrating an existing call site, leave
27
+ it on `"default"` — it's the only variant guaranteed to match previously-shipped screenshots.
28
+
29
+ ### Which "select" component — seven components overlap in name, not purpose
30
+
31
+ **Not picking a value from an options list at all:**
32
+
33
+ | Doing | Component | Why not the others |
34
+ | --- | --- | --- |
35
+ | Triggering a labelled *action* (export, row menu, "...") | `Dropdown` | A menu-button, not a form field — nothing is "selected" and persisted |
36
+ | Typing free-text tags (e.g. email addresses), no options list | `MultiSelect` | Despite the name, there is nothing to pick from — see gotcha below |
37
+ | Choosing which *fields/columns* to include, in a modal, from a checkbox grid | `FieldSelectModal` | Multi-checkbox selection UI, not an inline field control |
38
+
39
+ **Picking one or more values from an options list, inline in a form:**
40
+
41
+ | List length / need | Component | Why |
42
+ | --- | --- | --- |
43
+ | Short, always-visible list, no search needed | `Select` | Native `<select>` wrapper — lightest option, but the popup is OS-painted and can't be themed |
44
+ | New build, want themed/custom-rendered rows (icons, descriptions), no huge list | `SelectV2` | Fully React-rendered popup — **prefer this for new work** over `Select` |
45
+ | Long list needing search + chips + select-all + infinite scroll | `SearchSelect` | The full-featured picker; virtualizes |
46
+ | Search-as-you-type with avatars/colour swatches/a pinned "add new" row | `SelectSearch` | Lighter than `SearchSelect`; reach for it for that specific row-content need |
47
+
48
+ Default when unsure: `SelectV2` for a themed field, `Select` if the native popup is genuinely fine.
49
+
50
+ ---
51
+
52
+ ## Known gotchas (silent failures)
53
+
54
+ ### `Drawer` / `Modal` / `ProfileCard` drop any child that isn't a named slot
55
+
56
+ All three match children by exact type (`child.type === XHeader/XBody/XFooter`) and render only
57
+ those matches — nothing else in `children` is ever read. Two consequences: a sibling among the
58
+ slots (a nested `Modal`, `AlertDialog`, a confirm dialog, a portal after the footer) silently never
59
+ mounts; and a slot wrapped in a `<>...</>` fragment is dropped too, because the matching doesn't
60
+ unwrap fragments — a fragment's `.type` is `Fragment`, never the slot component.
61
+
62
+ **Apply:** put a confirm dialog **outside** the `Drawer`/`Modal`/`ProfileCard`, as a sibling in a
63
+ wrapping fragment — never among the header/body/footer slots. When a ternary picks between bodies,
64
+ branch into a variable rendered inside one body slot, rather than returning fragment-wrapped slots
65
+ per branch.
66
+
67
+ ### `Select`'s `onChange` never fires for the built-in placeholder
68
+
69
+ `Select` resolves the changed value against its `options` list and calls `onChange` only on a hit.
70
+ The built-in `placeholder` renders as its own empty-value option, which isn't in `options` — so
71
+ picking it resolves to no match and nothing fires. An "Any / All / None" reset built on the
72
+ placeholder alone is a dead control; the value snaps back and no filter clears.
73
+
74
+ **Apply:** fine for a plain pick-one-of-N. For a clearable filter, add an explicit
75
+ `{ id: "", label: "Any" }` entry to `options` instead of relying on the placeholder.
76
+
77
+ ### `MultiSelect` is an email-chip input, not a picker
78
+
79
+ Despite the name, `MultiSelect` takes **no `options` prop at all**. Typed text is validated as an
80
+ email address and rejected if it doesn't match — there is no way to choose from a product/entity
81
+ list.
82
+
83
+ **Apply:** use `SearchSelect` with `isMultiple` to pick multiple values from an options list.
84
+
85
+ ### `RadioButton` has a closed prop list — no `...rest`, no `name`
86
+
87
+ It forwards only a fixed set of props (`label`, `colorScheme`, `isChecked`, `onChange`,
88
+ `isDisabled`, `size`, `value`, `defaultChecked`, plus label/tooltip props) to the underlying radio
89
+ — a `data-*` hook, `aria-describedby`, or a `ref` cannot reach it.
90
+
91
+ **Apply:** it still joins an external `RadioGroup` correctly (grouping comes from context, no
92
+ `name` prop needed) — don't avoid it just for grouping. Reach for the library's Chakra escape
93
+ hatch only when you need something outside that closed list.
94
+
95
+ ### `Table`'s `onRowClick` is dead if every column has a `node` renderer
96
+
97
+ Row-click is gated **per column**, not per row: a column supplying a custom `node` renderer
98
+ swallows the click on that cell. Give every column a `node` and `onRowClick` becomes dead code —
99
+ accepted, typed, never called.
100
+
101
+ **Apply:** leave at least one column without a `node`, or put an explicit control inside a `node`
102
+ renderer instead of relying on row click.
103
+
104
+ ### `InputTextArea`'s `width` defaults to the number `500`, and no `id` means no label
105
+
106
+ `width` defaults to `500`, forwarded straight through — `500` isn't a sizing-scale key, so it
107
+ renders a literal `500px`, not a max and not responsive. It only shows up on a narrow screen.
108
+ Separately, `id` feeds both the visible label and the field itself with no fallback — omit it and
109
+ the label is associated with nothing; the field has no accessible name.
110
+
111
+ **Apply:** pass `width="100%"` **and** `id` at every call site.
112
+
113
+ ### `error` without `errorMessage` renders the literal word "Error" — and erases `helperText`
114
+
115
+ Every field that shows validation errors (`TextInput`, `InputTextArea`, `Select`, `MultiSelect`,
116
+ `SelectV2`, `SearchSelect`, `SelectSearch`, `Search`, `Checkbox`, the `DatePicker` variants,
117
+ `PhoneNumberInput`) falls back to the literal word **"Error"** when `error` is `true` but
118
+ `errorMessage` is empty. Worse, `helperText` only renders when `!error` — so the guidance
119
+ disappears exactly when it's needed.
120
+
121
+ **Apply:** never pass `error` without `errorMessage`. Gate `error` on a `touched` flag so a field
122
+ whose defaults arrive asynchronously doesn't open already red.
123
+
124
+ ### A wrapper presetting a prop must destructure with a default, not rely on JSX prop order
125
+
126
+ Wrapping a library component to preset a prop (e.g. `Table`'s `variant`) as
127
+ `<Table variant="studio" {...props} />` silently reverts to the library's own default the moment a
128
+ caller passes `variant={undefined}` — a later spread always wins in JSX, even with `undefined`,
129
+ and the library component resolves that `undefined` via its own default parameter.
130
+
131
+ **Apply:**
132
+ ```tsx
133
+ const AppTable = ({ variant = 'studio', ...props }: TableProps) => <Table variant={variant} {...props} />;
134
+ ```
135
+ Applies to any library prop with a default value, not just `Table`'s `variant`.
package/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  A comprehensive React component library built with TypeScript, providing a rich set of UI components for modern web applications.
4
4
 
5
+ > **Building with this library (including via an AI assistant)?** Read
6
+ > [COMPONENT-GOTCHAS.md](./COMPONENT-GOTCHAS.md) first — it documents known silent-failure traps
7
+ > and which near-duplicate component to pick for what. It ships with this package, so it's
8
+ > available at `node_modules/pixelize-design-library/COMPONENT-GOTCHAS.md` too.
9
+
5
10
  ## Features
6
11
 
7
12
  - 🎨 **Modern Design System** - Consistent and beautiful components
@@ -70,11 +70,6 @@ const validate = (apps) => {
70
70
  warnOnce(`url-${app.id || i}`, `app "${app.name}" has no url; it cannot be opened.`);
71
71
  });
72
72
  };
73
- // Past a pointer's pause threshold, so crossing the launcher on the way elsewhere does not open it,
74
- // but short enough that deliberately resting on it feels instant.
75
- const HOVER_OPEN_DELAY = 450;
76
- // Long enough to travel the gap between the launcher and the panel below it.
77
- const HOVER_CLOSE_GRACE = 250;
78
73
  // Flip back to true to restore `searchThreshold` opening the field on its own.
79
74
  const SEARCH_OPENS_BY_COUNT = false;
80
75
  const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable, searchThreshold = 8, shortcut = null, showShortcutHint = true, hideWhenAlone = true, emptyMessage = "No app matches that name.", footer, ariaLabel = "Switch app", panelWidth, maxHeight = "24rem", isLoading = false, }) => {
@@ -85,11 +80,8 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
85
80
  const [activeIndex, setActiveIndex] = (0, react_1.useState)(0);
86
81
  const searchRef = (0, react_1.useRef)(null);
87
82
  const triggerRef = (0, react_1.useRef)(null);
88
- const panelRef = (0, react_1.useRef)(null);
89
83
  const itemRefs = (0, react_1.useRef)([]);
90
84
  const typeahead = (0, react_1.useRef)({ buffer: "", at: 0 });
91
- const hoverTimer = (0, react_1.useRef)();
92
- const openedByHover = (0, react_1.useRef)(false);
93
85
  (0, react_1.useEffect)(() => validate(apps), [apps]);
94
86
  // Search is off while the panel's design settles: the app count alone no longer opens it. An
95
87
  // explicit `isSearchable` still wins, so nothing about the prop changed.
@@ -106,41 +98,6 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
106
98
  }
107
99
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(next);
108
100
  }, [onOpen, onClose, onOpenChange, requestExclusiveOpen]);
109
- const cancelHoverTimer = (0, react_1.useCallback)(() => {
110
- if (hoverTimer.current)
111
- clearTimeout(hoverTimer.current);
112
- hoverTimer.current = undefined;
113
- }, []);
114
- (0, react_1.useEffect)(() => cancelHoverTimer, [cancelHoverTimer]);
115
- // Touch and stylus report no hover; a timer there would open the panel on a tap-and-hold.
116
- const pointerHovers = () => {
117
- var _a;
118
- return typeof window !== "undefined" &&
119
- ((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(hover: hover) and (pointer: fine)").matches);
120
- };
121
- const handleHoverStart = (0, react_1.useCallback)(() => {
122
- if (isOpen || !pointerHovers())
123
- return;
124
- cancelHoverTimer();
125
- hoverTimer.current = setTimeout(() => {
126
- openedByHover.current = true;
127
- setOpen(true);
128
- }, HOVER_OPEN_DELAY);
129
- }, [isOpen, cancelHoverTimer, setOpen]);
130
- // A click-opened panel is sticky, and a keyboard user inside it is not "hovering away".
131
- const handleHoverEnd = (0, react_1.useCallback)(() => {
132
- cancelHoverTimer();
133
- if (!isOpen || !openedByHover.current)
134
- return;
135
- hoverTimer.current = setTimeout(() => {
136
- var _a;
137
- if ((_a = panelRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement))
138
- return;
139
- openedByHover.current = false;
140
- setOpen(false);
141
- setQuery("");
142
- }, HOVER_CLOSE_GRACE);
143
- }, [isOpen, cancelHoverTimer, setOpen]);
144
101
  const closeAndRefocus = (0, react_1.useCallback)(() => {
145
102
  var _a;
146
103
  setOpen(false);
@@ -264,7 +221,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
264
221
  const currentSub = (0, outlineRung_1.liftToContrast)(theme.colors.textMuted, currentBg, accentText_1.AA_NORMAL_TEXT);
265
222
  const width = panelWidth !== null && panelWidth !== void 0 ? panelWidth : "28rem";
266
223
  const hint = shortcut && showShortcutHint ? (0, AppSwitcherProps_1.formatShortcut)(shortcut) : null;
267
- return ((0, jsx_runtime_1.jsxs)(react_2.Popover, { isOpen: isOpen, onOpen: () => setOpen(true), onClose: () => { setOpen(false); setQuery(""); }, placement: "bottom-end", initialFocusRef: showSearch ? searchRef : undefined, isLazy: true, children: [(0, jsx_runtime_1.jsx)(react_2.PopoverTrigger, { children: (0, jsx_runtime_1.jsx)(react_2.Flex, { ref: triggerRef, as: "button", type: "button", "aria-label": ariaLabel, align: "center", justify: "center", boxSize: s.trigger, borderRadius: "lg", border: "1px solid", borderColor: isOpen ? theme.colors.boxborder[500] : theme.colors.boxborder[200], bg: isOpen ? theme.colors.backgroundColor.tertiary : "transparent", color: isOpen ? theme.colors.text[900] : theme.colors.textMuted, cursor: "pointer", transition: "background 0.15s ease, border-color 0.15s ease", _hover: { bg: rowHoverBg, color: theme.colors.text[900] }, onMouseEnter: handleHoverStart, onMouseLeave: handleHoverEnd, onClick: () => { openedByHover.current = false; cancelHoverTimer(); }, onKeyDown: (e) => {
224
+ return ((0, jsx_runtime_1.jsxs)(react_2.Popover, { isOpen: isOpen, onOpen: () => setOpen(true), onClose: () => { setOpen(false); setQuery(""); }, placement: "bottom-end", initialFocusRef: showSearch ? searchRef : undefined, isLazy: true, children: [(0, jsx_runtime_1.jsx)(react_2.PopoverTrigger, { children: (0, jsx_runtime_1.jsx)(react_2.Flex, { ref: triggerRef, as: "button", type: "button", "aria-label": ariaLabel, align: "center", justify: "center", boxSize: s.trigger, borderRadius: "lg", border: "1px solid", borderColor: isOpen ? theme.colors.boxborder[500] : theme.colors.boxborder[200], bg: isOpen ? theme.colors.backgroundColor.tertiary : "transparent", color: isOpen ? theme.colors.text[900] : theme.colors.textMuted, cursor: "pointer", transition: "background 0.15s ease, border-color 0.15s ease", _hover: { bg: rowHoverBg, color: theme.colors.text[900] }, onKeyDown: (e) => {
268
225
  if (!isOpen) {
269
226
  if (e.key === "ArrowDown") {
270
227
  e.preventDefault();
@@ -273,7 +230,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
273
230
  return;
274
231
  }
275
232
  handlePanelKeyDown(e);
276
- }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Grip, { size: s.icon + 1 }) }) }), (0, jsx_runtime_1.jsx)(react_2.PopoverContent, { ref: panelRef, onMouseEnter: cancelHoverTimer, onMouseLeave: handleHoverEnd, w: width, maxW: "calc(100vw - 1.5rem)", textAlign: "start", onKeyDown: handlePanelKeyDown, bg: theme.colors.backgroundColor.light, borderRadius: "xl", overflow: "hidden", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}`, _focus: { outline: "none", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}` }, children: (0, jsx_runtime_1.jsxs)(react_2.PopoverBody, { p: 0, children: [(0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, px: 3, pt: 3, pb: showSearch ? 2 : 1, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.6875rem", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: theme.colors.textMuted, children: HEADING }), (0, jsx_runtime_1.jsx)(react_2.Box, { flex: "1" }), hint && ((0, jsx_runtime_1.jsx)(react_2.Text, { as: "kbd", fontSize: "0.6875rem", fontFamily: "inherit", color: theme.colors.textMuted, border: "1px solid", borderRadius: "base", px: 1.5, py: 0.5, children: hint }))] }), showSearch && ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 3, pb: 2, children: (0, jsx_runtime_1.jsxs)(react_2.InputGroup, { size: "sm", children: [(0, jsx_runtime_1.jsx)(react_2.InputLeftElement, { pointerEvents: "none", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, color: theme.colors.textMuted }) }), (0, jsx_runtime_1.jsx)(react_2.Input, { ref: searchRef, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps", borderRadius: "lg", "aria-label": "Search apps", onKeyDown: (e) => {
233
+ }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Grip, { size: s.icon + 1 }) }) }), (0, jsx_runtime_1.jsx)(react_2.PopoverContent, { w: width, maxW: "calc(100vw - 1.5rem)", textAlign: "start", onKeyDown: handlePanelKeyDown, bg: theme.colors.backgroundColor.light, borderRadius: "xl", overflow: "hidden", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}`, _focus: { outline: "none", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}` }, children: (0, jsx_runtime_1.jsxs)(react_2.PopoverBody, { p: 0, children: [(0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, px: 3, pt: 3, pb: showSearch ? 2 : 1, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.6875rem", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: theme.colors.textMuted, children: HEADING }), (0, jsx_runtime_1.jsx)(react_2.Box, { flex: "1" }), hint && ((0, jsx_runtime_1.jsx)(react_2.Text, { as: "kbd", fontSize: "0.6875rem", fontFamily: "inherit", color: theme.colors.textMuted, border: "1px solid", borderRadius: "base", px: 1.5, py: 0.5, children: hint }))] }), showSearch && ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 3, pb: 2, children: (0, jsx_runtime_1.jsxs)(react_2.InputGroup, { size: "sm", children: [(0, jsx_runtime_1.jsx)(react_2.InputLeftElement, { pointerEvents: "none", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, color: theme.colors.textMuted }) }), (0, jsx_runtime_1.jsx)(react_2.Input, { ref: searchRef, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps", borderRadius: "lg", "aria-label": "Search apps", onKeyDown: (e) => {
277
234
  if (e.key === "ArrowDown" && visible.length > 0) {
278
235
  e.preventDefault();
279
236
  focusItem(0);
@@ -4,8 +4,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
4
4
  const react_1 = require("@chakra-ui/react");
5
5
  const lucide_react_1 = require("lucide-react");
6
6
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
7
- // `id` lets a field point `aria-describedby` here so the message is part of the control's
8
- // description on every focus. `role="alert"` alone announces once, when it first renders.
7
+ // `id` lets a field point `aria-describedby` here; `role="alert"` announces once, on render.
8
+ // Always pass `errorMessage` when `error` is true — omitted, this renders the literal word "Error", and every caller hides `helperText` while `error` is true.
9
9
  const ErrorMessage = ({ errorMessage, id }) => {
10
10
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
11
11
  return ((0, jsx_runtime_1.jsxs)(react_1.Flex, { id: id, align: "center", color: colors.semanticText.error, fontSize: "0.875rem", role: "alert", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Info, { width: "0.875rem" }), (0, jsx_runtime_1.jsx)(react_1.Text, { ml: "0.188rem", children: errorMessage !== null && errorMessage !== void 0 ? errorMessage : "Error" })] }));
@@ -3,5 +3,6 @@ export type DrawerProps = Pick<ChakraDrawerProps, "isOpen" | "onClose" | "placem
3
3
  size?: "xs" | "sm" | "md" | "lg" | "xl" | "full";
4
4
  placement?: "top" | "right" | "bottom" | "left";
5
5
  vaiant?: "solid" | "outline" | "ghost" | "link";
6
+ /** Only DrawerHeader/DrawerBody/DrawerFooter render; other elements and fragment-wrapped slots are silently dropped. */
6
7
  children?: React.ReactNode;
7
8
  };
@@ -1,4 +1,5 @@
1
1
  import { TextareaProps } from "@chakra-ui/react";
2
+ /** width defaults to 500 → literal "500px", not responsive (pass "100%"); id is required or the label has no accessible association. */
2
3
  export type InputTextAreaProps = Pick<TextareaProps, "placeholder" | "value" | "onChange" | "onBlur" | "name" | "id" | "size" | "resize" | "isDisabled" | "isReadOnly" | "isRequired" | "variant" | "autoComplete"> & {
3
4
  label?: string;
4
5
  error?: boolean;
@@ -5,6 +5,7 @@ export type ChakraModelProps = Pick<ModalProps, "isOpen" | "onClose" | "finalFoc
5
5
  overlaybackdropInvert?: string;
6
6
  overlaybackdropBlur?: string;
7
7
  size: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "full";
8
+ /** Only ModalHeader/ModalBody/ModalFooter render; other elements and fragment-wrapped slots are silently dropped. */
8
9
  children?: React.ReactNode;
9
10
  isLoading?: boolean;
10
11
  };
@@ -1,3 +1,4 @@
1
+ /** No `options` prop — typed text is validated as an email, not picked from a list; use `SearchSelect isMultiple`. */
1
2
  export type MultiSelectProps = {
2
3
  value: MultiSelctOPtions[];
3
4
  onValueChange: (options: MultiSelctOPtions[]) => void;
@@ -3,6 +3,7 @@ export type ProfileCardVariant = "elevated" | "outline" | "filled" | "unstyled";
3
3
  /** @deprecated Misspelling kept for existing callers — use "outline". */
4
4
  export type ProfileCardVariantLegacy = "outlein";
5
5
  export type ProfileCardProps = Pick<CardProps, "direction" | "maxW" | "align" | "justify" | "overflow"> & {
6
+ /** Only direct ProfileCardHeader/Body/Footer children render — no wrapping fragment, no other siblings. */
6
7
  children: React.ReactNode;
7
8
  variant?: ProfileCardVariant | ProfileCardVariantLegacy;
8
9
  size?: "sm" | "md" | "lg";
@@ -1,4 +1,5 @@
1
1
  import { RadioProps, RadioGroupProps } from '@chakra-ui/react';
2
+ /** Closed prop list, no ...rest — for aria-* or a ref use Chakra's Radio (Primitives barrel) instead. Still joins an external RadioGroup correctly via Chakra's own context. */
2
3
  export type ChakraRadioProps = Pick<RadioProps, "size" | "colorScheme" | "isChecked" | "onChange" | "isDisabled" | "value" | "defaultChecked"> & {
3
4
  label: string;
4
5
  labelFontSize?: string;
@@ -1,2 +1,2 @@
1
1
  import { SegmentedControlProps } from "./SegmentedControlProps";
2
- export default function SegmentedControl<T extends string = string>({ options, value, onChange, size, isFullWidth, "aria-label": ariaLabel, }: SegmentedControlProps<T>): import("react/jsx-runtime").JSX.Element;
2
+ export default function SegmentedControl<T extends string = string>({ options, value, onChange, size, isFullWidth, variant, "aria-label": ariaLabel, }: SegmentedControlProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -5,18 +5,38 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const react_2 = require("@chakra-ui/react");
7
7
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
8
+ const filledSurface_1 = require("../../Theme/tokens/builders/filledSurface");
8
9
  const SIZES = {
9
10
  sm: { h: "1.625rem", px: "0.625rem", fontSize: "0.75rem" },
10
11
  md: { h: "2rem", px: "0.875rem", fontSize: "0.8125rem" },
11
12
  };
12
- function SegmentedControl({ options, value, onChange, size = "md", isFullWidth = false, "aria-label": ariaLabel, }) {
13
+ function SegmentedControl({ options, value, onChange, size = "md", isFullWidth = false, variant = "default", "aria-label": ariaLabel, }) {
13
14
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
14
15
  const dims = SIZES[size];
15
16
  const refs = (0, react_1.useRef)([]);
17
+ const trackRef = (0, react_1.useRef)(null);
18
+ const isBrandSlide = variant === "brandSlide";
19
+ const isUnderlineSlide = variant === "underlineSlide";
20
+ const hasIndicator = isBrandSlide || isUnderlineSlide;
21
+ const brandFill = (0, filledSurface_1.resolveFilledSurface)(colors.primary, colors);
22
+ const [pill, setPill] = (0, react_1.useState)(null);
16
23
  const selectedIndex = options.findIndex((o) => o.value === value);
17
24
  const enabled = options.reduce((acc, o, i) => (o.isDisabled ? acc : [...acc, i]), []);
18
25
  // WAI-APG: with nothing selected, the first enabled segment owns the tab stop.
19
26
  const tabIndex = selectedIndex >= 0 ? selectedIndex : enabled[0];
27
+ (0, react_1.useLayoutEffect)(() => {
28
+ if (!hasIndicator)
29
+ return;
30
+ const track = trackRef.current;
31
+ const node = refs.current[selectedIndex];
32
+ if (!track || !node) {
33
+ setPill(null);
34
+ return;
35
+ }
36
+ const trackRect = track.getBoundingClientRect();
37
+ const nodeRect = node.getBoundingClientRect();
38
+ setPill({ left: nodeRect.left - trackRect.left, width: nodeRect.width });
39
+ }, [hasIndicator, selectedIndex, options, isFullWidth, size]);
20
40
  const selectAt = (index) => {
21
41
  var _a;
22
42
  (_a = refs.current[index]) === null || _a === void 0 ? void 0 : _a.focus();
@@ -46,13 +66,23 @@ function SegmentedControl({ options, value, onChange, size = "md", isFullWidth =
46
66
  selectAt(enabled[enabled.length - 1]);
47
67
  }
48
68
  };
49
- return ((0, jsx_runtime_1.jsx)(react_2.Flex, { role: "radiogroup", "aria-label": ariaLabel, onKeyDown: handleKeyDown, display: "inline-flex", align: "center", gap: "0.125rem", p: "0.125rem", borderRadius: "md", bg: colors.background[600], w: isFullWidth ? "100%" : "fit-content", children: options.map((option, index) => {
50
- const isSelected = option.value === value;
51
- return ((0, jsx_runtime_1.jsxs)(react_2.Box, { as: "button", type: "button", ref: (node) => {
52
- refs.current[index] = node;
53
- }, role: "radio", "aria-checked": isSelected, disabled: option.isDisabled, tabIndex: index === tabIndex ? 0 : -1, onClick: () => onChange(option.value), display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "0.375rem", flex: isFullWidth ? "1" : undefined, h: dims.h, px: dims.px, borderRadius: "base", borderWidth: "1px", borderStyle: "solid", borderColor: isSelected ? colors.background[400] : "transparent", bg: isSelected ? colors.background[50] : "transparent", boxShadow: isSelected ? "sm" : "none", color: isSelected ? colors.text[900] : colors.text[600], fontSize: dims.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: "background-color 0.2s ease, color 0.2s ease", _hover: option.isDisabled || isSelected ? undefined : { color: colors.text[900] }, _focusVisible: {
54
- outline: "none",
55
- boxShadow: `0 0 0 2px ${colors.primary[300]}`,
56
- }, children: [option.icon, option.label] }, option.value));
57
- }) }));
69
+ return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { ref: trackRef, role: "radiogroup", "aria-label": ariaLabel, onKeyDown: handleKeyDown, position: "relative", display: "inline-flex", align: "center", gap: isUnderlineSlide ? "0" : "0.125rem", p: isUnderlineSlide ? "0" : "0.125rem", borderRadius: isUnderlineSlide ? "none" : "md", borderBottom: isUnderlineSlide ? `0.0625rem solid ${colors.boxborder[200]}` : undefined, bg: isBrandSlide ? colors.primary.opacity[8] : isUnderlineSlide ? "transparent" : colors.background[600], w: isFullWidth ? "100%" : "fit-content", children: [isBrandSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", top: "0.125rem", left: 0, h: dims.h, w: `${pill.width}px`, borderRadius: "base", bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), 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: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), options.map((option, index) => {
70
+ const isSelected = option.value === value;
71
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { as: "button", type: "button", ref: (node) => {
72
+ refs.current[index] = node;
73
+ }, role: "radio", "aria-checked": isSelected, disabled: option.isDisabled, tabIndex: index === tabIndex ? 0 : -1, onClick: () => onChange(option.value), position: "relative", zIndex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "0.375rem", flex: isFullWidth ? "1" : undefined, h: dims.h, px: dims.px, borderRadius: isUnderlineSlide ? "none" : "base", borderWidth: "1px", borderStyle: "solid", borderColor: hasIndicator
74
+ ? "transparent"
75
+ : isSelected
76
+ ? colors.primary.opacity[32]
77
+ : "transparent", bg: hasIndicator ? "transparent" : isSelected ? colors.backgroundColor.light : "transparent", boxShadow: hasIndicator ? "none" : isSelected ? "sm" : "none", color: isBrandSlide
78
+ ? isSelected
79
+ ? brandFill.color
80
+ : colors.text[600]
81
+ : isSelected
82
+ ? colors.accentText
83
+ : colors.text[600], fontSize: dims.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: "color 0.2s ease", _hover: option.isDisabled || isSelected ? undefined : { color: colors.text[900] }, _focusVisible: {
84
+ outline: "none",
85
+ boxShadow: `0 0 0 2px ${colors.primary[300]}`,
86
+ }, children: [option.icon, option.label] }, option.value));
87
+ })] }));
58
88
  }
@@ -1,4 +1,5 @@
1
1
  export type SegmentedControlSize = "sm" | "md";
2
+ export type SegmentedControlVariant = "default" | "brandSlide" | "underlineSlide";
2
3
  export type SegmentedControlOption<T extends string = string> = {
3
4
  value: T;
4
5
  label: string;
@@ -12,5 +13,7 @@ export type SegmentedControlProps<T extends string = string> = {
12
13
  onChange: (value: T) => void;
13
14
  size?: SegmentedControlSize;
14
15
  isFullWidth?: boolean;
16
+ /** brandSlide = primary view switcher; underlineSlide = dense toolbars/headers. */
17
+ variant?: SegmentedControlVariant;
15
18
  "aria-label": string;
16
19
  };
@@ -10,6 +10,7 @@ export type chakraSelectProps = Pick<SelectProps, "placeholder" | "size" | "vari
10
10
  options: OptionProp[];
11
11
  width?: string | number;
12
12
  height?: string | number;
13
+ /** Selecting the built-in placeholder (value="") never fires this unless `options` has an explicit `id: ""` entry. */
13
14
  onChange: (selectedOption: OptionProp | undefined) => void;
14
15
  formControlStyle?: React.CSSProperties;
15
16
  isInformation?: boolean;
@@ -36,6 +36,7 @@ export type TableProps = {
36
36
  onSelection?: (selected: (string | number)[]) => void;
37
37
  isPagination?: boolean;
38
38
  selections?: (string | number)[];
39
+ /** Fires per cell, gated on the column: a column with `node` swallows its clicks. */
39
40
  onRowClick?: (row: DataObject, header: Record<string | number, string | number>) => void;
40
41
  isActionFreeze?: boolean;
41
42
  paginationMode?: "client" | "server";
@@ -4,45 +4,95 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
- const react_1 = require("@chakra-ui/react");
7
+ const react_1 = require("react");
8
+ const react_2 = require("@chakra-ui/react");
8
9
  const lucide_react_1 = require("lucide-react");
9
10
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
10
11
  const outlineRung_1 = require("../../Theme/tokens/builders/outlineRung");
12
+ const filledSurface_1 = require("../../Theme/tokens/builders/filledSurface");
11
13
  const ToolTip_1 = __importDefault(require("../ToolTip/ToolTip"));
12
- const TableToggle = ({ value, onChange }) => {
14
+ const MODES = ["grid", "list"];
15
+ const TableToggle = ({ value, onChange, variant = "default" }) => {
13
16
  const theme = (0, useCustomTheme_1.useCustomTheme)();
14
17
  const { colors } = theme;
18
+ const isBrandSlide = variant === "brandSlide";
19
+ const isUnderlineSlide = variant === "underlineSlide";
20
+ const hasIndicator = isBrandSlide || isUnderlineSlide;
21
+ const brandFill = (0, filledSurface_1.resolveFilledSurface)(colors.primary, colors);
15
22
  const restInk = (0, outlineRung_1.liftToContrast)(colors.background[800], colors.background[600], outlineRung_1.AA_NON_TEXT);
16
23
  const hoverInk = (0, outlineRung_1.liftToContrast)(colors.background[900], colors.background[400], outlineRung_1.AA_NON_TEXT);
24
+ const trackRef = (0, react_1.useRef)(null);
25
+ const refs = (0, react_1.useRef)([]);
26
+ const [pill, setPill] = (0, react_1.useState)(null);
27
+ const selectedIndex = MODES.indexOf(value);
28
+ (0, react_1.useLayoutEffect)(() => {
29
+ if (!hasIndicator)
30
+ return;
31
+ const track = trackRef.current;
32
+ const node = refs.current[selectedIndex];
33
+ if (!track || !node) {
34
+ setPill(null);
35
+ return;
36
+ }
37
+ const trackRect = track.getBoundingClientRect();
38
+ const nodeRect = node.getBoundingClientRect();
39
+ setPill({ left: nodeRect.left - trackRect.left, width: nodeRect.width });
40
+ }, [hasIndicator, selectedIndex]);
17
41
  const styles = {
18
42
  container: {
19
43
  display: "flex",
20
44
  alignItems: "center",
21
- bg: colors.background[600],
22
- p: "0.125rem",
23
- borderRadius: "md",
45
+ bg: isBrandSlide ? colors.primary.opacity[8] : isUnderlineSlide ? "transparent" : colors.background[600],
46
+ p: isUnderlineSlide ? "0" : "0.125rem",
47
+ borderRadius: isUnderlineSlide ? "none" : "md",
48
+ borderBottom: isUnderlineSlide ? `0.0625rem solid ${colors.boxborder[200]}` : undefined,
24
49
  height: "2rem",
25
- gap: "0.125rem",
50
+ gap: isUnderlineSlide ? "0" : "0.125rem",
26
51
  width: "6.3rem",
52
+ position: "relative",
27
53
  },
28
54
  button: (mode) => ({
29
- bg: value === mode ? colors.background[50] : "transparent",
30
- boxShadow: value === mode ? "md" : "none",
31
- color: value === mode ? colors.text[900] : restInk,
32
- borderRadius: "base",
55
+ position: "relative",
56
+ zIndex: 1,
57
+ bg: hasIndicator ? "transparent" : value === mode ? colors.backgroundColor.light : "transparent",
58
+ boxShadow: hasIndicator ? "none" : value === mode ? "md" : "none",
59
+ color: isBrandSlide
60
+ ? value === mode
61
+ ? brandFill.color
62
+ : restInk
63
+ : value === mode
64
+ ? colors.accentText
65
+ : restInk,
66
+ borderRadius: isUnderlineSlide ? "none" : "base",
33
67
  height: "1.75rem",
34
- border: "0.031rem solid",
35
- borderColor: value === mode ? colors.background[400] : "transparent",
68
+ border: hasIndicator ? "none" : "0.031rem solid",
69
+ borderColor: hasIndicator
70
+ ? "transparent"
71
+ : value === mode
72
+ ? colors.primary.opacity[32]
73
+ : "transparent",
36
74
  padding: "0.313rem 0.813rem",
37
75
  _hover: {
38
- bg: value === mode
39
- ? colors.background[50]
40
- : colors.background[400],
41
- color: value === mode ? colors.text[900] : hoverInk,
76
+ bg: hasIndicator
77
+ ? "transparent"
78
+ : value === mode
79
+ ? colors.backgroundColor.light
80
+ : colors.background[400],
81
+ color: isBrandSlide
82
+ ? value === mode
83
+ ? brandFill.color
84
+ : hoverInk
85
+ : value === mode
86
+ ? colors.accentText
87
+ : hoverInk,
42
88
  },
43
- transition: "all 0.3s ease-in-out",
89
+ transition: "color 0.2s ease-in-out",
44
90
  }),
45
91
  };
46
- return ((0, jsx_runtime_1.jsxs)(react_1.Box, { ...styles.container, children: [(0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Table View", placement: "top", fontSize: 12, hasArrow: true, children: (0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": "Grid View", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Table2, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("grid"), ...styles.button("grid") }) }), (0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Kanban View", placement: "top", fontSize: 12, hasArrow: true, children: (0, jsx_runtime_1.jsx)(react_1.IconButton, { "aria-label": "List View", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Columns3, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("list"), ...styles.button("list") }) })] }));
92
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { ref: trackRef, ...styles.container, children: [isBrandSlide && pill && ((0, jsx_runtime_1.jsx)(react_2.Box, { "aria-hidden": "true", position: "absolute", top: "0.125rem", left: 0, h: "1.75rem", w: `${pill.width}px`, borderRadius: "base", bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), 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: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), (0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Table View", placement: "top", fontSize: 12, hasArrow: true, children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { ref: (node) => {
93
+ refs.current[0] = node;
94
+ }, "aria-label": "Grid View", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Table2, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("grid"), ...styles.button("grid") }) }), (0, jsx_runtime_1.jsx)(ToolTip_1.default, { label: "Kanban View", placement: "top", fontSize: 12, hasArrow: true, children: (0, jsx_runtime_1.jsx)(react_2.IconButton, { ref: (node) => {
95
+ refs.current[1] = node;
96
+ }, "aria-label": "List View", icon: (0, jsx_runtime_1.jsx)(lucide_react_1.Columns3, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("list"), ...styles.button("list") }) })] }));
47
97
  };
48
98
  exports.default = TableToggle;
@@ -1,5 +1,8 @@
1
1
  export type ViewMode = "grid" | "list";
2
+ export type TableToggleVariant = "default" | "brandSlide" | "underlineSlide";
2
3
  export type ViewToggleProps = {
3
4
  value: ViewMode;
4
5
  onChange: (val: ViewMode) => void;
6
+ /** brandSlide = primary view switcher; underlineSlide = dense toolbars/headers. */
7
+ variant?: TableToggleVariant;
5
8
  };
@@ -14,5 +14,5 @@ function ToggleSwitch({ isChecked, onChange, isDisabled = false, size = "md", id
14
14
  return ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "button", type: "button", id: id, role: "switch", "aria-checked": isChecked, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, disabled: isDisabled, onClick: () => onChange(!isChecked), position: "relative", display: "inline-flex", alignItems: "center", flexShrink: 0, w: dims.w, h: dims.h, p: 0, borderRadius: "full", borderWidth: "1px", borderStyle: "solid", borderColor: isChecked ? colors.primary[500] : colors.boxborder[500], bg: isChecked ? colors.primary[500] : colors.background[600], opacity: isDisabled ? 0.5 : 1, cursor: isDisabled ? "not-allowed" : "pointer", transition: "background-color 0.2s ease, border-color 0.2s ease", _focusVisible: {
15
15
  outline: "none",
16
16
  boxShadow: `0 0 0 2px ${colors.primary[300]}`,
17
- }, children: (0, jsx_runtime_1.jsx)(react_1.Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.background[50], boxShadow: "sm", transition: "transform 0.2s ease", transform: isChecked ? `translateX(${dims.travel})` : "translateX(0)" }) }));
17
+ }, children: (0, jsx_runtime_1.jsx)(react_1.Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.white, boxShadow: "sm", transition: "transform 0.2s ease", transform: isChecked ? `translateX(${dims.travel})` : "translateX(0)" }) }));
18
18
  }
@@ -35,11 +35,6 @@ const validate = (apps) => {
35
35
  warnOnce(`url-${app.id || i}`, `app "${app.name}" has no url; it cannot be opened.`);
36
36
  });
37
37
  };
38
- // Past a pointer's pause threshold, so crossing the launcher on the way elsewhere does not open it,
39
- // but short enough that deliberately resting on it feels instant.
40
- const HOVER_OPEN_DELAY = 450;
41
- // Long enough to travel the gap between the launcher and the panel below it.
42
- const HOVER_CLOSE_GRACE = 250;
43
38
  // Flip back to true to restore `searchThreshold` opening the field on its own.
44
39
  const SEARCH_OPENS_BY_COUNT = false;
45
40
  const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable, searchThreshold = 8, shortcut = null, showShortcutHint = true, hideWhenAlone = true, emptyMessage = "No app matches that name.", footer, ariaLabel = "Switch app", panelWidth, maxHeight = "24rem", isLoading = false, }) => {
@@ -50,11 +45,8 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
50
45
  const [activeIndex, setActiveIndex] = useState(0);
51
46
  const searchRef = useRef(null);
52
47
  const triggerRef = useRef(null);
53
- const panelRef = useRef(null);
54
48
  const itemRefs = useRef([]);
55
49
  const typeahead = useRef({ buffer: "", at: 0 });
56
- const hoverTimer = useRef();
57
- const openedByHover = useRef(false);
58
50
  useEffect(() => validate(apps), [apps]);
59
51
  // Search is off while the panel's design settles: the app count alone no longer opens it. An
60
52
  // explicit `isSearchable` still wins, so nothing about the prop changed.
@@ -71,41 +63,6 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
71
63
  }
72
64
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(next);
73
65
  }, [onOpen, onClose, onOpenChange, requestExclusiveOpen]);
74
- const cancelHoverTimer = useCallback(() => {
75
- if (hoverTimer.current)
76
- clearTimeout(hoverTimer.current);
77
- hoverTimer.current = undefined;
78
- }, []);
79
- useEffect(() => cancelHoverTimer, [cancelHoverTimer]);
80
- // Touch and stylus report no hover; a timer there would open the panel on a tap-and-hold.
81
- const pointerHovers = () => {
82
- var _a;
83
- return typeof window !== "undefined" &&
84
- ((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(hover: hover) and (pointer: fine)").matches);
85
- };
86
- const handleHoverStart = useCallback(() => {
87
- if (isOpen || !pointerHovers())
88
- return;
89
- cancelHoverTimer();
90
- hoverTimer.current = setTimeout(() => {
91
- openedByHover.current = true;
92
- setOpen(true);
93
- }, HOVER_OPEN_DELAY);
94
- }, [isOpen, cancelHoverTimer, setOpen]);
95
- // A click-opened panel is sticky, and a keyboard user inside it is not "hovering away".
96
- const handleHoverEnd = useCallback(() => {
97
- cancelHoverTimer();
98
- if (!isOpen || !openedByHover.current)
99
- return;
100
- hoverTimer.current = setTimeout(() => {
101
- var _a;
102
- if ((_a = panelRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement))
103
- return;
104
- openedByHover.current = false;
105
- setOpen(false);
106
- setQuery("");
107
- }, HOVER_CLOSE_GRACE);
108
- }, [isOpen, cancelHoverTimer, setOpen]);
109
66
  const closeAndRefocus = useCallback(() => {
110
67
  var _a;
111
68
  setOpen(false);
@@ -229,7 +186,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
229
186
  const currentSub = liftToContrast(theme.colors.textMuted, currentBg, AA_NORMAL_TEXT);
230
187
  const width = panelWidth !== null && panelWidth !== void 0 ? panelWidth : "28rem";
231
188
  const hint = shortcut && showShortcutHint ? formatShortcut(shortcut) : null;
232
- return (_jsxs(Popover, { isOpen: isOpen, onOpen: () => setOpen(true), onClose: () => { setOpen(false); setQuery(""); }, placement: "bottom-end", initialFocusRef: showSearch ? searchRef : undefined, isLazy: true, children: [_jsx(PopoverTrigger, { children: _jsx(Flex, { ref: triggerRef, as: "button", type: "button", "aria-label": ariaLabel, align: "center", justify: "center", boxSize: s.trigger, borderRadius: "lg", border: "1px solid", borderColor: isOpen ? theme.colors.boxborder[500] : theme.colors.boxborder[200], bg: isOpen ? theme.colors.backgroundColor.tertiary : "transparent", color: isOpen ? theme.colors.text[900] : theme.colors.textMuted, cursor: "pointer", transition: "background 0.15s ease, border-color 0.15s ease", _hover: { bg: rowHoverBg, color: theme.colors.text[900] }, onMouseEnter: handleHoverStart, onMouseLeave: handleHoverEnd, onClick: () => { openedByHover.current = false; cancelHoverTimer(); }, onKeyDown: (e) => {
189
+ return (_jsxs(Popover, { isOpen: isOpen, onOpen: () => setOpen(true), onClose: () => { setOpen(false); setQuery(""); }, placement: "bottom-end", initialFocusRef: showSearch ? searchRef : undefined, isLazy: true, children: [_jsx(PopoverTrigger, { children: _jsx(Flex, { ref: triggerRef, as: "button", type: "button", "aria-label": ariaLabel, align: "center", justify: "center", boxSize: s.trigger, borderRadius: "lg", border: "1px solid", borderColor: isOpen ? theme.colors.boxborder[500] : theme.colors.boxborder[200], bg: isOpen ? theme.colors.backgroundColor.tertiary : "transparent", color: isOpen ? theme.colors.text[900] : theme.colors.textMuted, cursor: "pointer", transition: "background 0.15s ease, border-color 0.15s ease", _hover: { bg: rowHoverBg, color: theme.colors.text[900] }, onKeyDown: (e) => {
233
190
  if (!isOpen) {
234
191
  if (e.key === "ArrowDown") {
235
192
  e.preventDefault();
@@ -238,7 +195,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
238
195
  return;
239
196
  }
240
197
  handlePanelKeyDown(e);
241
- }, children: _jsx(Grip, { size: s.icon + 1 }) }) }), _jsx(PopoverContent, { ref: panelRef, onMouseEnter: cancelHoverTimer, onMouseLeave: handleHoverEnd, w: width, maxW: "calc(100vw - 1.5rem)", textAlign: "start", onKeyDown: handlePanelKeyDown, bg: theme.colors.backgroundColor.light, borderRadius: "xl", overflow: "hidden", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}`, _focus: { outline: "none", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}` }, children: _jsxs(PopoverBody, { p: 0, children: [_jsxs(Flex, { align: "center", gap: 2, px: 3, pt: 3, pb: showSearch ? 2 : 1, children: [_jsx(Text, { fontSize: "0.6875rem", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: theme.colors.textMuted, children: HEADING }), _jsx(Box, { flex: "1" }), hint && (_jsx(Text, { as: "kbd", fontSize: "0.6875rem", fontFamily: "inherit", color: theme.colors.textMuted, border: "1px solid", borderRadius: "base", px: 1.5, py: 0.5, children: hint }))] }), showSearch && (_jsx(Box, { px: 3, pb: 2, children: _jsxs(InputGroup, { size: "sm", children: [_jsx(InputLeftElement, { pointerEvents: "none", children: _jsx(Search, { size: 15, color: theme.colors.textMuted }) }), _jsx(Input, { ref: searchRef, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps", borderRadius: "lg", "aria-label": "Search apps", onKeyDown: (e) => {
198
+ }, children: _jsx(Grip, { size: s.icon + 1 }) }) }), _jsx(PopoverContent, { w: width, maxW: "calc(100vw - 1.5rem)", textAlign: "start", onKeyDown: handlePanelKeyDown, bg: theme.colors.backgroundColor.light, borderRadius: "xl", overflow: "hidden", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}`, _focus: { outline: "none", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}` }, children: _jsxs(PopoverBody, { p: 0, children: [_jsxs(Flex, { align: "center", gap: 2, px: 3, pt: 3, pb: showSearch ? 2 : 1, children: [_jsx(Text, { fontSize: "0.6875rem", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: theme.colors.textMuted, children: HEADING }), _jsx(Box, { flex: "1" }), hint && (_jsx(Text, { as: "kbd", fontSize: "0.6875rem", fontFamily: "inherit", color: theme.colors.textMuted, border: "1px solid", borderRadius: "base", px: 1.5, py: 0.5, children: hint }))] }), showSearch && (_jsx(Box, { px: 3, pb: 2, children: _jsxs(InputGroup, { size: "sm", children: [_jsx(InputLeftElement, { pointerEvents: "none", children: _jsx(Search, { size: 15, color: theme.colors.textMuted }) }), _jsx(Input, { ref: searchRef, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps", borderRadius: "lg", "aria-label": "Search apps", onKeyDown: (e) => {
242
199
  if (e.key === "ArrowDown" && visible.length > 0) {
243
200
  e.preventDefault();
244
201
  focusItem(0);
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Flex, Text, } from "@chakra-ui/react";
3
3
  import { Info } from "lucide-react";
4
4
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
- // `id` lets a field point `aria-describedby` here so the message is part of the control's
6
- // description on every focus. `role="alert"` alone announces once, when it first renders.
5
+ // `id` lets a field point `aria-describedby` here; `role="alert"` announces once, on render.
6
+ // Always pass `errorMessage` when `error` is true — omitted, this renders the literal word "Error", and every caller hides `helperText` while `error` is true.
7
7
  const ErrorMessage = ({ errorMessage, id }) => {
8
8
  const { colors } = useCustomTheme();
9
9
  return (_jsxs(Flex, { id: id, align: "center", color: colors.semanticText.error, fontSize: "0.875rem", role: "alert", children: [_jsx(Info, { width: "0.875rem" }), _jsx(Text, { ml: "0.188rem", children: errorMessage !== null && errorMessage !== void 0 ? errorMessage : "Error" })] }));
@@ -1,19 +1,39 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useRef } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef, useState } from "react";
3
3
  import { Box, Flex } from "@chakra-ui/react";
4
4
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
+ import { resolveFilledSurface } from "../../Theme/tokens/builders/filledSurface.js";
5
6
  const SIZES = {
6
7
  sm: { h: "1.625rem", px: "0.625rem", fontSize: "0.75rem" },
7
8
  md: { h: "2rem", px: "0.875rem", fontSize: "0.8125rem" },
8
9
  };
9
- export default function SegmentedControl({ options, value, onChange, size = "md", isFullWidth = false, "aria-label": ariaLabel, }) {
10
+ export default function SegmentedControl({ options, value, onChange, size = "md", isFullWidth = false, variant = "default", "aria-label": ariaLabel, }) {
10
11
  const { colors } = useCustomTheme();
11
12
  const dims = SIZES[size];
12
13
  const refs = useRef([]);
14
+ const trackRef = useRef(null);
15
+ const isBrandSlide = variant === "brandSlide";
16
+ const isUnderlineSlide = variant === "underlineSlide";
17
+ const hasIndicator = isBrandSlide || isUnderlineSlide;
18
+ const brandFill = resolveFilledSurface(colors.primary, colors);
19
+ const [pill, setPill] = useState(null);
13
20
  const selectedIndex = options.findIndex((o) => o.value === value);
14
21
  const enabled = options.reduce((acc, o, i) => (o.isDisabled ? acc : [...acc, i]), []);
15
22
  // WAI-APG: with nothing selected, the first enabled segment owns the tab stop.
16
23
  const tabIndex = selectedIndex >= 0 ? selectedIndex : enabled[0];
24
+ useLayoutEffect(() => {
25
+ if (!hasIndicator)
26
+ return;
27
+ const track = trackRef.current;
28
+ const node = refs.current[selectedIndex];
29
+ if (!track || !node) {
30
+ setPill(null);
31
+ return;
32
+ }
33
+ const trackRect = track.getBoundingClientRect();
34
+ const nodeRect = node.getBoundingClientRect();
35
+ setPill({ left: nodeRect.left - trackRect.left, width: nodeRect.width });
36
+ }, [hasIndicator, selectedIndex, options, isFullWidth, size]);
17
37
  const selectAt = (index) => {
18
38
  var _a;
19
39
  (_a = refs.current[index]) === null || _a === void 0 ? void 0 : _a.focus();
@@ -43,13 +63,23 @@ export default function SegmentedControl({ options, value, onChange, size = "md"
43
63
  selectAt(enabled[enabled.length - 1]);
44
64
  }
45
65
  };
46
- return (_jsx(Flex, { role: "radiogroup", "aria-label": ariaLabel, onKeyDown: handleKeyDown, display: "inline-flex", align: "center", gap: "0.125rem", p: "0.125rem", borderRadius: "md", bg: colors.background[600], w: isFullWidth ? "100%" : "fit-content", children: options.map((option, index) => {
47
- const isSelected = option.value === value;
48
- return (_jsxs(Box, { as: "button", type: "button", ref: (node) => {
49
- refs.current[index] = node;
50
- }, role: "radio", "aria-checked": isSelected, disabled: option.isDisabled, tabIndex: index === tabIndex ? 0 : -1, onClick: () => onChange(option.value), display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "0.375rem", flex: isFullWidth ? "1" : undefined, h: dims.h, px: dims.px, borderRadius: "base", borderWidth: "1px", borderStyle: "solid", borderColor: isSelected ? colors.background[400] : "transparent", bg: isSelected ? colors.background[50] : "transparent", boxShadow: isSelected ? "sm" : "none", color: isSelected ? colors.text[900] : colors.text[600], fontSize: dims.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: "background-color 0.2s ease, color 0.2s ease", _hover: option.isDisabled || isSelected ? undefined : { color: colors.text[900] }, _focusVisible: {
51
- outline: "none",
52
- boxShadow: `0 0 0 2px ${colors.primary[300]}`,
53
- }, children: [option.icon, option.label] }, option.value));
54
- }) }));
66
+ return (_jsxs(Flex, { ref: trackRef, role: "radiogroup", "aria-label": ariaLabel, onKeyDown: handleKeyDown, position: "relative", display: "inline-flex", align: "center", gap: isUnderlineSlide ? "0" : "0.125rem", p: isUnderlineSlide ? "0" : "0.125rem", borderRadius: isUnderlineSlide ? "none" : "md", borderBottom: isUnderlineSlide ? `0.0625rem solid ${colors.boxborder[200]}` : undefined, bg: isBrandSlide ? colors.primary.opacity[8] : isUnderlineSlide ? "transparent" : colors.background[600], w: isFullWidth ? "100%" : "fit-content", children: [isBrandSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", top: "0.125rem", left: 0, h: dims.h, w: `${pill.width}px`, borderRadius: "base", bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), 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: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), options.map((option, index) => {
67
+ const isSelected = option.value === value;
68
+ return (_jsxs(Box, { as: "button", type: "button", ref: (node) => {
69
+ refs.current[index] = node;
70
+ }, role: "radio", "aria-checked": isSelected, disabled: option.isDisabled, tabIndex: index === tabIndex ? 0 : -1, onClick: () => onChange(option.value), position: "relative", zIndex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "0.375rem", flex: isFullWidth ? "1" : undefined, h: dims.h, px: dims.px, borderRadius: isUnderlineSlide ? "none" : "base", borderWidth: "1px", borderStyle: "solid", borderColor: hasIndicator
71
+ ? "transparent"
72
+ : isSelected
73
+ ? colors.primary.opacity[32]
74
+ : "transparent", bg: hasIndicator ? "transparent" : isSelected ? colors.backgroundColor.light : "transparent", boxShadow: hasIndicator ? "none" : isSelected ? "sm" : "none", color: isBrandSlide
75
+ ? isSelected
76
+ ? brandFill.color
77
+ : colors.text[600]
78
+ : isSelected
79
+ ? colors.accentText
80
+ : colors.text[600], fontSize: dims.fontSize, fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", opacity: option.isDisabled ? 0.5 : 1, cursor: option.isDisabled ? "not-allowed" : "pointer", transition: "color 0.2s ease", _hover: option.isDisabled || isSelected ? undefined : { color: colors.text[900] }, _focusVisible: {
81
+ outline: "none",
82
+ boxShadow: `0 0 0 2px ${colors.primary[300]}`,
83
+ }, children: [option.icon, option.label] }, option.value));
84
+ })] }));
55
85
  }
@@ -1,43 +1,93 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLayoutEffect, useRef, useState } from "react";
2
3
  import { Box, IconButton } from "@chakra-ui/react";
3
4
  import { Columns3, Table2 } from "lucide-react";
4
5
  import { useCustomTheme } from "../../Theme/useCustomTheme.js";
5
6
  import { AA_NON_TEXT, liftToContrast } from "../../Theme/tokens/builders/outlineRung.js";
7
+ import { resolveFilledSurface } from "../../Theme/tokens/builders/filledSurface.js";
6
8
  import ToolTip from "../ToolTip/ToolTip.js";
7
- const TableToggle = ({ value, onChange }) => {
9
+ const MODES = ["grid", "list"];
10
+ const TableToggle = ({ value, onChange, variant = "default" }) => {
8
11
  const theme = useCustomTheme();
9
12
  const { colors } = theme;
13
+ const isBrandSlide = variant === "brandSlide";
14
+ const isUnderlineSlide = variant === "underlineSlide";
15
+ const hasIndicator = isBrandSlide || isUnderlineSlide;
16
+ const brandFill = resolveFilledSurface(colors.primary, colors);
10
17
  const restInk = liftToContrast(colors.background[800], colors.background[600], AA_NON_TEXT);
11
18
  const hoverInk = liftToContrast(colors.background[900], colors.background[400], AA_NON_TEXT);
19
+ const trackRef = useRef(null);
20
+ const refs = useRef([]);
21
+ const [pill, setPill] = useState(null);
22
+ const selectedIndex = MODES.indexOf(value);
23
+ useLayoutEffect(() => {
24
+ if (!hasIndicator)
25
+ return;
26
+ const track = trackRef.current;
27
+ const node = refs.current[selectedIndex];
28
+ if (!track || !node) {
29
+ setPill(null);
30
+ return;
31
+ }
32
+ const trackRect = track.getBoundingClientRect();
33
+ const nodeRect = node.getBoundingClientRect();
34
+ setPill({ left: nodeRect.left - trackRect.left, width: nodeRect.width });
35
+ }, [hasIndicator, selectedIndex]);
12
36
  const styles = {
13
37
  container: {
14
38
  display: "flex",
15
39
  alignItems: "center",
16
- bg: colors.background[600],
17
- p: "0.125rem",
18
- borderRadius: "md",
40
+ bg: isBrandSlide ? colors.primary.opacity[8] : isUnderlineSlide ? "transparent" : colors.background[600],
41
+ p: isUnderlineSlide ? "0" : "0.125rem",
42
+ borderRadius: isUnderlineSlide ? "none" : "md",
43
+ borderBottom: isUnderlineSlide ? `0.0625rem solid ${colors.boxborder[200]}` : undefined,
19
44
  height: "2rem",
20
- gap: "0.125rem",
45
+ gap: isUnderlineSlide ? "0" : "0.125rem",
21
46
  width: "6.3rem",
47
+ position: "relative",
22
48
  },
23
49
  button: (mode) => ({
24
- bg: value === mode ? colors.background[50] : "transparent",
25
- boxShadow: value === mode ? "md" : "none",
26
- color: value === mode ? colors.text[900] : restInk,
27
- borderRadius: "base",
50
+ position: "relative",
51
+ zIndex: 1,
52
+ bg: hasIndicator ? "transparent" : value === mode ? colors.backgroundColor.light : "transparent",
53
+ boxShadow: hasIndicator ? "none" : value === mode ? "md" : "none",
54
+ color: isBrandSlide
55
+ ? value === mode
56
+ ? brandFill.color
57
+ : restInk
58
+ : value === mode
59
+ ? colors.accentText
60
+ : restInk,
61
+ borderRadius: isUnderlineSlide ? "none" : "base",
28
62
  height: "1.75rem",
29
- border: "0.031rem solid",
30
- borderColor: value === mode ? colors.background[400] : "transparent",
63
+ border: hasIndicator ? "none" : "0.031rem solid",
64
+ borderColor: hasIndicator
65
+ ? "transparent"
66
+ : value === mode
67
+ ? colors.primary.opacity[32]
68
+ : "transparent",
31
69
  padding: "0.313rem 0.813rem",
32
70
  _hover: {
33
- bg: value === mode
34
- ? colors.background[50]
35
- : colors.background[400],
36
- color: value === mode ? colors.text[900] : hoverInk,
71
+ bg: hasIndicator
72
+ ? "transparent"
73
+ : value === mode
74
+ ? colors.backgroundColor.light
75
+ : colors.background[400],
76
+ color: isBrandSlide
77
+ ? value === mode
78
+ ? brandFill.color
79
+ : hoverInk
80
+ : value === mode
81
+ ? colors.accentText
82
+ : hoverInk,
37
83
  },
38
- transition: "all 0.3s ease-in-out",
84
+ transition: "color 0.2s ease-in-out",
39
85
  }),
40
86
  };
41
- return (_jsxs(Box, { ...styles.container, children: [_jsx(ToolTip, { label: "Table View", placement: "top", fontSize: 12, hasArrow: true, children: _jsx(IconButton, { "aria-label": "Grid View", icon: _jsx(Table2, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("grid"), ...styles.button("grid") }) }), _jsx(ToolTip, { label: "Kanban View", placement: "top", fontSize: 12, hasArrow: true, children: _jsx(IconButton, { "aria-label": "List View", icon: _jsx(Columns3, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("list"), ...styles.button("list") }) })] }));
87
+ return (_jsxs(Box, { ref: trackRef, ...styles.container, children: [isBrandSlide && pill && (_jsx(Box, { "aria-hidden": "true", position: "absolute", top: "0.125rem", left: 0, h: "1.75rem", w: `${pill.width}px`, borderRadius: "base", bg: brandFill.fill, boxShadow: `0 0.125rem 0.5rem ${colors.boxShadow.primary}`, transform: `translateX(${pill.left}px)`, transition: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), 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: "transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.24s cubic-bezier(0.2, 0.8, 0.2, 1)" })), _jsx(ToolTip, { label: "Table View", placement: "top", fontSize: 12, hasArrow: true, children: _jsx(IconButton, { ref: (node) => {
88
+ refs.current[0] = node;
89
+ }, "aria-label": "Grid View", icon: _jsx(Table2, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("grid"), ...styles.button("grid") }) }), _jsx(ToolTip, { label: "Kanban View", placement: "top", fontSize: 12, hasArrow: true, children: _jsx(IconButton, { ref: (node) => {
90
+ refs.current[1] = node;
91
+ }, "aria-label": "List View", icon: _jsx(Columns3, { size: "1.25rem" }), variant: "ghost", onClick: () => onChange("list"), ...styles.button("list") }) })] }));
42
92
  };
43
93
  export default TableToggle;
@@ -11,5 +11,5 @@ export default function ToggleSwitch({ isChecked, onChange, isDisabled = false,
11
11
  return (_jsx(Box, { as: "button", type: "button", id: id, role: "switch", "aria-checked": isChecked, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, disabled: isDisabled, onClick: () => onChange(!isChecked), position: "relative", display: "inline-flex", alignItems: "center", flexShrink: 0, w: dims.w, h: dims.h, p: 0, borderRadius: "full", borderWidth: "1px", borderStyle: "solid", borderColor: isChecked ? colors.primary[500] : colors.boxborder[500], bg: isChecked ? colors.primary[500] : colors.background[600], opacity: isDisabled ? 0.5 : 1, cursor: isDisabled ? "not-allowed" : "pointer", transition: "background-color 0.2s ease, border-color 0.2s ease", _focusVisible: {
12
12
  outline: "none",
13
13
  boxShadow: `0 0 0 2px ${colors.primary[300]}`,
14
- }, children: _jsx(Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.background[50], boxShadow: "sm", transition: "transform 0.2s ease", transform: isChecked ? `translateX(${dims.travel})` : "translateX(0)" }) }));
14
+ }, children: _jsx(Box, { "aria-hidden": "true", position: "absolute", left: "0.125rem", w: dims.knob, h: dims.knob, borderRadius: "full", bg: colors.white, boxShadow: "sm", transition: "transform 0.2s ease", transform: isChecked ? `translateX(${dims.travel})` : "translateX(0)" }) }));
15
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixelize-design-library",
3
- "version": "2.4.2-beta.35",
3
+ "version": "2.4.2-beta.37",
4
4
  "description": "React component library for Pixelize apps: themeable Chakra-based components, design tokens and light/dark brand theming.",
5
5
  "keywords": [
6
6
  "react",
@@ -28,7 +28,8 @@
28
28
  "types": "dist/index.d.ts",
29
29
  "files": [
30
30
  "dist",
31
- "README.md"
31
+ "README.md",
32
+ "COMPONENT-GOTCHAS.md"
32
33
  ],
33
34
  "dependencies": {
34
35
  "@fontsource-variable/inter": "^5.2.8",
@@ -151,7 +152,6 @@
151
152
  "jest": "^29.7.0",
152
153
  "jest-axe": "^11.0.0",
153
154
  "jest-environment-jsdom": "^29.7.0",
154
- "path": "^0.12.7",
155
155
  "postcss": "^8.5.4",
156
156
  "prettier": "^3.3.2",
157
157
  "prop-types": "^15.8.1",