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.
Files changed (74) hide show
  1. package/CHANGELOG.md +83 -8
  2. package/COMPONENT-GOTCHAS.md +37 -4
  3. package/DESIGN-GUIDE.md +7 -3
  4. package/dist/Components/CopyButton/CopyButton.d.ts +3 -1
  5. package/dist/Components/CopyButton/CopyButton.js +2 -2
  6. package/dist/Components/Drawer/Drawer.d.ts +1 -1
  7. package/dist/Components/Drawer/Drawer.js +6 -2
  8. package/dist/Components/Drawer/DrawerProps.d.ts +4 -0
  9. package/dist/Components/FormDrawer/FormDrawer.d.ts +1 -1
  10. package/dist/Components/FormDrawer/FormDrawer.js +3 -3
  11. package/dist/Components/FormDrawer/FormDrawerProps.d.ts +2 -0
  12. package/dist/Components/Modal/dialogNaming.d.ts +8 -0
  13. package/dist/Components/Modal/dialogNaming.js +21 -0
  14. package/dist/Components/NavLauncher/LauncherTabs.js +2 -2
  15. package/dist/Components/NavLauncher/NavLauncher.d.ts +1 -1
  16. package/dist/Components/NavLauncher/NavLauncher.js +257 -47
  17. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +79 -3
  18. package/dist/Components/NavLauncher/ScopeBar.d.ts +14 -0
  19. package/dist/Components/NavLauncher/ScopeBar.js +137 -0
  20. package/dist/Components/NavLauncher/engine/distance.d.ts +4 -0
  21. package/dist/Components/NavLauncher/engine/distance.js +41 -0
  22. package/dist/Components/NavLauncher/engine/engine.d.ts +84 -0
  23. package/dist/Components/NavLauncher/engine/engine.js +471 -0
  24. package/dist/Components/NavLauncher/engine/fold.d.ts +15 -0
  25. package/dist/Components/NavLauncher/engine/fold.js +83 -0
  26. package/dist/Components/NavLauncher/engine/index.d.ts +21 -0
  27. package/dist/Components/NavLauncher/engine/index.js +17 -0
  28. package/dist/Components/NavLauncher/launcherParts.d.ts +2 -0
  29. package/dist/Components/NavLauncher/launcherParts.js +11 -7
  30. package/dist/Components/NavLauncher/match.d.ts +13 -2
  31. package/dist/Components/NavLauncher/match.js +26 -12
  32. package/dist/Components/NavLauncher/searchLayout.d.ts +60 -0
  33. package/dist/Components/NavLauncher/searchLayout.js +108 -0
  34. package/dist/Components/NavLauncher/spotlight.d.ts +39 -0
  35. package/dist/Components/NavLauncher/spotlight.js +202 -0
  36. package/dist/Components/NavLauncher/spotlightParts.d.ts +49 -0
  37. package/dist/Components/NavLauncher/spotlightParts.js +108 -0
  38. package/dist/Components/Table/components/TableHeader.js +3 -1
  39. package/dist/Components/Table/components/TableLoading.js +8 -2
  40. package/dist/Components/WizardDialog/WizardDialog.d.ts +1 -1
  41. package/dist/Components/WizardDialog/WizardDialog.js +15 -3
  42. package/dist/Components/WizardDialog/WizardDialogProps.d.ts +5 -3
  43. package/dist/Components/WorkspaceWindow/WorkspaceWindow.js +3 -2
  44. package/dist/Components/WorkspaceWindow/WorkspaceWindowProps.d.ts +2 -8
  45. package/dist/Theme/chakra/ringShadow.d.ts +3 -0
  46. package/dist/Theme/chakra/ringShadow.js +7 -0
  47. package/dist/Theme/tokens/builders/buildDarkPalette.d.ts +1 -1
  48. package/dist/Theme/tokens/builders/buildDarkPalette.js +5 -5
  49. package/dist/esm/Components/CopyButton/CopyButton.js +2 -2
  50. package/dist/esm/Components/Drawer/Drawer.js +6 -2
  51. package/dist/esm/Components/FormDrawer/FormDrawer.js +3 -3
  52. package/dist/esm/Components/Modal/dialogNaming.js +18 -0
  53. package/dist/esm/Components/NavLauncher/LauncherTabs.js +2 -2
  54. package/dist/esm/Components/NavLauncher/NavLauncher.js +259 -49
  55. package/dist/esm/Components/NavLauncher/ScopeBar.js +134 -0
  56. package/dist/esm/Components/NavLauncher/engine/distance.js +37 -0
  57. package/dist/esm/Components/NavLauncher/engine/engine.js +458 -0
  58. package/dist/esm/Components/NavLauncher/engine/fold.js +74 -0
  59. package/dist/esm/Components/NavLauncher/engine/index.js +14 -0
  60. package/dist/esm/Components/NavLauncher/launcherParts.js +10 -6
  61. package/dist/esm/Components/NavLauncher/match.js +25 -12
  62. package/dist/esm/Components/NavLauncher/searchLayout.js +100 -0
  63. package/dist/esm/Components/NavLauncher/spotlight.js +194 -0
  64. package/dist/esm/Components/NavLauncher/spotlightParts.js +95 -0
  65. package/dist/esm/Components/Table/components/TableHeader.js +4 -2
  66. package/dist/esm/Components/Table/components/TableLoading.js +6 -3
  67. package/dist/esm/Components/WizardDialog/WizardDialog.js +15 -3
  68. package/dist/esm/Components/WorkspaceWindow/WorkspaceWindow.js +4 -3
  69. package/dist/esm/Theme/chakra/ringShadow.js +3 -0
  70. package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +5 -5
  71. package/dist/esm/index.js +2 -1
  72. package/dist/index.d.ts +5 -3
  73. package/dist/index.js +4 -1
  74. 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, so brand hue carries
264
- through. `withTheme` stays light-only, so existing apps are visually unchanged — dark is opt-in.
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 carry the brand hue and separate by lift.** The dark canvas takes a faint brand tint and cards
465
- (`background[50]`) sit one step (1.16:1) above it, with overlays (`surfaceRaised`) one step (1.15:1) above
466
- the card; every filled mark keeps 3:1 on all three. Light mode is unchanged. A brand opts out with
467
- `buildDarkPalette(light, { planes: "flat" })`. `meadow` and `rosewood` dark `accentText` move one rung
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
@@ -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 does not simulate it, so use `fireEvent.submit(form)` in tests.
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
- - **`headerActions` text joins the dialog's accessible name** (header content becomes part of it,
1417
- as the search value already does: "Menu leave"). Use icon-only buttons with an `aria-label`.
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 `Wizard`: per-step `validate` messages, locked future steps, error summary, `summary`-driven review, `isFinishing` |
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 — a denied/insecure-context failure is swallowed silently. */
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
- /* clipboard unavailable — silently ignore */
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 Drawer({ isOpen, onClose, placement = "right", size = "xl", id, variant, vaiant, initialFocusRef, finalFocusRef, closeOnOverlayClick, closeOnEsc, contentProps, hideCloseButton, portalChildren, children, }) {
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)(react_2.DrawerContent, { ...contentProps, 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] })] }));
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: "14px", fontWeight: isSelected ? 600 : 500, color: isSelected ? colors.accentText : colors.textMuted, transition: (0, motion_1.transitionFor)(["color"]), _hover: {
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: "8px" }, children: tab.label }, tab.id));
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;