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.
- package/COMPONENT-GOTCHAS.md +135 -0
- package/README.md +5 -0
- package/dist/Components/AppSwitcher/AppSwitcher.js +2 -45
- package/dist/Components/Common/ErrorMessage.js +2 -2
- package/dist/Components/Drawer/DrawerProps.d.ts +1 -0
- package/dist/Components/InputTextArea/InputTextAreaProps.d.ts +1 -0
- package/dist/Components/Modal/ModalProps.d.ts +1 -0
- package/dist/Components/MultiSelect/MultiSelectProps.d.ts +1 -0
- package/dist/Components/ProfileCard/ProfileCardProps.d.ts +1 -0
- package/dist/Components/RadioButton/RadioButtonProps.d.ts +1 -0
- package/dist/Components/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/Components/SegmentedControl/SegmentedControl.js +40 -10
- package/dist/Components/SegmentedControl/SegmentedControlProps.d.ts +3 -0
- package/dist/Components/Select/SelectProps.d.ts +1 -0
- package/dist/Components/Table/TableProps.d.ts +1 -0
- package/dist/Components/Toggle/TableToggle.js +68 -18
- package/dist/Components/Toggle/TableToggleProps.d.ts +3 -0
- package/dist/Components/ToggleSwitch/ToggleSwitch.js +1 -1
- package/dist/esm/Components/AppSwitcher/AppSwitcher.js +2 -45
- package/dist/esm/Components/Common/ErrorMessage.js +2 -2
- package/dist/esm/Components/SegmentedControl/SegmentedControl.js +42 -12
- package/dist/esm/Components/Toggle/TableToggle.js +67 -17
- package/dist/esm/Components/ToggleSwitch/ToggleSwitch.js +1 -1
- package/package.json +3 -3
|
@@ -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] },
|
|
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, {
|
|
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
|
|
8
|
-
//
|
|
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
|
};
|
|
@@ -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.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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("
|
|
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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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:
|
|
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:
|
|
39
|
-
?
|
|
40
|
-
:
|
|
41
|
-
|
|
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: "
|
|
89
|
+
transition: "color 0.2s ease-in-out",
|
|
44
90
|
}),
|
|
45
91
|
};
|
|
46
|
-
return ((0, jsx_runtime_1.jsxs)(
|
|
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.
|
|
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] },
|
|
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, {
|
|
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
|
|
6
|
-
//
|
|
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 {
|
|
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 (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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:
|
|
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:
|
|
34
|
-
?
|
|
35
|
-
:
|
|
36
|
-
|
|
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: "
|
|
84
|
+
transition: "color 0.2s ease-in-out",
|
|
39
85
|
}),
|
|
40
86
|
};
|
|
41
|
-
return (_jsxs(Box, { ...styles.container, children: [_jsx(
|
|
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.
|
|
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.
|
|
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",
|