pixelize-design-library 2.4.2-beta.64 → 2.4.2-beta.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -8
- package/COMPONENT-GOTCHAS.md +37 -4
- package/DESIGN-GUIDE.md +7 -3
- package/dist/Components/CopyButton/CopyButton.d.ts +3 -1
- package/dist/Components/CopyButton/CopyButton.js +2 -2
- package/dist/Components/Drawer/Drawer.d.ts +1 -1
- package/dist/Components/Drawer/Drawer.js +6 -2
- package/dist/Components/Drawer/DrawerProps.d.ts +4 -0
- package/dist/Components/FormDrawer/FormDrawer.d.ts +1 -1
- package/dist/Components/FormDrawer/FormDrawer.js +3 -3
- package/dist/Components/FormDrawer/FormDrawerProps.d.ts +2 -0
- package/dist/Components/Modal/dialogNaming.d.ts +8 -0
- package/dist/Components/Modal/dialogNaming.js +21 -0
- package/dist/Components/NavLauncher/LauncherTabs.js +2 -2
- package/dist/Components/NavLauncher/NavLauncher.d.ts +1 -1
- package/dist/Components/NavLauncher/NavLauncher.js +257 -47
- package/dist/Components/NavLauncher/NavLauncherProps.d.ts +79 -3
- package/dist/Components/NavLauncher/ScopeBar.d.ts +14 -0
- package/dist/Components/NavLauncher/ScopeBar.js +137 -0
- package/dist/Components/NavLauncher/engine/distance.d.ts +4 -0
- package/dist/Components/NavLauncher/engine/distance.js +41 -0
- package/dist/Components/NavLauncher/engine/engine.d.ts +84 -0
- package/dist/Components/NavLauncher/engine/engine.js +471 -0
- package/dist/Components/NavLauncher/engine/fold.d.ts +15 -0
- package/dist/Components/NavLauncher/engine/fold.js +83 -0
- package/dist/Components/NavLauncher/engine/index.d.ts +21 -0
- package/dist/Components/NavLauncher/engine/index.js +17 -0
- package/dist/Components/NavLauncher/launcherParts.d.ts +2 -0
- package/dist/Components/NavLauncher/launcherParts.js +11 -7
- package/dist/Components/NavLauncher/match.d.ts +13 -2
- package/dist/Components/NavLauncher/match.js +26 -12
- package/dist/Components/NavLauncher/searchLayout.d.ts +60 -0
- package/dist/Components/NavLauncher/searchLayout.js +108 -0
- package/dist/Components/NavLauncher/spotlight.d.ts +39 -0
- package/dist/Components/NavLauncher/spotlight.js +202 -0
- package/dist/Components/NavLauncher/spotlightParts.d.ts +49 -0
- package/dist/Components/NavLauncher/spotlightParts.js +108 -0
- package/dist/Components/Table/components/TableHeader.js +3 -1
- package/dist/Components/Table/components/TableLoading.js +8 -2
- package/dist/Components/WizardDialog/WizardDialog.d.ts +1 -1
- package/dist/Components/WizardDialog/WizardDialog.js +15 -3
- package/dist/Components/WizardDialog/WizardDialogProps.d.ts +5 -3
- package/dist/Components/WorkspaceWindow/WorkspaceWindow.js +3 -2
- package/dist/Components/WorkspaceWindow/WorkspaceWindowProps.d.ts +2 -8
- package/dist/Theme/chakra/ringShadow.d.ts +3 -0
- package/dist/Theme/chakra/ringShadow.js +7 -0
- package/dist/Theme/tokens/builders/buildDarkPalette.d.ts +1 -1
- package/dist/Theme/tokens/builders/buildDarkPalette.js +5 -5
- package/dist/esm/Components/CopyButton/CopyButton.js +2 -2
- package/dist/esm/Components/Drawer/Drawer.js +6 -2
- package/dist/esm/Components/FormDrawer/FormDrawer.js +3 -3
- package/dist/esm/Components/Modal/dialogNaming.js +18 -0
- package/dist/esm/Components/NavLauncher/LauncherTabs.js +2 -2
- package/dist/esm/Components/NavLauncher/NavLauncher.js +259 -49
- package/dist/esm/Components/NavLauncher/ScopeBar.js +134 -0
- package/dist/esm/Components/NavLauncher/engine/distance.js +37 -0
- package/dist/esm/Components/NavLauncher/engine/engine.js +458 -0
- package/dist/esm/Components/NavLauncher/engine/fold.js +74 -0
- package/dist/esm/Components/NavLauncher/engine/index.js +14 -0
- package/dist/esm/Components/NavLauncher/launcherParts.js +10 -6
- package/dist/esm/Components/NavLauncher/match.js +25 -12
- package/dist/esm/Components/NavLauncher/searchLayout.js +100 -0
- package/dist/esm/Components/NavLauncher/spotlight.js +194 -0
- package/dist/esm/Components/NavLauncher/spotlightParts.js +95 -0
- package/dist/esm/Components/Table/components/TableHeader.js +4 -2
- package/dist/esm/Components/Table/components/TableLoading.js +6 -3
- package/dist/esm/Components/WizardDialog/WizardDialog.js +15 -3
- package/dist/esm/Components/WorkspaceWindow/WorkspaceWindow.js +4 -3
- package/dist/esm/Theme/chakra/ringShadow.js +3 -0
- package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +5 -5
- package/dist/esm/index.js +2 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -24,6 +24,19 @@ next major.
|
|
|
24
24
|
`closeLabel(item)`, `dirtyLabel`, `label`; `WorkspaceTrayItem` gains `isDirty` (unsaved dot) and `subtitle`.
|
|
25
25
|
`WorkspaceWindow` gains `labels` ({minimize, close, openFullPage}) and `finalFocusRef`.
|
|
26
26
|
The tray now renders in a portal, so a test that queries inside its render container must query `document.body`.
|
|
27
|
+
- **`WorkspaceTray` `renderItem` receives the built-in pill** as a third argument, `renderItem(item, api, pill)`: the pill
|
|
28
|
+
already carries the unsaved dot, subtitle and close button, so a consumer can return it or wrap it (in
|
|
29
|
+
`WorkspaceTrayPreview` for a hover preview, say) instead of rebuilding it. Two-argument callbacks and the default
|
|
30
|
+
pill are unchanged.
|
|
31
|
+
- **`FormDrawer` `isSubmitDisabled`**: disables the primary button and makes the form ignore Enter and any other submit,
|
|
32
|
+
so `onSubmit` never fires, not even from `requestSubmit()` or a dispatched submit event (the drawer is a real
|
|
33
|
+
`<form>`; the button alone does not guard those). Unlike `isSubmitting` it shows no spinner, and Cancel and every
|
|
34
|
+
close path stay live. Omitted, nothing changes.
|
|
35
|
+
- **`WizardDialog` `chrome="inline"`** (default `"modal"`): draws the wizard (title, stepper, step, review, footer) in place
|
|
36
|
+
with no Modal, for a host that is already the dialog, such as a `WorkspaceWindow`: no overlay, focus trap, close X,
|
|
37
|
+
Esc-to-close or `role="dialog"`. It renders nothing while `isOpen` is false, ignores `size`, and Cancel still calls
|
|
38
|
+
`onClose`. It reuses the same flow as the modal (`useWizardFlow`, `validate`, `finishDisabled`, `finishBlockedReason`,
|
|
39
|
+
`stepId`); test ids are `wizard-inline` / `${data-testid}-inline`. `Wizard` does not take the prop.
|
|
27
40
|
- **`optionRowFill(rows, isActive)`** and **`optionRowCursorSx(rows)`** are exported from the package root: the one
|
|
28
41
|
fill an option row paints (the cursor's), and its `:hover`/`:focus` form for roving-focus lists.
|
|
29
42
|
- **`accentTextOnPlanes(scale, planes)`** is exported from the package root: the first accent rung clearing AA on every
|
|
@@ -95,6 +108,53 @@ next major.
|
|
|
95
108
|
document-wide `prefers-reduced-motion` rule; `pz-motion-keep` (`MOTION_KEEP_CLASS`) opts an element out.
|
|
96
109
|
The default is unchanged. `CustomThemeProps` gains `zIndices.popoverNested` and the two font sizes, so
|
|
97
110
|
code that hand-builds a full `CustomThemeProps` needs those keys.
|
|
111
|
+
- **`LauncherSearchEngine.matches` and `.createMatcher`**: the boolean form of the engine for table and list filters.
|
|
112
|
+
`matches(query, ...texts)` is true when every query word hits some text (same folding, typo tolerance and `synonyms`
|
|
113
|
+
option as `search`, no ranking); `createMatcher(query)` compiles the query once for a loop over rows, and an empty query
|
|
114
|
+
matches every row. `LauncherMatcher` is exported as a type.
|
|
115
|
+
- **`LauncherSearchEngine` searches Hindi and Tamil by written letter**: Latin accents, the Devanagari nukta and
|
|
116
|
+
zero-width joiners are ignored, chandrabindu folds into anusvara, and every other combining mark stays part of its
|
|
117
|
+
letter, so a bare vowel sign never matches ("का" does not find "कमाई"). Typo length and distance count letters
|
|
118
|
+
("कम्मचारी" finds "कर्मचारी"), and a Tamil word ending in ம் also finds titles that drop it ("நேரம்" finds "நேர அட்டவணை").
|
|
119
|
+
`editDistance` accepts arrays of letters. Latin results are unchanged. `engine/conformance.fixture.json` is the
|
|
120
|
+
behaviour contract shared with HRMS mobile search, asserted by `engine/conformance.test.ts`.
|
|
121
|
+
- **`NavLauncher` spotlight search** (all additive; an app passing none of these renders as before):
|
|
122
|
+
- `searchMode="spotlight"` ranks every row by relevance (exact > prefix > word > word-prefix >
|
|
123
|
+
initialism > substring > typo > subsequence, label > keyword > context), tolerates typos
|
|
124
|
+
(Damerau-Levenshtein 1 from 4 letters, 2 from 8), folds case and accents, expands `synonyms`
|
|
125
|
+
both ways, boosts `pinnedIds` / `frequentIds` / `recentIds`, and shows the best row first as a
|
|
126
|
+
**Top hit** card naming the keyword or fact that matched. Groups collapse to `groupLimit` rows
|
|
127
|
+
(default 5 in spotlight, unlimited in classic) behind a "Show N more" option.
|
|
128
|
+
- `scopes` + `activeScope` / `onScopeChange`: filter chips in the header row beside the field
|
|
129
|
+
(their own row below `md`), always shown so they say what can be searched; while typing each
|
|
130
|
+
chip shows its live match count and is named "People, 3 results". One Tab stop (radiogroup,
|
|
131
|
+
roving tabindex, arrows / Home / End); the strip scrolls sideways behind an edge fade with a
|
|
132
|
+
mouse page arrow and keeps the selected chip in view, so the field never drops below 15rem (or a
|
|
133
|
+
third of a wide row). "All" is prepended, `screens`
|
|
134
|
+
and `actions` are built in and `LauncherResultGroup.scope` (default its `id`) places any other
|
|
135
|
+
group. A chip other than All / Screens picked with an empty field lists that scope's items as
|
|
136
|
+
passed, with the facts pane; `LauncherScope.placeholder` narrows the field's placeholder.
|
|
137
|
+
- `quickActions`: up to 5 pill buttons at the top of the menu while the query is empty and the
|
|
138
|
+
scope is All.
|
|
139
|
+
- `LauncherSearchItem.facts` (`{ label, value, copyable? }`): a right preview pane at `lg`+ for the
|
|
140
|
+
highlighted row with Copy buttons (blocked clipboard selects the value instead), docked under the
|
|
141
|
+
list below `lg`. `answers` (items with `answer: { label, value, hint? }`) render as direct-answer
|
|
142
|
+
cards above every result; Enter copies an answer that has no `run` / `href`.
|
|
143
|
+
- `LauncherResultGroup.matchLocally`: the launcher filters and ranks a static group itself.
|
|
144
|
+
- `LauncherSearchEngine` — the framework-free engine behind it (`createIndex`, `search`,
|
|
145
|
+
`searchDocuments`, `highlightRanges`, `usageBoosts`, `foldText`, `tokenize`, `editDistance`; options
|
|
146
|
+
`synonyms`, `boosts`, `fuzzy`, `relativeCutoff`, `limit`) for any other list or palette; `highlightSegments` now exported, with an opt-in `{ fuzzy, synonyms }`.
|
|
147
|
+
- New optional `LauncherLabels` keys: `topHit`, `matchedOn`, `answers`, `hintCopy`, `scopes`,
|
|
148
|
+
`scopeAll`, `scopeChanged`, `scopeCount`, `scopeEmpty`, `quickActions`, `showMore`, `preview`,
|
|
149
|
+
`copy`, `copied`, `copySelected`.
|
|
150
|
+
- The dialog is now always named `labels.title`; the search value and `headerActions` no longer
|
|
151
|
+
join its accessible name.
|
|
152
|
+
- Types `LauncherFact`, `LauncherAnswer`, `LauncherScope`, `LauncherSearchMode`, `HighlightOptions`,
|
|
153
|
+
`LauncherSearch*` / `LauncherSynonyms` / `LauncherMatchKind` / `LauncherTokenMatch`.
|
|
154
|
+
- `CopyButton` `onCopyError` — fires when the clipboard is missing or denied, instead of failing silently.
|
|
155
|
+
- `Drawer` `aria-label` / `aria-labelledby` — name the dialog instead of its whole header (a header
|
|
156
|
+
holding a field or buttons otherwise becomes the name); `contentProps["aria-labelledby"]` never
|
|
157
|
+
worked.
|
|
98
158
|
- **HRMS library requests** — a navigation launcher, key caps, a merge-field `Editor`, a wizard and
|
|
99
159
|
smaller gaps found by the HRMS migration:
|
|
100
160
|
- `NavLauncher` — full-height navigation launcher (browse modules, pins, Frequent, Recent, search
|
|
@@ -260,8 +320,9 @@ next major.
|
|
|
260
320
|
library; bring your own.
|
|
261
321
|
|
|
262
322
|
- **Dark mode.** `PixelizeThemeProvider` (defaults to `system`), `useThemeMode`, `ThemeSwitcher`.
|
|
263
|
-
Dark palettes are derived per brand from that brand's own light palette
|
|
264
|
-
|
|
323
|
+
Dark palettes are derived per brand from that brand's own light palette: surfaces are hue-neutral,
|
|
324
|
+
and the brand hue carries through on the accent, ink, rules and rail. `withTheme` stays light-only,
|
|
325
|
+
so existing apps are visually unchanged — dark is opt-in.
|
|
265
326
|
- **`MediaViewer`** — a full-screen viewer for images, video, audio, PDF, Office documents, text
|
|
266
327
|
and archives, with a filmstrip, keyboard navigation and per-kind fallbacks. `index` is optional:
|
|
267
328
|
omit it and the viewer owns navigation itself.
|
|
@@ -393,6 +454,14 @@ next major.
|
|
|
393
454
|
|
|
394
455
|
### Changed (visual)
|
|
395
456
|
|
|
457
|
+
- **Dark surfaces are hue-neutral on every brand.** The canvas, card, overlay, named `backgroundColor` ladder,
|
|
458
|
+
`background[50-900]` and `table.hover` no longer take a brand tint (`SURFACE_SAT` 14 to 0): all 9 brands share
|
|
459
|
+
canvas `#121212`, card `#212121` (1.163:1 over the canvas) and overlay `#2c2c2c` (1.153:1 over the card), so
|
|
460
|
+
switching brand no longer recolours the app background. The brand hue stays on the accent, ink, rules and the
|
|
461
|
+
sidebar rail. Light palettes are byte-identical to before (SHA-256 of all 9 resolved light palettes). Muted dark
|
|
462
|
+
text re-derives against the neutral surfaces and shifts a few hex values on lavender, rosewood, slate and zinc
|
|
463
|
+
(lavender `#9f92aa` to `#a498ae`); every muted token still passes the same AA gate. `planes: "flat"` keeps its
|
|
464
|
+
meaning (card = canvas) on the same canvas.
|
|
396
465
|
- **`Table` loading state.** With `isLoading` and no rows, each row now draws one skeleton per column (a checkbox
|
|
397
466
|
placeholder in the checkbox column, blank spacer cells) with the variant's row rule, instead of one bar spanning the
|
|
398
467
|
table. If `columns` is still empty while loading, the header shows four placeholder cells and a disabled select-all
|
|
@@ -461,11 +530,10 @@ next major.
|
|
|
461
530
|
2px on hover; the shadow still deepens.
|
|
462
531
|
- **`Button` (solid, outline, ghost) and `IconButton` scale to 0.98 while pressed** (`MOTION_PRESS_SCALE`).
|
|
463
532
|
Never when disabled or loading, nor under `prefers-reduced-motion`.
|
|
464
|
-
- **Dark planes
|
|
465
|
-
(`
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
lighter so they clear AA on selected list rows.
|
|
533
|
+
- **Dark planes separate by lift.** Cards (`background[50]`) sit one step (1.16:1) above the canvas, with
|
|
534
|
+
overlays (`surfaceRaised`) one step (1.15:1) above the card; every filled mark keeps 3:1 on all three. Light
|
|
535
|
+
mode is unchanged. A brand opts out of the lift with `buildDarkPalette(light, { planes: "flat" })`. `meadow`
|
|
536
|
+
and `rosewood` dark `accentText` move one rung lighter so they clear AA on selected list rows.
|
|
469
537
|
- **`zinc`:** muted text in light mode is `#515157` (was `#27272a`, 14.9:1), settled under a new 8:1 muted
|
|
470
538
|
ceiling; zinc's dark card is no longer darker than its canvas.
|
|
471
539
|
- **A brand's primary never reads as danger.** `radiant`'s danger colour (`red` and `semantic.error`) is
|
|
@@ -538,6 +606,8 @@ next major.
|
|
|
538
606
|
- **`FileUpload` no longer leaks a blob URL per image thumbnail per render.** Each file gets one URL,
|
|
539
607
|
revoked when the file is removed or the component unmounts.
|
|
540
608
|
- **`Table` column search shows the current search when reopened**, instead of stale text.
|
|
609
|
+
- **`NavLauncher` search rows shorten the `· keyword` hint before the label**, so a long keyword list
|
|
610
|
+
no longer cuts "Apply for leave" down to "Apply…".
|
|
541
611
|
- **`NavigationBar` right cluster no longer overflows onto the avatar at phone widths.** The title
|
|
542
612
|
truncates first (overflow-only tooltip when it is a string); the `moreIcon` cluster can shrink, while
|
|
543
613
|
the sidebar toggle, back button and avatar menu never do. (HRMS, beta.60 launcher trigger overflow)
|
|
@@ -791,7 +861,12 @@ Nothing is removed yet — each of these still ships its full implementation.
|
|
|
791
861
|
- `CustomModulesTableLabels.loading` — never rendered; the loading state shows skeleton bars only.
|
|
792
862
|
- `WorkspaceWindow` `onMedium` and `onMaximize` — never called; the window has no such controls.
|
|
793
863
|
- `NavLauncher` `openShortcutHint` and `LauncherLabels.hintOpenMenu` — no longer rendered (the launcher
|
|
794
|
-
is already open when its footer shows); bind the open chord in the app only.
|
|
864
|
+
is already open when its footer shows); bind the open chord in the app only. `hintOpenMenu` is now
|
|
865
|
+
optional, so a full `LauncherLabels` object type-checks without it.
|
|
866
|
+
- `searchLauncher` — legacy flat search (one Screens group); `NavLauncher` never calls it. Classic mode
|
|
867
|
+
runs `searchLauncherSections`, which matches a screen on its own label and keywords only ("leave
|
|
868
|
+
balance" finds nothing unless one screen's keywords carry both words); `searchMode="spotlight"`
|
|
869
|
+
ranks and also matches a screen on its module and section.
|
|
795
870
|
- `SelectV2` — use `Select` with `native={false}`; it is already a thin wrapper over it.
|
|
796
871
|
- `SearchSelect`, `SelectSearch` are **no longer `@deprecated`.** Both now say "prefer `Select`
|
|
797
872
|
for new work" instead: `@deprecated` claimed a migration that loses real capabilities
|
package/COMPONENT-GOTCHAS.md
CHANGED
|
@@ -586,7 +586,8 @@ It sets `noValidate`: validate in `onSubmit` and pass messages via `errors`. Err
|
|
|
586
586
|
fires only after a submit; if validation is async, set `isSubmitting` synchronously in `onSubmit` so
|
|
587
587
|
focus waits for the result, otherwise errors that arrive later are not focused. The footer submit
|
|
588
588
|
button is tied to the form with the `form` attribute — Enter-to-submit works in browsers, but
|
|
589
|
-
jsdom / user-event
|
|
589
|
+
jsdom / user-event only simulates it for a form with exactly one text field, so use
|
|
590
|
+
`fireEvent.submit(form)` in tests. `isSubmitDisabled` swallows both: `onSubmit` is not called while it is true.
|
|
590
591
|
|
|
591
592
|
### `QueryStateBoundary` evaluates JSX `children` even while loading
|
|
592
593
|
|
|
@@ -1292,7 +1293,9 @@ remount, wrap `<Editor>` itself in a keyed parent, not via `Editor`'s own `key`
|
|
|
1292
1293
|
|
|
1293
1294
|
`validate` runs on Continue, on Finish, and again for every step on Finish. Passing `stepId` controls
|
|
1294
1295
|
the visible step only: stages past the furthest valid step stay disabled. Esc, close, Back and Cancel
|
|
1295
|
-
are disabled while `isFinishing`.
|
|
1296
|
+
are disabled while `isFinishing`. With `chrome="inline"` there is no Modal at all: no focus trap, no Esc,
|
|
1297
|
+
no close X and no `role="dialog"` (the host window supplies them), `size` is ignored, and nothing renders
|
|
1298
|
+
while `isOpen` is false; Cancel still calls `onClose`, so the host must close itself there.
|
|
1296
1299
|
|
|
1297
1300
|
### `PageHeader` actions wrap by default
|
|
1298
1301
|
|
|
@@ -1413,8 +1416,9 @@ the `Search` listeners see it — otherwise the first mounted `Search` takes Ctr
|
|
|
1413
1416
|
- **Esc is handled by the launcher, not the Drawer** (`closeOnEsc` is off): it clears a non-empty
|
|
1414
1417
|
query and keeps focus in the field, and closes only when the query is empty. The keydown is stopped inside
|
|
1415
1418
|
the launcher, so a consumer `Escape` listener on `document` never sees it.
|
|
1416
|
-
-
|
|
1417
|
-
|
|
1419
|
+
- **The dialog's accessible name is always `labels.title`** (a hidden heading, through `Drawer`'s
|
|
1420
|
+
`aria-labelledby`); the search value, scope chips and `headerActions` no longer join it. A consumer
|
|
1421
|
+
test asserting the old "Menu leave" style name must assert `labels.title` instead.
|
|
1418
1422
|
- **The result count is `aria-hidden`; the announcement is a separate visually hidden `aria-live="polite"`
|
|
1419
1423
|
node, deliberately not `role="status"`**, so `getByRole("status")` still matches only the no-results
|
|
1420
1424
|
message and the loading skeleton.
|
|
@@ -1429,6 +1433,7 @@ the `Search` listeners see it — otherwise the first mounted `Search` takes Ctr
|
|
|
1429
1433
|
a consumer that annotates a *full* `LauncherLabels` object fails to compile.
|
|
1430
1434
|
- **`openShortcutHint` and `labels.hintOpenMenu` are deprecated no-ops:** the open chord is no longer
|
|
1431
1435
|
shown in the footer. Drop them; the footer's hints are arrows, ENTER, ⌘ ENTER, ⌘ + click, P, ESC.
|
|
1436
|
+
Both are optional, so a full `LauncherLabels` object needs no `Omit<LauncherLabels, "hintOpenMenu">`.
|
|
1432
1437
|
- **`apps` is data-driven.** Gate by plan by changing the array or setting `locked` / `lockedReason`;
|
|
1433
1438
|
keep `id`s stable (hues and `key` derive from them), and pass `color` on an app whose mark must not
|
|
1434
1439
|
shift when siblings are added or removed. Locked rows are not links, not focusable, `aria-disabled`,
|
|
@@ -1441,6 +1446,34 @@ the `Search` listeners see it — otherwise the first mounted `Search` takes Ctr
|
|
|
1441
1446
|
stylesheet.
|
|
1442
1447
|
- **A search row without `href` (an action with `run`) cannot open in a new tab:** any click, with or
|
|
1443
1448
|
without Cmd/Ctrl, runs it and closes the launcher, same as Enter.
|
|
1449
|
+
- **Spotlight search is opt-in (`searchMode="spotlight"`).** Without it matching, ranking and row
|
|
1450
|
+
order are exactly as before; `scopes`, `quickActions`, `answers`, `facts` and `groupLimit` work in
|
|
1451
|
+
both modes. Facts are searchable in spotlight only.
|
|
1452
|
+
- **Two search models, and classic never matches a screen on its module.** Classic (the default) runs
|
|
1453
|
+
`searchLauncherSections`: registry order, no ranking, and every query word must appear in one row's
|
|
1454
|
+
own label or keywords. "leave balance" therefore finds nothing unless one screen's keywords carry
|
|
1455
|
+
both words. Spotlight ranks and also matches a screen on its module and section labels, so the same
|
|
1456
|
+
query finds Leave › Balances. The exported `searchLauncher` (one flat Screens group) is legacy and
|
|
1457
|
+
`@deprecated`; the component never calls it.
|
|
1458
|
+
- **Scope chips live in the header row and are always shown** (their own header row below `md`);
|
|
1459
|
+
while a query is active each chip's name carries its count ("People, 3 results"), so a test that
|
|
1460
|
+
types first must match `getByRole("radio", { name: /^People/ })`, not the bare label.
|
|
1461
|
+
- **Picking a chip other than All or Screens with an empty field lists that scope** exactly as
|
|
1462
|
+
passed: its `actions` or `extraResults` groups, so a remote group with nothing loaded yet shows
|
|
1463
|
+
`labels.scopeEmpty`. Load the scope's items when `onScopeChange` fires if it should list them.
|
|
1464
|
+
- **Quick actions show only under All with an empty query, as the first block of the menu** (not
|
|
1465
|
+
while `isLoading`); only the first 5 render.
|
|
1466
|
+
- **Closing resets the scope to "all"** and calls `onScopeChange("all")`; a controlled `activeScope`
|
|
1467
|
+
must apply it, like `onQueryChange("")`.
|
|
1468
|
+
- **`facts` swap the Quick access rail for a Preview pane at `lg`** whenever any visible result has
|
|
1469
|
+
facts; below `lg` the active result's facts dock under the list. Copy buttons are never inside an
|
|
1470
|
+
`option` or the listbox (axe `nested-interactive`, `aria-required-children`).
|
|
1471
|
+
- **An answer without `run` or `href` copies `answer.value` on Enter/click**; answers show in the
|
|
1472
|
+
All scope only.
|
|
1473
|
+
- **`extraResults` groups are not filtered** unless `matchLocally: true`; remote rows the query does not
|
|
1474
|
+
match stay listed (score 0) and can never be the Top hit.
|
|
1475
|
+
- **`LauncherSearchEngine` searches what it is given.** It has no React, DOM or network; build the
|
|
1476
|
+
index with `createIndex` once per data change, not per keystroke.
|
|
1444
1477
|
- **`LauncherTrigger` does not bind its shortcut**; `shortcut={null}` removes the hint and
|
|
1445
1478
|
`aria-keyshortcuts`. An explicit `aria-label` hides the visible label from assistive tech, so keep
|
|
1446
1479
|
the visible text inside the label (WCAG 2.5.3). `display="icon"` has no visible text: the label is
|
package/DESIGN-GUIDE.md
CHANGED
|
@@ -152,7 +152,7 @@ sections are `Card` with the title in `header`.
|
|
|
152
152
|
|
|
153
153
|
| Pattern | Component — don't hand-build it |
|
|
154
154
|
| --- | --- |
|
|
155
|
-
| Multi-step flow (send, import, onboard) with validated Continue and a review step | `WizardDialog` (`Wizard` inline) — don't hand-roll Back / Continue / Finish around `StageProgress`; each step's `validate` returns its error messages, future steps stay locked until valid, each step's `summary` drives the review |
|
|
155
|
+
| Multi-step flow (send, import, onboard) with validated Continue and a review step | `WizardDialog` (`Wizard` inline; `chrome="inline"` inside a `WorkspaceWindow` or other host that is already the dialog) — don't hand-roll Back / Continue / Finish around `StageProgress`; each step's `validate` returns its error messages, future steps stay locked until valid, each step's `summary` drives the review |
|
|
156
156
|
| Rich-text template with `${field}` placeholders | `Editor mergeFields={…}` — not a `Select` + caret tracking + dialog beside it; use its `label` / `helperText` / `errorMessage` |
|
|
157
157
|
| A PDF behind auth, shown inline | `PdfViewer getBlob={…}` — the toolbar's Open / Download then work with the token, so don't add your own buttons |
|
|
158
158
|
| Toolbar: search · filters · active-filter chips · count · actions | `FilterBar` (`search`, children, `activeFilters` + `onRemoveFilter`/`onClearAll`, `summary`, `actions`) |
|
|
@@ -203,6 +203,10 @@ scrim `Box`. `Loading` is the older API and stays on the heavier house scrim by
|
|
|
203
203
|
`headerActions` for the app's own icon button (help, customise) with an `aria-label`.
|
|
204
204
|
- Show the suite's apps with `appsVisibility="always"` and drive plan gating by editing `apps` /
|
|
205
205
|
`locked`.
|
|
206
|
+
- For Spotlight-style search pass `searchMode="spotlight"` plus `synonyms` for the app's jargon
|
|
207
|
+
(`pf`, `wfh`), `scopes` for its entity groups, up to 5 `quickActions`, copyable `facts` on reference
|
|
208
|
+
items and `answers` for one-line lookups. Reuse `LauncherSearchEngine` for any other in-app search
|
|
209
|
+
instead of writing a matcher.
|
|
206
210
|
- Show shortcut hints with `KbdShortcut`, never a hand-styled `<kbd>`.
|
|
207
211
|
|
|
208
212
|
## 7. Every page has four states
|
|
@@ -260,8 +264,8 @@ not shipped in this package). Start from the closest one:
|
|
|
260
264
|
| Settings / long form | `settings.tsx` | `SectionRail` (rail from `lg`, `Select` below, dirty dots), `SettingsSection` + `SettingRow`, `SaveBar`, danger zone |
|
|
261
265
|
| Board | `pipeline.tsx` | `KanbanBoard` with custom cards and column totals (`height`/`maxH` to size it) |
|
|
262
266
|
| Inbox / master–detail | `inbox.tsx` | selectable list rows, conversation pane, composer, mobile list↔detail |
|
|
263
|
-
| App launcher (browse, pins, recents, search with actions) | `launcher.tsx` (`/examples/launcher`) | `NavLauncher` + `LauncherTrigger` in `NavigationBar leading`, app-owned registry and permission filter, pinned / frequent / recent ids, `linkAs`, remote results via `extraResults`, `isLoading`, `SideBar pinnedItems
|
|
264
|
-
| Wizard / multi-step dialog | `wizard.tsx` (`/examples/wizard`) | `WizardDialog` and inline `
|
|
267
|
+
| App launcher (browse, pins, recents, search with actions) | `launcher.tsx` (`/examples/launcher`) | `NavLauncher` + `LauncherTrigger` in `NavigationBar leading`, app-owned registry and permission filter, pinned / frequent / recent ids, `linkAs`, remote results via `extraResults`, `isLoading`, `SideBar pinnedItems`, Classic / Spotlight toggle, synonyms, scopes, quick actions, company facts with Copy, answers |
|
|
268
|
+
| Wizard / multi-step dialog | `wizard.tsx` (`/examples/wizard`) | `WizardDialog`, inline `Wizard` and `WizardDialog chrome="inline"` in a `WorkspaceWindow`: per-step `validate` messages, locked future steps, error summary, `summary`-driven review, `isFinishing` |
|
|
265
269
|
| Template editor with merge fields | `templateEditor.tsx` (`/examples/template-editor`) | `Editor mergeFields`: grouped Insert field, chips, unknown-field error chips and `onUnknownTokens`, Make dynamic, side preview, label / helper / error |
|
|
266
270
|
| Shop front (home → listing → product → bag → checkout → order) | `storefront/` | `ProductCard` grid / rail / quick-add, `ProductDetails` + `StickyBuyBar`, `CartItem`, `OrderSummary`, `CouponField`, address/payment groups, `OrderStatusTimeline`, `BagBar`; mobile frame + desktop |
|
|
267
271
|
|
|
@@ -16,8 +16,10 @@ export interface CopyButtonProps {
|
|
|
16
16
|
iconSize?: number;
|
|
17
17
|
/** Only affects the default "icon" variant's IconButton — "button" and "menu-item" ignore it. */
|
|
18
18
|
size?: "xs" | "sm" | "md";
|
|
19
|
-
/** Fires only if the clipboard write resolves
|
|
19
|
+
/** Fires only if the clipboard write resolves. */
|
|
20
20
|
onCopy?: (value: string) => void;
|
|
21
|
+
/** Fires when the clipboard is missing or denied, instead of failing silently. */
|
|
22
|
+
onCopyError?: (value: string) => void;
|
|
21
23
|
}
|
|
22
24
|
/** Copies `value` to the clipboard in 3 layouts (icon/button/menu-item) — not a full share/export action. */
|
|
23
25
|
declare const CopyButton: React.FC<CopyButtonProps>;
|
|
@@ -15,7 +15,7 @@ const primitives_1 = require("../../Theme/tokens/primitives");
|
|
|
15
15
|
const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
|
|
16
16
|
const ToolTip_1 = __importDefault(require("../ToolTip/ToolTip"));
|
|
17
17
|
/** Copies `value` to the clipboard in 3 layouts (icon/button/menu-item) — not a full share/export action. */
|
|
18
|
-
const CopyButton = ({ value, variant = "icon", label = "Copy", copiedLabel = "Copied!", tooltip = "Copy", feedbackDuration = 1500, iconSize = 16, size = "sm", onCopy, }) => {
|
|
18
|
+
const CopyButton = ({ value, variant = "icon", label = "Copy", copiedLabel = "Copied!", tooltip = "Copy", feedbackDuration = 1500, iconSize = 16, size = "sm", onCopy, onCopyError, }) => {
|
|
19
19
|
const theme = (0, useCustomTheme_1.useCustomTheme)();
|
|
20
20
|
const [copied, setCopied] = (0, react_1.useState)(false);
|
|
21
21
|
const timer = (0, react_1.useRef)();
|
|
@@ -30,7 +30,7 @@ const CopyButton = ({ value, variant = "icon", label = "Copy", copiedLabel = "Co
|
|
|
30
30
|
timer.current = setTimeout(() => setCopied(false), feedbackDuration);
|
|
31
31
|
}
|
|
32
32
|
catch {
|
|
33
|
-
|
|
33
|
+
onCopyError === null || onCopyError === void 0 ? void 0 : onCopyError(value);
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
// NOTE: primary[500] fails AA as canvas ink in 5/7 brands (dark) — accentTextOnCanvas steps to a clearing rung.
|
|
@@ -3,4 +3,4 @@ import { DrawerProps, DrawerHeaderSlotProps, DrawerBodySlotProps, DrawerFooterSl
|
|
|
3
3
|
export declare const DrawerHeader: React.FC<DrawerHeaderSlotProps>;
|
|
4
4
|
export declare const DrawerBody: React.FC<DrawerBodySlotProps>;
|
|
5
5
|
export declare const DrawerFooter: React.FC<DrawerFooterSlotProps>;
|
|
6
|
-
export default function Drawer({ isOpen, onClose, placement, size, id, variant, vaiant, initialFocusRef, finalFocusRef, closeOnOverlayClick, closeOnEsc, contentProps, hideCloseButton, portalChildren, children, }: DrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export default function Drawer({ isOpen, onClose, placement, size, id, variant, vaiant, initialFocusRef, finalFocusRef, closeOnOverlayClick, closeOnEsc, contentProps, hideCloseButton, portalChildren, children, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: DrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -9,6 +9,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
9
9
|
const react_1 = __importDefault(require("react"));
|
|
10
10
|
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
11
11
|
const surfaceRaised_1 = require("../../Theme/tokens/builders/surfaceRaised");
|
|
12
|
+
const dialogNaming_1 = require("../Modal/dialogNaming");
|
|
12
13
|
const warnDroppedSlotChildren_1 = require("../Modal/warnDroppedSlotChildren");
|
|
13
14
|
const react_2 = require("@chakra-ui/react");
|
|
14
15
|
const DrawerHeader = ({ children }) => {
|
|
@@ -39,12 +40,15 @@ const findSlot = (children, slot) => {
|
|
|
39
40
|
}
|
|
40
41
|
return undefined;
|
|
41
42
|
};
|
|
42
|
-
function
|
|
43
|
+
function DialogContent({ label, labelledBy, ...props }) {
|
|
44
|
+
return ((0, jsx_runtime_1.jsx)(dialogNaming_1.DialogNaming, { label: label, labelledBy: labelledBy, children: (0, jsx_runtime_1.jsx)(react_2.DrawerContent, { ...props }) }));
|
|
45
|
+
}
|
|
46
|
+
function Drawer({ isOpen, onClose, placement = "right", size = "xl", id, variant, vaiant, initialFocusRef, finalFocusRef, closeOnOverlayClick, closeOnEsc, contentProps, hideCloseButton, portalChildren, children, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }) {
|
|
43
47
|
var _a, _b;
|
|
44
48
|
const { colors } = (0, useCustomTheme_1.useCustomTheme)();
|
|
45
49
|
const headerSlot = findSlot(children, exports.DrawerHeader);
|
|
46
50
|
const bodySlot = findSlot(children, exports.DrawerBody);
|
|
47
51
|
const footerSlot = findSlot(children, exports.DrawerFooter);
|
|
48
52
|
(0, warnDroppedSlotChildren_1.warnDroppedSlotChildren)("Drawer", children, [exports.DrawerHeader, exports.DrawerBody, exports.DrawerFooter]);
|
|
49
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.Drawer, { isOpen: isOpen, placement: placement, onClose: onClose, size: size, variant: (_a = variant !== null && variant !== void 0 ? variant : vaiant) !== null && _a !== void 0 ? _a : "solid", id: id, initialFocusRef: initialFocusRef, finalFocusRef: finalFocusRef, closeOnOverlayClick: closeOnOverlayClick, closeOnEsc: closeOnEsc, children: [(0, jsx_runtime_1.jsx)(react_2.DrawerOverlay, {}), (0, jsx_runtime_1.jsxs)(
|
|
53
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Drawer, { isOpen: isOpen, placement: placement, onClose: onClose, size: size, variant: (_a = variant !== null && variant !== void 0 ? variant : vaiant) !== null && _a !== void 0 ? _a : "solid", id: id, initialFocusRef: initialFocusRef, finalFocusRef: finalFocusRef, closeOnOverlayClick: closeOnOverlayClick, closeOnEsc: closeOnEsc, children: [(0, jsx_runtime_1.jsx)(react_2.DrawerOverlay, {}), (0, jsx_runtime_1.jsxs)(DialogContent, { ...contentProps, label: ariaLabel, labelledBy: ariaLabelledBy, children: [!hideCloseButton && (0, jsx_runtime_1.jsx)(react_2.DrawerCloseButton, {}), headerSlot && ((0, jsx_runtime_1.jsx)(react_2.DrawerHeader, { borderBottomWidth: "1px", borderColor: (0, surfaceRaised_1.raisedRule)(colors), ...headerSlot.props, children: headerSlot })), (0, jsx_runtime_1.jsx)(react_2.DrawerBody, { ...((_b = bodySlot === null || bodySlot === void 0 ? void 0 : bodySlot.props) !== null && _b !== void 0 ? _b : {}), children: bodySlot }), footerSlot && ((0, jsx_runtime_1.jsx)(react_2.DrawerFooter, { borderTopWidth: "1px", borderColor: (0, surfaceRaised_1.raisedRule)(colors), ...footerSlot.props, children: footerSlot })), portalChildren] })] }));
|
|
50
54
|
}
|
|
@@ -14,6 +14,10 @@ export type DrawerProps = Pick<ChakraDrawerProps, "isOpen" | "onClose" | "placem
|
|
|
14
14
|
hideCloseButton?: boolean;
|
|
15
15
|
/** Rendered inside the drawer's dialog after the slots (so live regions stay announced); nested dialogs portal out and keep focus here. Unmounts with the drawer. */
|
|
16
16
|
portalChildren?: React.ReactNode;
|
|
17
|
+
/** Names the dialog instead of the whole header, for a header that holds fields or buttons. */
|
|
18
|
+
"aria-label"?: string;
|
|
19
|
+
/** Id of the element that names the dialog, e.g. a heading inside the header; wins over `aria-label`. */
|
|
20
|
+
"aria-labelledby"?: string;
|
|
17
21
|
};
|
|
18
22
|
/** Chakra style props (padding, borderBottom, gap, sx, ...) spread onto the rendered DrawerHeader; omit to keep theme defaults. Chakra's Drawer reuses Modal's header part internally. */
|
|
19
23
|
export type DrawerHeaderSlotProps = ModalHeaderProps;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { FormDrawerProps } from "./FormDrawerProps";
|
|
2
|
-
export default function FormDrawer({ isOpen, onClose, title, description, sections, errors, isDirty, onSubmit, isSubmitting, submitLabel, cancelLabel, secondaryAction, size, labels, "data-testid": testId, }: FormDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export default function FormDrawer({ isOpen, onClose, title, description, sections, errors, isDirty, onSubmit, isSubmitting, isSubmitDisabled, submitLabel, cancelLabel, secondaryAction, size, labels, "data-testid": testId, }: FormDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -51,7 +51,7 @@ const DEFAULT_LABELS = {
|
|
|
51
51
|
discard: "Discard",
|
|
52
52
|
keepEditing: "Keep editing",
|
|
53
53
|
};
|
|
54
|
-
function FormDrawer({ isOpen, onClose, title, description, sections, errors = [], isDirty, onSubmit, isSubmitting = false, submitLabel = "Save", cancelLabel = "Cancel", secondaryAction, size = "md", labels, "data-testid": testId = "form-drawer", }) {
|
|
54
|
+
function FormDrawer({ isOpen, onClose, title, description, sections, errors = [], isDirty, onSubmit, isSubmitting = false, isSubmitDisabled = false, submitLabel = "Save", cancelLabel = "Cancel", secondaryAction, size = "md", labels, "data-testid": testId = "form-drawer", }) {
|
|
55
55
|
const { colors } = (0, useCustomTheme_1.useCustomTheme)();
|
|
56
56
|
const uid = (0, react_1.useId)();
|
|
57
57
|
const formId = `${uid}-form`;
|
|
@@ -84,7 +84,7 @@ function FormDrawer({ isOpen, onClose, title, description, sections, errors = []
|
|
|
84
84
|
};
|
|
85
85
|
const submit = (event) => {
|
|
86
86
|
event.preventDefault();
|
|
87
|
-
if (isSubmitting)
|
|
87
|
+
if (isSubmitting || isSubmitDisabled)
|
|
88
88
|
return;
|
|
89
89
|
focusPending.current = true;
|
|
90
90
|
setAttempt((n) => n + 1);
|
|
@@ -93,5 +93,5 @@ function FormDrawer({ isOpen, onClose, title, description, sections, errors = []
|
|
|
93
93
|
return ((0, jsx_runtime_1.jsxs)(Drawer_1.default, { isOpen: isOpen, onClose: requestClose, size: size, closeOnEsc: !isSubmitting, closeOnOverlayClick: !isSubmitting, portalChildren: (0, jsx_runtime_1.jsx)(AlertDialog_1.default, { isOpen: confirmDiscard, onClose: () => setConfirmDiscard(false), leastDestructiveRef: keepEditingRef, isCentered: true, tone: "danger", title: text.discardTitle, content: (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "sm", color: colors.text[800], children: text.discardBody }), cancelButtonText: text.keepEditing, confirmButtonText: text.discard, onConfirm: () => {
|
|
94
94
|
setConfirmDiscard(false);
|
|
95
95
|
onClose();
|
|
96
|
-
} }), children: [(0, jsx_runtime_1.jsxs)(Drawer_1.DrawerHeader, { children: [(0, jsx_runtime_1.jsx)(react_2.Heading, { id: titleId, as: "h2", fontSize: "md", fontWeight: 600, color: colors.header[900], children: title }), description ? ((0, jsx_runtime_1.jsx)(react_2.Text, { mt: 0.5, fontSize: "sm", fontWeight: 400, color: colors.textMuted, children: description })) : null] }), (0, jsx_runtime_1.jsx)(Drawer_1.DrawerBody, { children: (0, jsx_runtime_1.jsx)(react_2.Box, { as: "form", id: formId, "aria-labelledby": titleId, onSubmit: submit, noValidate: true, "data-testid": testId, children: (0, jsx_runtime_1.jsxs)(react_2.Flex, { direction: "column", gap: 5, pt: 2, children: [errors.length ? ((0, jsx_runtime_1.jsxs)(react_2.Alert, { ref: summaryRef, tabIndex: -1, status: "error", variant: "subtle", borderRadius: "md", alignItems: "flex-start", "data-testid": `${testId}-errors`, children: [(0, jsx_runtime_1.jsx)(react_2.AlertIcon, {}), (0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsx)(react_2.AlertTitle, { fontSize: "sm", children: text.fixErrors }), (0, jsx_runtime_1.jsx)(react_2.AlertDescription, { fontSize: "sm", children: (0, jsx_runtime_1.jsx)(react_2.UnorderedList, { mt: 1, children: errors.map((message) => ((0, jsx_runtime_1.jsx)(react_2.ListItem, { children: message }, message))) }) })] })] })) : null, sections.map((section, index) => ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [index > 0 ? (0, jsx_runtime_1.jsx)(react_2.Divider, { borderColor: colors.boxborder[200] }) : null, (0, jsx_runtime_1.jsxs)(react_2.Flex, { as: "section", direction: "column", gap: 4, "aria-label": section.title, children: [section.title ? ((0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsx)(react_2.Heading, { as: "h3", fontSize: "sm", fontWeight: 600, color: colors.header[900], children: section.title }), section.description ? ((0, jsx_runtime_1.jsx)(react_2.Text, { mt: 0.5, fontSize: "xs", color: colors.textMuted, children: section.description })) : null] })) : null, section.content] })] }, section.id)))] }) }) }), (0, jsx_runtime_1.jsx)(Drawer_1.DrawerFooter, { children: (0, jsx_runtime_1.jsxs)(react_2.Flex, { gap: 2, w: "100%", justify: "flex-end", wrap: "wrap", children: [secondaryAction, (0, jsx_runtime_1.jsx)(Button_1.default, { variant: "ghost", colorScheme: "gray", onClick: requestClose, isDisabled: isSubmitting, label: cancelLabel }), (0, jsx_runtime_1.jsx)(Button_1.default, { variant: "solid", colorScheme: "primary", type: "submit", form: formId, isLoading: isSubmitting, loadingText: submitLabel, label: submitLabel, "data-testid": `${testId}-submit` })] }) })] }));
|
|
96
|
+
} }), children: [(0, jsx_runtime_1.jsxs)(Drawer_1.DrawerHeader, { children: [(0, jsx_runtime_1.jsx)(react_2.Heading, { id: titleId, as: "h2", fontSize: "md", fontWeight: 600, color: colors.header[900], children: title }), description ? ((0, jsx_runtime_1.jsx)(react_2.Text, { mt: 0.5, fontSize: "sm", fontWeight: 400, color: colors.textMuted, children: description })) : null] }), (0, jsx_runtime_1.jsx)(Drawer_1.DrawerBody, { children: (0, jsx_runtime_1.jsx)(react_2.Box, { as: "form", id: formId, "aria-labelledby": titleId, onSubmit: submit, noValidate: true, "data-testid": testId, children: (0, jsx_runtime_1.jsxs)(react_2.Flex, { direction: "column", gap: 5, pt: 2, children: [errors.length ? ((0, jsx_runtime_1.jsxs)(react_2.Alert, { ref: summaryRef, tabIndex: -1, status: "error", variant: "subtle", borderRadius: "md", alignItems: "flex-start", "data-testid": `${testId}-errors`, children: [(0, jsx_runtime_1.jsx)(react_2.AlertIcon, {}), (0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsx)(react_2.AlertTitle, { fontSize: "sm", children: text.fixErrors }), (0, jsx_runtime_1.jsx)(react_2.AlertDescription, { fontSize: "sm", children: (0, jsx_runtime_1.jsx)(react_2.UnorderedList, { mt: 1, children: errors.map((message) => ((0, jsx_runtime_1.jsx)(react_2.ListItem, { children: message }, message))) }) })] })] })) : null, sections.map((section, index) => ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [index > 0 ? (0, jsx_runtime_1.jsx)(react_2.Divider, { borderColor: colors.boxborder[200] }) : null, (0, jsx_runtime_1.jsxs)(react_2.Flex, { as: "section", direction: "column", gap: 4, "aria-label": section.title, children: [section.title ? ((0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsx)(react_2.Heading, { as: "h3", fontSize: "sm", fontWeight: 600, color: colors.header[900], children: section.title }), section.description ? ((0, jsx_runtime_1.jsx)(react_2.Text, { mt: 0.5, fontSize: "xs", color: colors.textMuted, children: section.description })) : null] })) : null, section.content] })] }, section.id)))] }) }) }), (0, jsx_runtime_1.jsx)(Drawer_1.DrawerFooter, { children: (0, jsx_runtime_1.jsxs)(react_2.Flex, { gap: 2, w: "100%", justify: "flex-end", wrap: "wrap", children: [secondaryAction, (0, jsx_runtime_1.jsx)(Button_1.default, { variant: "ghost", colorScheme: "gray", onClick: requestClose, isDisabled: isSubmitting, label: cancelLabel }), (0, jsx_runtime_1.jsx)(Button_1.default, { variant: "solid", colorScheme: "primary", type: "submit", form: formId, isLoading: isSubmitting, isDisabled: isSubmitDisabled, loadingText: submitLabel, label: submitLabel, "data-testid": `${testId}-submit` })] }) })] }));
|
|
97
97
|
}
|
|
@@ -40,6 +40,8 @@ export interface FormDrawerProps {
|
|
|
40
40
|
onSubmit: () => void;
|
|
41
41
|
/** Spins the submit button, disables Cancel and blocks every close path. Set it synchronously in `onSubmit` so error focus waits for the result. */
|
|
42
42
|
isSubmitting?: boolean;
|
|
43
|
+
/** Disables the primary button and swallows Enter and any other form submit, so `onSubmit` never fires; unlike `isSubmitting`, Cancel and every close path stay live. */
|
|
44
|
+
isSubmitDisabled?: boolean;
|
|
43
45
|
/** Defaults to "Save". */
|
|
44
46
|
submitLabel?: string;
|
|
45
47
|
/** Defaults to "Cancel". */
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
type DialogNamingProps = {
|
|
3
|
+
label?: string;
|
|
4
|
+
labelledBy?: string;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare function DialogNaming({ label, labelledBy, children }: DialogNamingProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DialogNaming = DialogNaming;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_2 = require("@chakra-ui/react");
|
|
7
|
+
// Chakra's getDialogProps always points aria-labelledby at the header, so override it at the source.
|
|
8
|
+
function DialogNaming({ label, labelledBy, children }) {
|
|
9
|
+
const modal = (0, react_2.useModalContext)();
|
|
10
|
+
const value = (0, react_1.useMemo)(() => label || labelledBy
|
|
11
|
+
? {
|
|
12
|
+
...modal,
|
|
13
|
+
getDialogProps: ((...args) => ({
|
|
14
|
+
...modal.getDialogProps(...args),
|
|
15
|
+
...(label ? { "aria-label": label } : {}),
|
|
16
|
+
"aria-labelledby": labelledBy,
|
|
17
|
+
})),
|
|
18
|
+
}
|
|
19
|
+
: modal, [modal, label, labelledBy]);
|
|
20
|
+
return (0, jsx_runtime_1.jsx)(react_2.ModalContextProvider, { value: value, children: children });
|
|
21
|
+
}
|
|
@@ -48,7 +48,7 @@ function LauncherTabs({ tabs, selected, onSelect, idBase, label, onLeaveUp, onLe
|
|
|
48
48
|
refs.current.set(tab.id, node);
|
|
49
49
|
else
|
|
50
50
|
refs.current.delete(tab.id);
|
|
51
|
-
}, "aria-selected": isSelected, "aria-controls": isSelected ? (0, exports.panelId)(idBase) : undefined, tabIndex: isSelected ? 0 : -1, onClick: () => onSelect(tab.id), position: "relative", flex: { base: 1, md: "none" }, minW: { md: "112px" }, h: "46px", px: { base: "6px", md: "14px" }, whiteSpace: "nowrap", fontSize: "
|
|
51
|
+
}, "aria-selected": isSelected, "aria-controls": isSelected ? (0, exports.panelId)(idBase) : undefined, tabIndex: isSelected ? 0 : -1, onClick: () => onSelect(tab.id), position: "relative", flex: { base: 1, md: "none" }, minW: { md: "112px" }, h: "46px", px: { base: "6px", md: "14px" }, whiteSpace: "nowrap", fontSize: "sm", fontWeight: isSelected ? 600 : 500, color: isSelected ? colors.accentText : colors.textMuted, transition: (0, motion_1.transitionFor)(["color"]), _hover: {
|
|
52
52
|
color: isSelected ? colors.accentText : colors.text[900],
|
|
53
53
|
}, _after: {
|
|
54
54
|
content: '""',
|
|
@@ -59,6 +59,6 @@ function LauncherTabs({ tabs, selected, onSelect, idBase, label, onLeaveUp, onLe
|
|
|
59
59
|
h: "2px",
|
|
60
60
|
borderRadius: "2px 2px 0 0",
|
|
61
61
|
bg: isSelected ? colors.primary[500] : "transparent",
|
|
62
|
-
}, sx: { ...ring, borderRadius: "
|
|
62
|
+
}, sx: { ...ring, borderRadius: "lg" }, children: tab.label }, tab.id));
|
|
63
63
|
}) }));
|
|
64
64
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { NavLauncherProps } from "./NavLauncherProps";
|
|
2
2
|
/** Left-drawer navigation launcher: browse every module, pins, Frequent, Recent, search and (compact) the suite's apps. */
|
|
3
|
-
export default function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, frequentIds, recentIds, onTogglePin, onNavigate, linkAs, actions, extraResults, query: controlledQuery, onQueryChange, isSearching, isLoading, keyScreenLimit, frequentLimit, suggestions, searchHint, modKeyLabel, headerActions, maxWidth, labels: labelOverrides, apps, currentAppId, onAppSelect, appsVisibility, "data-testid": testId, }: NavLauncherProps): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export default function NavLauncher({ isOpen, onClose, sections: rawSections, pinnedIds, frequentIds, recentIds, onTogglePin, onNavigate, linkAs, actions, extraResults, query: controlledQuery, onQueryChange, isSearching, isLoading, keyScreenLimit, frequentLimit, suggestions, searchHint, modKeyLabel, headerActions, maxWidth, labels: labelOverrides, apps, currentAppId, onAppSelect, appsVisibility, searchMode, synonyms, scopes, activeScope, onScopeChange, quickActions, answers, groupLimit, "data-testid": testId, }: NavLauncherProps): import("react/jsx-runtime").JSX.Element;
|