pixelize-design-library 2.4.2-beta.59 → 2.4.2-beta.61
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 +110 -13
- package/COMPONENT-GOTCHAS.md +159 -21
- package/DESIGN-GUIDE.md +26 -7
- package/dist/Components/AudioRecorder/AudioRecorder.d.ts +8 -0
- package/dist/Components/AudioRecorder/AudioRecorder.js +169 -0
- package/dist/Components/AudioRecorder/AudioRecorderProps.d.ts +20 -0
- package/dist/Components/AudioRecorder/AudioRecorderProps.js +2 -0
- package/dist/Components/BulkSelectionBar/BulkSelectionBar.d.ts +3 -0
- package/dist/Components/BulkSelectionBar/BulkSelectionBar.js +43 -0
- package/dist/Components/BulkSelectionBar/BulkSelectionBarProps.d.ts +35 -0
- package/dist/Components/BulkSelectionBar/BulkSelectionBarProps.js +2 -0
- package/dist/Components/BulkSelectionBar/index.d.ts +2 -0
- package/dist/Components/BulkSelectionBar/index.js +8 -0
- package/dist/Components/CalendarGrid/CalendarGrid.d.ts +3 -0
- package/dist/Components/CalendarGrid/CalendarGrid.js +173 -0
- package/dist/Components/CalendarGrid/CalendarGridProps.d.ts +54 -0
- package/dist/Components/CalendarGrid/CalendarGridProps.js +2 -0
- package/dist/Components/CalendarGrid/calendarMath.d.ts +49 -0
- package/dist/Components/CalendarGrid/calendarMath.js +178 -0
- package/dist/Components/CircularProgress/CircularProgress.d.ts +4 -0
- package/dist/Components/CircularProgress/CircularProgress.js +39 -0
- package/dist/Components/CircularProgress/CircularProgressProps.d.ts +22 -0
- package/dist/Components/CircularProgress/CircularProgressProps.js +2 -0
- package/dist/Components/CircularProgress/arc.d.ts +12 -0
- package/dist/Components/CircularProgress/arc.js +16 -0
- package/dist/Components/Common/fieldStyles.d.ts +1 -0
- package/dist/Components/Common/fieldStyles.js +2 -1
- package/dist/Components/Drawer/Drawer.d.ts +1 -1
- package/dist/Components/Drawer/Drawer.js +4 -2
- package/dist/Components/Drawer/DrawerProps.d.ts +3 -1
- package/dist/Components/EmptyState/EmptyState.js +2 -1
- package/dist/Components/EmptyState/EmptyStateProps.d.ts +2 -2
- package/dist/Components/FormDrawer/FormDrawer.d.ts +2 -0
- package/dist/Components/FormDrawer/FormDrawer.js +97 -0
- package/dist/Components/FormDrawer/FormDrawerProps.d.ts +55 -0
- package/dist/Components/FormDrawer/FormDrawerProps.js +2 -0
- package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +1 -1
- package/dist/Components/LauncherTrigger/LauncherTrigger.js +8 -1
- package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +2 -0
- package/dist/Components/Modal/Modal.d.ts +1 -1
- package/dist/Components/Modal/Modal.js +4 -2
- package/dist/Components/Modal/ModalProps.d.ts +3 -1
- package/dist/Components/Modal/warnDroppedSlotChildren.d.ts +2 -0
- package/dist/Components/Modal/warnDroppedSlotChildren.js +45 -0
- package/dist/Components/NavLauncher/LauncherTabs.d.ts +18 -0
- package/dist/Components/NavLauncher/LauncherTabs.js +64 -0
- package/dist/Components/NavLauncher/NavLauncher.d.ts +1 -1
- package/dist/Components/NavLauncher/NavLauncher.js +268 -61
- package/dist/Components/NavLauncher/NavLauncherProps.d.ts +29 -10
- package/dist/Components/NavLauncher/NewTabLink.d.ts +2 -2
- package/dist/Components/NavLauncher/NewTabLink.js +7 -3
- package/dist/Components/NavLauncher/launcherParts.d.ts +64 -2
- package/dist/Components/NavLauncher/launcherParts.js +103 -13
- package/dist/Components/NavLauncher/resultNavigation.d.ts +3 -0
- package/dist/Components/NavLauncher/resultNavigation.js +65 -0
- package/dist/Components/NavLauncher/revealStyles.d.ts +1 -1
- package/dist/Components/NavLauncher/revealStyles.js +2 -2
- package/dist/Components/NavigationBar/NavigationBar.js +2 -1
- package/dist/Components/NavigationBar/NavigationBarProps.d.ts +1 -0
- package/dist/Components/OrgSwitcher/OrgSwitcher.js +18 -6
- package/dist/Components/OrgSwitcher/OrgSwitcherProps.d.ts +4 -0
- package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +5 -0
- package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewerProps.d.ts +1 -1
- package/dist/Components/QueryState/QueryStateBoundary.d.ts +2 -2
- package/dist/Components/QueryState/QueryStateBoundary.js +9 -3
- package/dist/Components/QueryState/QueryStateProps.d.ts +19 -2
- package/dist/Components/SectionRail/SectionRail.d.ts +3 -0
- package/dist/Components/SectionRail/SectionRail.js +49 -0
- package/dist/Components/SectionRail/SectionRailProps.d.ts +23 -0
- package/dist/Components/SectionRail/SectionRailProps.js +2 -0
- package/dist/Components/SectionRail/index.d.ts +2 -0
- package/dist/Components/SectionRail/index.js +8 -0
- package/dist/Components/SegmentedControl/SegmentedControl.js +9 -14
- package/dist/Components/SegmentedControl/segmentedInk.d.ts +0 -2
- package/dist/Components/SegmentedControl/segmentedInk.js +1 -5
- package/dist/Components/StatCard/StatCard.d.ts +1 -1
- package/dist/Components/StatCard/StatCard.js +6 -2
- package/dist/Components/StatCard/StatCardProps.d.ts +3 -0
- package/dist/Components/StatusTag/StatusTag.d.ts +4 -0
- package/dist/Components/StatusTag/StatusTag.js +24 -0
- package/dist/Components/StatusTag/StatusTagProps.d.ts +20 -0
- package/dist/Components/StatusTag/StatusTagProps.js +2 -0
- package/dist/Components/Table/Table.d.ts +1 -1
- package/dist/Components/Table/Table.js +6 -2
- package/dist/Components/Table/TableProps.d.ts +20 -0
- package/dist/Components/Table/components/TableBody.d.ts +1 -1
- package/dist/Components/Table/components/TableBody.js +43 -18
- package/dist/Components/Table/components/TableHeader.d.ts +1 -1
- package/dist/Components/Table/components/TableHeader.js +7 -5
- package/dist/Components/Table/components/rowDetail.d.ts +32 -0
- package/dist/Components/Table/components/rowDetail.js +20 -0
- package/dist/Components/Table/hooks/useRowExpansion.d.ts +7 -0
- package/dist/Components/Table/hooks/useRowExpansion.js +27 -0
- package/dist/Components/Tabs/Tabs.d.ts +1 -1
- package/dist/Components/Tabs/Tabs.js +15 -2
- package/dist/Components/Tabs/TabsProps.d.ts +6 -2
- package/dist/Components/Tabs/components/Tab.js +4 -33
- package/dist/Components/Tabs/components/TabLinkList.d.ts +7 -0
- package/dist/Components/Tabs/components/TabLinkList.js +34 -0
- package/dist/Components/Tabs/useTabAppearance.d.ts +17 -0
- package/dist/Components/Tabs/useTabAppearance.js +67 -0
- package/dist/Components/Tag/Tag.js +11 -1
- package/dist/Components/Tag/TagProps.d.ts +4 -0
- package/dist/Components/Tag/tagDot.d.ts +3 -0
- package/dist/Components/Tag/tagDot.js +21 -0
- package/dist/esm/Components/AudioRecorder/AudioRecorder.js +161 -0
- package/dist/esm/Components/AudioRecorder/AudioRecorderProps.js +1 -0
- package/dist/esm/Components/BulkSelectionBar/BulkSelectionBar.js +38 -0
- package/dist/esm/Components/BulkSelectionBar/BulkSelectionBarProps.js +1 -0
- package/dist/esm/Components/BulkSelectionBar/index.js +1 -0
- package/dist/esm/Components/CalendarGrid/CalendarGrid.js +168 -0
- package/dist/esm/Components/CalendarGrid/CalendarGridProps.js +1 -0
- package/dist/esm/Components/CalendarGrid/calendarMath.js +155 -0
- package/dist/esm/Components/CircularProgress/CircularProgress.js +37 -0
- package/dist/esm/Components/CircularProgress/CircularProgressProps.js +1 -0
- package/dist/esm/Components/CircularProgress/arc.js +11 -0
- package/dist/esm/Components/Common/fieldStyles.js +2 -1
- package/dist/esm/Components/Drawer/Drawer.js +4 -2
- package/dist/esm/Components/EmptyState/EmptyState.js +2 -1
- package/dist/esm/Components/FormDrawer/FormDrawer.js +58 -0
- package/dist/esm/Components/FormDrawer/FormDrawerProps.js +1 -0
- package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +5 -1
- package/dist/esm/Components/Modal/Modal.js +4 -2
- package/dist/esm/Components/Modal/warnDroppedSlotChildren.js +38 -0
- package/dist/esm/Components/NavLauncher/LauncherTabs.js +58 -0
- package/dist/esm/Components/NavLauncher/NavLauncher.js +270 -63
- package/dist/esm/Components/NavLauncher/NewTabLink.js +4 -3
- package/dist/esm/Components/NavLauncher/launcherParts.js +98 -14
- package/dist/esm/Components/NavLauncher/resultNavigation.js +61 -0
- package/dist/esm/Components/NavLauncher/revealStyles.js +2 -2
- package/dist/esm/Components/NavigationBar/NavigationBar.js +2 -1
- package/dist/esm/Components/OrgSwitcher/OrgSwitcher.js +16 -7
- package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +5 -0
- package/dist/esm/Components/QueryState/QueryStateBoundary.js +10 -4
- package/dist/esm/Components/SectionRail/SectionRail.js +44 -0
- package/dist/esm/Components/SectionRail/SectionRailProps.js +1 -0
- package/dist/esm/Components/SectionRail/index.js +1 -0
- package/dist/esm/Components/SegmentedControl/SegmentedControl.js +11 -16
- package/dist/esm/Components/SegmentedControl/segmentedInk.js +0 -3
- package/dist/esm/Components/StatCard/StatCard.js +6 -2
- package/dist/esm/Components/StatusTag/StatusTag.js +19 -0
- package/dist/esm/Components/StatusTag/StatusTagProps.js +1 -0
- package/dist/esm/Components/Table/Table.js +7 -3
- package/dist/esm/Components/Table/components/TableBody.js +43 -18
- package/dist/esm/Components/Table/components/TableHeader.js +7 -5
- package/dist/esm/Components/Table/components/rowDetail.js +14 -0
- package/dist/esm/Components/Table/hooks/useRowExpansion.js +23 -0
- package/dist/esm/Components/Tabs/Tabs.js +15 -2
- package/dist/esm/Components/Tabs/components/Tab.js +4 -33
- package/dist/esm/Components/Tabs/components/TabLinkList.js +29 -0
- package/dist/esm/Components/Tabs/useTabAppearance.js +63 -0
- package/dist/esm/Components/Tag/Tag.js +12 -2
- package/dist/esm/Components/Tag/tagDot.js +17 -0
- package/dist/esm/index.js +7 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +18 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -24,18 +24,44 @@ next major.
|
|
|
24
24
|
with actions and remote groups, optional suite-apps section below `md`), with `PinToggle`,
|
|
25
25
|
`NewTabLink`, `searchLauncher`, `searchLauncherSections` (sectioned search: modules, then their
|
|
26
26
|
screens, keyword-only hits get a `· keyword` hint) and `flattenScreens`. The pin control is a
|
|
27
|
-
star, shown with the ↗ link on row hover/focus only (always on touch); pinned rail items are tiles. It binds
|
|
27
|
+
star, shown with the ↗ link on row hover/focus only (always on touch); pinned rail items are tiles. It never opens itself or binds a global chord (Cmd/Ctrl+K and `/` only refocus its field while it is open). `LauncherTrigger`
|
|
28
28
|
is the chip (10px radius, 36px, accent ink, one ⌘K hint) that opens an app-owned launcher (`aria-haspopup="dialog"`, `aria-expanded`,
|
|
29
|
-
`aria-keyshortcuts`)
|
|
29
|
+
`aria-keyshortcuts`); `display="icon"` is a 36px icon-only variant for a nav bar's `moreIcon`
|
|
30
|
+
(label is the accessible name, tooltip "Menu (⌘K)"). Types: `NavLauncherProps`, `LauncherScreen`, `LauncherModule`,
|
|
30
31
|
`LauncherSection`, `LauncherSearchItem`, `LauncherLabels` and friends, `LauncherTriggerProps`.
|
|
31
|
-
`NavLauncher` follows canvas `Launcher.dc.html`: stacked sections with "(n)" counts and a
|
|
32
|
-
grid
|
|
33
|
-
|
|
32
|
+
`NavLauncher` follows canvas `Launcher.dc.html`: from `lg` up, stacked sections with "(n)" counts and a
|
|
33
|
+
1/2/3-column grid beside a "Quick access" rail of `clamp(20rem, 30%, 26rem)` in a panel whose `maxWidth`
|
|
34
|
+
defaults to 1080px (Pinned tiles, Frequently visited, Recent). Below `lg` (phone and tablet) the body is a
|
|
35
|
+
Menu / Quick access / Apps tab bar (labels `tabsLabel`, `tabMenu`, `tabQuickAccess`, `tabApps`; Apps follows
|
|
36
|
+
`appsVisibility`) above one scrolling panel with only the selected panel mounted, rows are 44px, and search
|
|
37
|
+
keeps the stacked results plus rail with no tab bar. The header is the search field (fills the row; 44px at
|
|
38
|
+
`md`+, 40px below, 16px type), a muted live result count (`labels.resultsCount`, with a separate polite
|
|
39
|
+
announcement), optional `headerActions` (an app's own icon button) and close; there is no "Menu" pill and
|
|
40
|
+
`labels.title` names the dialog and the results list. "Pins follow your account" footer; its key hints
|
|
41
|
+
are key caps (arrows, ENTER, ⌘ ENTER / CTRL ENTER, ⌘ + click, P, ESC) and are hidden on touch devices
|
|
42
|
+
(`labels.hintClickNewTab`; the ↗ link's tooltip is `labels.newTabTip`, `NewTabLink` takes `tip`). New optional props `frequentLimit`, `suggestions` (no-results chips), `searchHint`,
|
|
34
43
|
`LauncherModule.isNew`, and labels `quickAccess`, `newBadge`, `screens`, `pinShort`, `footerNote`,
|
|
35
44
|
`suggestionsHint`, `found`, `pinTip`, `unpinTip`. The rail landmark is named "Quick access".
|
|
36
|
-
Keyboard:
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
Keyboard: in the field, the first result is active as soon as there is a query, ↑/↓ follow the
|
|
46
|
+
visual rows of the result grid, → at the end of the text enters the grid and ←/→/Home/End then move
|
|
47
|
+
through results until the query changes, and PageUp/PageDown page; Esc clears the query first and
|
|
48
|
+
closes only when it is empty; Cmd/Ctrl+K and `/` focus the field. In lists ←/→ and ↑/↓ are grid-aware
|
|
49
|
+
(row-major, ↑/↓ cross sections), Home/End/PageUp/PageDown work, `P` pins or unpins the focused item
|
|
50
|
+
(never while typing) and typing anywhere else in the launcher refocuses the field; ArrowUp from the
|
|
51
|
+
first item returns to the search field (or the selected tab) and pinned tiles are a 2-column grid
|
|
52
|
+
(3 in the tablet Quick access tab). The keyboard-active row highlights as a whole (soft fill plus
|
|
53
|
+
one 1px inset ring, at least 3:1), the whole row is the link target (Cmd/middle-click opens a new
|
|
54
|
+
tab), and truncated titles, rail rows, pinned tiles, app names and search rows show a tooltip with
|
|
55
|
+
the full text. New label `hintPin`; hint label defaults are lowercase and follow the canvas order.
|
|
56
|
+
`LauncherLabels` gained required keys (`hintClickNewTab`, `newTabTip`, `resultsCount`, `tabsLabel`,
|
|
57
|
+
`tabMenu`, `tabQuickAccess`, `tabApps`): a consumer that types a full `LauncherLabels` object needs
|
|
58
|
+
them, while the `labels` prop (`Partial<LauncherLabels>`) is unaffected. Consumer tests: below `lg`
|
|
59
|
+
the Quick access and Apps sections are behind their tabs, and the dialog's accessible name now comes
|
|
60
|
+
from a visually hidden "Menu" heading.
|
|
61
|
+
- `OrgSwitcher` `display` (`"full"` default | `"avatar"` | `"auto"`) — `"avatar"` is a 36px avatar plus
|
|
62
|
+
chevron trigger whose accessible name is `Switch organization, <name>` with the name in a tooltip;
|
|
63
|
+
`"auto"` is avatar below `md` and full from `md` up. Types `OrgSwitcherProps`, `OrgSwitcherSize` and
|
|
64
|
+
`OrgSwitcherDisplay` are now exported from the package root.
|
|
39
65
|
- `Kbd` / `KbdShortcut` — a key cap and a chord (`mod+k` renders ⌘ K or Ctrl K), in the
|
|
40
66
|
`AppSwitcher` notation. `Search` and `AppSwitcher` render their shortcut hints with `Kbd`.
|
|
41
67
|
- `NavigationBar` `leading` — a slot between the sidebar toggle/back button and the title, outside
|
|
@@ -68,6 +94,48 @@ next major.
|
|
|
68
94
|
`navigator.pdfViewerEnabled`. `PdfViewerProps` / `PdfViewerLabels` are now exported types.
|
|
69
95
|
- Page examples: **App launcher** (`/examples/launcher`), **Wizard** (`/examples/wizard`),
|
|
70
96
|
**Template editor** (`/examples/template-editor`).
|
|
97
|
+
- `Tag` `display="dot"` — an 8px tone dot plus plain label for quiet rows (`variant` and icons
|
|
98
|
+
are ignored; the dot ink is lifted to 3:1 on both canvases). `StatusTag` (+ `StatusTagProps`,
|
|
99
|
+
`StatusAppearance`) maps an app's status union to a `Tag` tone once; an unknown status renders
|
|
100
|
+
neutral and warns once in development. (HRMS #13)
|
|
101
|
+
- `Drawer` / `Modal` `portalChildren` — rendered **inside** the dialog content after the slots (not
|
|
102
|
+
a sibling in the portal, which Chakra would `aria-hide`), for nested dialogs and live regions;
|
|
103
|
+
a nested `AlertDialog` still portals out, stacks above and returns focus. (HRMS #20)
|
|
104
|
+
- `FormDrawer` (+ `FormDrawerProps`, `FormDrawerSection`, `FormDrawerLabels`) — a sectioned
|
|
105
|
+
create/edit form in a `Drawer`: `role="alert"` error summary focused after a failed submit,
|
|
106
|
+
sticky footer with a real `<form>` submit, discard-changes confirm on every close path while
|
|
107
|
+
`isDirty`, all close paths blocked while `isSubmitting`. (HRMS #14)
|
|
108
|
+
- `Table` row detail expander — `renderRowDetail(row)` (null = no chevron), `expandedRows` /
|
|
109
|
+
`onExpandedRowsChange` (uncontrolled when `expandedRows` is omitted). Chevron column after the
|
|
110
|
+
checkbox, full-width detail row; works with pagination, virtualisation, frozen columns and
|
|
111
|
+
grouped mode. The chevron is a button with `aria-expanded` / `aria-controls`. (HRMS #17)
|
|
112
|
+
- `EmptyState` `description` accepts a `ReactNode` — a string keeps today's styling, a node sits in
|
|
113
|
+
the same muted block above `action`. (HRMS #18)
|
|
114
|
+
- `QueryStateBoundary` `isForbidden` (boolean or `(error) => boolean`), `forbidden`
|
|
115
|
+
(`{ title, description, icon, action }`) and `hasData`; type `QueryStateForbiddenProps`. Order is
|
|
116
|
+
now loading (only when `!hasData`) → forbidden → error → empty → children; forbidden is a lock
|
|
117
|
+
`EmptyState` with `role="status"`. Defaults unchanged. (HRMS #21)
|
|
118
|
+
- `Tabs` link mode — `TabItem.href` and `TabsProps.linkAs`: when every item has an `href`, `Tabs`
|
|
119
|
+
renders a `<nav aria-label>` of links (same variants and sizes, no panels, not a tablist) with
|
|
120
|
+
`aria-current="page"` on `value`; modifier and middle clicks are never intercepted. (HRMS #19)
|
|
121
|
+
- `BulkSelectionBar` (+ `BulkAction`, `BulkActionTone`, `BulkSelectionBarProps`) — selection bar for
|
|
122
|
+
beside a `Table`: "{n} selected", facts about the whole selection, tone-ranked actions, clear,
|
|
123
|
+
`inline` or `sticky`, polite count announcement; renders nothing at 0. (HRMS #15)
|
|
124
|
+
- `SectionRail` (+ `RailSection`, `SectionRailProps`) — section navigation for long settings
|
|
125
|
+
screens: a vertical `nav` with `aria-current` and a dirty dot per section, collapsing to a
|
|
126
|
+
`Select` below `collapseBelow` (default `md`). (HRMS #16)
|
|
127
|
+
- `StatCard` `iconTone` (neutral / info / success / warning / error) paints the icon chip with the
|
|
128
|
+
soft tone; unset keeps today's brand-primary chip. (HRMS #23)
|
|
129
|
+
- `CircularProgress` (+ `CircularProgressProps`, `CircularProgressSize`) — SVG ring, sm/md/lg =
|
|
130
|
+
32/48/72px, `thickness`, `tone`, centred `label`, `role="progressbar"`. (HRMS #24)
|
|
131
|
+
- `CalendarGrid` (+ `CalendarEvent`, `CalendarGridProps`, `CalendarGridView`, `CalendarGridLabels`) —
|
|
132
|
+
a controlled month/week/day grid of date-granularity events: multi-day spans, "+N more" opens the
|
|
133
|
+
day, `role="grid"` with arrow/Home/End/Page keys, link or button chips in `Tag` tones, `locale`,
|
|
134
|
+
`weekStartsOn`, `isLoading`. (HRMS #25)
|
|
135
|
+
- `AudioRecorder` (+ `AudioRecorderProps`, `AudioRecorderLabels`) — microphone recorder: asks for
|
|
136
|
+
the mic on Start, mm:ss timer, `maxDurationSec` auto-stop, hands a `File` named for the recorded
|
|
137
|
+
mime to `onRecorded`, permission-denied and unsupported states, releases the mic on unmount.
|
|
138
|
+
(HRMS #26)
|
|
71
139
|
|
|
72
140
|
- **Commerce kit** — the components for a shop front, designed mobile-first (Pebble principles:
|
|
73
141
|
chromeless tonal cards, one loud action per view, selection never by colour alone). Product:
|
|
@@ -152,6 +220,16 @@ next major.
|
|
|
152
220
|
- `Editor` binds Jodit's `change` / `blur` itself on every Jodit instance and renders Jodit only once
|
|
153
221
|
its popup root exists (no throwaway first instance). Behaviour for existing props is unchanged.
|
|
154
222
|
- `BreadcrumbItem.path` is optional (type widening).
|
|
223
|
+
- **`Drawer` / `Modal` warn when they drop a child.** A child that is not a Header/Body/Footer slot
|
|
224
|
+
still renders nothing, but now logs a dev-only `console.warn` (once per child type) pointing at
|
|
225
|
+
`portalChildren`. (HRMS #20)
|
|
226
|
+
- **`Table` `studio` variant on touch devices:** under `@media (pointer: coarse)` the row checkbox is
|
|
227
|
+
always visible and the row number is hidden, instead of a hover-only reveal. (HRMS #17)
|
|
228
|
+
- **`ProfilePhotoViewer` re-syncs when `photoUrl` changes** (it used to seed on mount only), so
|
|
229
|
+
consumers can drop `key={photoUrl}`. A locally picked preview is replaced by the new URL — update
|
|
230
|
+
`photoUrl` only once an upload has finished. (HRMS #22)
|
|
231
|
+
- **Page examples:** `records.tsx` uses `BulkSelectionBar` instead of the `Table`'s `headerActions`;
|
|
232
|
+
`settings.tsx` uses `SectionRail` instead of a hand-built section nav.
|
|
155
233
|
|
|
156
234
|
- **`ProductCard` and `ProductDetails` are rewritten** — see Breaking below. `ProductCard` is a
|
|
157
235
|
chromeless 4:5 card (grid / list / rail, swatch dots that swap the photo, quick-add sheet or
|
|
@@ -192,6 +270,21 @@ next major.
|
|
|
192
270
|
|
|
193
271
|
### Fixed
|
|
194
272
|
|
|
273
|
+
- **`NavigationBar` right cluster no longer overflows onto the avatar at phone widths.** The title
|
|
274
|
+
truncates first (overflow-only tooltip when it is a string); the `moreIcon` cluster can shrink, while
|
|
275
|
+
the sidebar toggle, back button and avatar menu never do. (HRMS, beta.60 launcher trigger overflow)
|
|
276
|
+
- **`OrgSwitcher` trigger shrinks and its menu fits the screen.** The full trigger truncates its label
|
|
277
|
+
(avatar and chevron are kept) when its container is short, and the menu panel is capped at
|
|
278
|
+
`100vw - 32px`, so a long org name no longer causes horizontal page scroll on phones.
|
|
279
|
+
- **`NavLauncher` keyboard and link fixes.** Arrow keys, typing and Backspace were dead on rail and
|
|
280
|
+
apps rows while a search was active; Enter could throw when results shrank; Enter during IME
|
|
281
|
+
composition opened a result; removing the focused row (e.g. an app dropped from `apps`) dropped
|
|
282
|
+
focus to the page body (it now moves to the neighbouring row, or the field).
|
|
283
|
+
- **`Search` / `TextInput` / `Select` / `Textarea` / `NumberInput` / `FileUpload` / `PincodeChecker`
|
|
284
|
+
focus glow was transitioned in**, so a screenshot or accessibility check taken the instant a field
|
|
285
|
+
gained focus saw no ring. `FIELD_TRANSITION` no longer animates `box-shadow`; border-colour and
|
|
286
|
+
background transitions are unchanged and still honour reduced motion.
|
|
287
|
+
|
|
195
288
|
- **`SideBar` rail controls now show a `:focus-visible` ring** (rows, submenu and flyout rows,
|
|
196
289
|
`SecondaryBar`, collapse toggle, Add New, user footer, Collapse/Hide/Show, switch-app cells,
|
|
197
290
|
overlay close) — WCAG 2.4.7 / 2.4.11.
|
|
@@ -370,8 +463,8 @@ next major.
|
|
|
370
463
|
which is deep in light and pale in dark, so the bar inverted and the whole comparison was
|
|
371
464
|
unreadable. It now derives the bar from `resolveFilledSurface(...).activeFill` and **measures**
|
|
372
465
|
both contrast ratios live against the active brand and mode, instead of printing two numbers that
|
|
373
|
-
were only ever true in light mode. `segmentedTrackSurface
|
|
374
|
-
|
|
466
|
+
were only ever true in light mode. `segmentedTrackSurface` and `segmentedRestInk`
|
|
467
|
+
are now one shared module, so the demo, the component and the contrast
|
|
375
468
|
gate cannot drift apart.
|
|
376
469
|
- **`SegmentedControl` on a brand-filled bar was unreadable mud.** `brandSlide`'s track is an 8%
|
|
377
470
|
brand tint, which on a brand bar composites straight back into the bar — the control lost its
|
|
@@ -387,9 +480,11 @@ next major.
|
|
|
387
480
|
the ring's surface-toned halo painted a white band and both bands landed outside the pill — a
|
|
388
481
|
thick double border that overflowed the track. A radiogroup is also a single tab stop, so ringing
|
|
389
482
|
one segment says nothing the selection does not already say. The **group** now carries the
|
|
390
|
-
indicator
|
|
391
|
-
|
|
392
|
-
`
|
|
483
|
+
indicator: Button's own two-tone ring (halo plus brand band, at least 3:1 on any fill, light and
|
|
484
|
+
dark) on the track while a segment has keyboard focus (never after a mouse click).
|
|
485
|
+
`underlineSlide` has no track chrome, so there the
|
|
486
|
+
segment wears `focusVisibleStyles`. `ThemeSwitcher`'s mode segments follow the same rule through
|
|
487
|
+
their own border.
|
|
393
488
|
- **`Switch` clipped long labels.** Its 12.5rem width was fixed, so labels wider than the track
|
|
394
489
|
overflowed the background; it is now a minimum. `SegmentedControl`'s track carries
|
|
395
490
|
`minW="max-content"` for the same reason.
|
|
@@ -422,6 +517,8 @@ next major.
|
|
|
422
517
|
|
|
423
518
|
Nothing is removed yet — each of these still ships its full implementation.
|
|
424
519
|
|
|
520
|
+
- `NavLauncher` `openShortcutHint` and `LauncherLabels.hintOpenMenu` — no longer rendered (the launcher
|
|
521
|
+
is already open when its footer shows); bind the open chord in the app only.
|
|
425
522
|
- `SelectV2` — use `Select` with `native={false}`; it is already a thin wrapper over it.
|
|
426
523
|
- `SearchSelect`, `SelectSearch` are **no longer `@deprecated`.** Both now say "prefer `Select`
|
|
427
524
|
for new work" instead: `@deprecated` claimed a migration that loses real capabilities
|
package/COMPONENT-GOTCHAS.md
CHANGED
|
@@ -177,6 +177,15 @@ is now the library's own themed component, with a real dual-thumb mode and a `ma
|
|
|
177
177
|
|
|
178
178
|
**Apply:** "on vs off" is always `ToggleSwitch`; 2+ named options is always `SegmentedControl`.
|
|
179
179
|
|
|
180
|
+
**`SegmentedControl`'s focus ring sits on the track, not the segment** (Button's two-tone ring). It is
|
|
181
|
+
driven by a `:focus-visible` check in `onFocus`, so a mouse click stays ringless. `underlineSlide` has no
|
|
182
|
+
track chrome, so its focused segment wears the ring instead.
|
|
183
|
+
|
|
184
|
+
**A focus indicator must never be in a `transition` list.** The 0ms computed style is the transparent
|
|
185
|
+
start value, so a screenshot or axe check taken on focus misses it. Field styles go through
|
|
186
|
+
`FIELD_TRANSITION` (`Common/fieldStyles.ts`), which animates `border-color` and `background-color`
|
|
187
|
+
only; keep `box-shadow` out of any transition you add around a field.
|
|
188
|
+
|
|
180
189
|
**`SegmentedControl` and `Tabs` look near-identical and are not interchangeable.** They share the
|
|
181
190
|
size scale and tokens, so `SegmentedControl variant="underlineSlide"` and `Tabs variant="line"`
|
|
182
191
|
render almost the same picture, as do `variant="default"` and `Tabs variant="subtle"`. The
|
|
@@ -304,7 +313,7 @@ not enough when the overlay sits inside a transition wrapper — the wrapper kee
|
|
|
304
313
|
| --- | --- | --- |
|
|
305
314
|
| "Nothing here yet" with an optional action | `EmptyState` | Neutral placeholder |
|
|
306
315
|
| A failed load with retry | `ErrorState` | `role="alert"`, `onRetry`/`isRetrying` built in |
|
|
307
|
-
| One fetch that can be loading, failed, empty or ready | `QueryStateBoundary` | Picks for you: loading > error > empty > `children` |
|
|
316
|
+
| One fetch that can be loading, forbidden, failed, empty or ready | `QueryStateBoundary` | Picks for you: loading > forbidden > error > empty > `children` |
|
|
308
317
|
|
|
309
318
|
---
|
|
310
319
|
|
|
@@ -421,6 +430,12 @@ renders a plain tag with no way to remove it.
|
|
|
421
430
|
removable chip. `icon` alone (no `onIconClick`) is the decorative case — a plain right-side icon
|
|
422
431
|
with no click affordance, aria-hidden.
|
|
423
432
|
|
|
433
|
+
### `Tag display="dot"` ignores `variant` and icons; `StatusTag` renders an unknown status neutral
|
|
434
|
+
|
|
435
|
+
`display="dot"` ignores `variant`, `icon`, `leadingIcon` and `onIconClick`. `StatusTag`'s `map` is
|
|
436
|
+
keyed by your status union, but a runtime value outside it (API drift) renders a neutral chip and
|
|
437
|
+
only warns in development — guard upstream if a silent neutral would mislead.
|
|
438
|
+
|
|
424
439
|
### A rounded box with `overflow: hidden` clips glyphs at the corners — never round the clipping element
|
|
425
440
|
|
|
426
441
|
`border-radius` reshapes the overflow clip, so a pill-shaped element with `overflow: hidden`
|
|
@@ -457,20 +472,33 @@ popovers and menus now paint `colors.surfaceRaised` — identical `#ffffff` in l
|
|
|
457
472
|
step up in dark. A custom panel, popover or `ModalContent bg=` in an app should read
|
|
458
473
|
`surfaceRaised` too, and a divider inside it `raisedRule(colors)`.
|
|
459
474
|
|
|
460
|
-
### `Drawer` / `Modal` / `ProfileCard` drop any child that isn't a named slot
|
|
475
|
+
### `Drawer` / `Modal` / `ProfileCard` drop any child that isn't a named slot — use `portalChildren`
|
|
461
476
|
|
|
462
477
|
All three match children by exact type (`child.type === XHeader/XBody/XFooter`) and render only
|
|
463
478
|
those matches — nothing else in `children` is ever read. A sibling among the slots (a nested
|
|
464
|
-
`Modal`, `AlertDialog`, a confirm dialog, a
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
drops the slot
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
479
|
+
`Modal`, `AlertDialog`, a confirm dialog, a live region) silently never mounts; `Drawer` and `Modal`
|
|
480
|
+
now warn once per dropped child type in development, `ProfileCard` still says nothing. All three
|
|
481
|
+
unwrap **one** `<>...</>` level around a slot (so `<>{cond && <DrawerFooter>...}</>` still renders)
|
|
482
|
+
and spread styling props (`padding`, `borderBottom`, `sx`, …) from the matched slot onto the real
|
|
483
|
+
header/body/footer — a second nested fragment, or any other wrapper element, still drops the slot
|
|
484
|
+
silently.
|
|
485
|
+
|
|
486
|
+
**Apply:** pass a nested confirm dialog, toast host or live region to `Drawer` / `Modal` as
|
|
487
|
+
`portalChildren`. It renders **inside** the dialog content after the slots (a sibling in the portal
|
|
488
|
+
would be `aria-hidden` by Chakra, so a live region there is never announced); a nested `AlertDialog`
|
|
489
|
+
portals out on its own, stacks above and returns focus. It unmounts with the dialog, so reset its
|
|
490
|
+
open state when the parent closes. `ProfileCard` has no such prop — keep its confirm dialog outside,
|
|
491
|
+
as a sibling in a wrapping fragment. When a ternary picks between bodies, branch into a variable
|
|
492
|
+
rendered inside one body slot, rather than returning fragment-wrapped slots per branch. `Drawer`'s
|
|
493
|
+
misspelled `vaiant` is `@deprecated` — use `variant`.
|
|
494
|
+
|
|
495
|
+
### `FormDrawer` has no browser validation, and its submit button sits outside the `<form>`
|
|
496
|
+
|
|
497
|
+
It sets `noValidate`: validate in `onSubmit` and pass messages via `errors`. Error-summary focus
|
|
498
|
+
fires only after a submit; if validation is async, set `isSubmitting` synchronously in `onSubmit` so
|
|
499
|
+
focus waits for the result, otherwise errors that arrive later are not focused. The footer submit
|
|
500
|
+
button is tied to the form with the `form` attribute — Enter-to-submit works in browsers, but
|
|
501
|
+
jsdom / user-event does not simulate it, so use `fireEvent.submit(form)` in tests.
|
|
474
502
|
|
|
475
503
|
### `QueryStateBoundary` evaluates JSX `children` even while loading
|
|
476
504
|
|
|
@@ -478,11 +506,23 @@ JSX children are built before the boundary decides what to show, so `data.items.
|
|
|
478
506
|
while `data` is still `undefined`. **Apply:** pass `children` as a function — `{() => <List
|
|
479
507
|
items={data.items} />}` — whenever it reads data that may not exist yet.
|
|
480
508
|
|
|
509
|
+
### `QueryStateBoundary` `isForbidden` and `hasData`
|
|
510
|
+
|
|
511
|
+
An `isForbidden` function is only called when `error` is set (never with `undefined`); `isForbidden:
|
|
512
|
+
true` shows the panel even with no error. `isLoading` + `hasData` keeps `children` visible during a
|
|
513
|
+
refetch, so pass `hasData` only when data is genuinely loaded — otherwise a first load shows a blank
|
|
514
|
+
region.
|
|
515
|
+
|
|
481
516
|
### `StatCard`'s delta colour follows `trend` vs `goodDirection`, not `trend` alone
|
|
482
517
|
|
|
483
518
|
Green when `trend === goodDirection`, red otherwise — a falling cost with `goodDirection="down"` is
|
|
484
519
|
green. Pass `onClick` for a clickable card (it renders its own `<button>`); never wrap it in one.
|
|
485
520
|
|
|
521
|
+
### `CircularProgress` `tone="info"` is the brand primary, not blue
|
|
522
|
+
|
|
523
|
+
Unlike `Tag` and `StatCard`, where `info` is blue, the default `tone="info"` here paints the brand
|
|
524
|
+
primary scale. `aria-label` is required — the ring has no visible caption.
|
|
525
|
+
|
|
486
526
|
### A clickable `TextInput` icon is a real button — give it a name
|
|
487
527
|
|
|
488
528
|
With `onRightIconclick`/`onLeftIconClick` the icon renders as a `<button>`. Without
|
|
@@ -562,6 +602,14 @@ accepted, typed, never called.
|
|
|
562
602
|
**Apply:** leave at least one column without a `node`, or put an explicit control inside a `node`
|
|
563
603
|
renderer instead of relying on row click.
|
|
564
604
|
|
|
605
|
+
### `Table.renderRowDetail` runs for every row on every render, and `expandedRows` ids match strictly
|
|
606
|
+
|
|
607
|
+
It decides whether each row gets a chevron, so an inline function re-renders every row (defeating
|
|
608
|
+
`TableRow`'s memo) — wrap it in `useCallback` / `useMemo`. `expandedRows` ids compare with `===`, so
|
|
609
|
+
`1` and `"1"` are different: keep the id type the same as `row.id`. It is not the older
|
|
610
|
+
`row.content` + `isContent` expander (chevron before the checkbox, index-based); both can coexist,
|
|
611
|
+
but only `row.content` still disables grouped virtualisation.
|
|
612
|
+
|
|
565
613
|
### `error` without `errorMessage` renders the literal word "Error" — and erases `helperText`
|
|
566
614
|
|
|
567
615
|
Every field that shows validation errors (`TextInput`, `InputTextArea`, `Select`, `MultiSelect`,
|
|
@@ -1013,6 +1061,18 @@ reach it.
|
|
|
1013
1061
|
**Apply:** don't depend on `onChange` firing for a repeat pick. There's no supported way to attach
|
|
1014
1062
|
test hooks or a ref to the trigger.
|
|
1015
1063
|
|
|
1064
|
+
### `OrgSwitcher display="full"` still squeezes the navbar title on phones — use `display="auto"`
|
|
1065
|
+
|
|
1066
|
+
Inside a `NavigationBar` `moreIcon` cluster, a full trigger needs about 88px (avatar + chevron) even
|
|
1067
|
+
when its label truncates to nothing, and the title is the first thing to give way. With a long org name
|
|
1068
|
+
the title is invisible on 320-375px bars, and a smaller `maxWidth` does not change that.
|
|
1069
|
+
|
|
1070
|
+
**Apply:** `display="auto"` (avatar + chevron below `md`, full from `md`) or `"avatar"`. In avatar mode
|
|
1071
|
+
the accessible name is `${ariaLabel}, ${org name}` and the name appears in a tooltip that wraps a
|
|
1072
|
+
`span` around the trigger, so don't pass the trigger a ref. `auto` falls back to `"full"` where
|
|
1073
|
+
`matchMedia` is missing (SSR, jsdom). `NavigationBar`'s `moreIcon` root is `min-width: 0`: give your
|
|
1074
|
+
own icon buttons `flexShrink={0}` and let any label truncate.
|
|
1075
|
+
|
|
1016
1076
|
### `UpgradeButton`'s gold icon accent silently falls back to the label color on low contrast
|
|
1017
1077
|
|
|
1018
1078
|
The gold accent is only used when it clears AA contrast against both gradient stops; a custom
|
|
@@ -1222,7 +1282,8 @@ Each mounted `Search` registers its own `window` Ctrl+K listener, so two on one
|
|
|
1222
1282
|
keypress. **Apply:** pass `shortcut={false}` on all but one. `showShortcutHint={false}` only hides
|
|
1223
1283
|
the hint; the listener stays.
|
|
1224
1284
|
|
|
1225
|
-
The same applies to a launcher: `NavLauncher` and `LauncherTrigger` bind no chord
|
|
1285
|
+
The same applies to a launcher: `NavLauncher` and `LauncherTrigger` bind no chord to *open* (once open,
|
|
1286
|
+
the launcher uses Cmd/Ctrl+K and `/` only to refocus its own field), and the library
|
|
1226
1287
|
reserves `mod+k` for the app's own palette. The app must pass `shortcut={false}` on every page
|
|
1227
1288
|
`Search`, or claim the chord in capture phase (`keydown` on `window` with `capture: true`) before
|
|
1228
1289
|
the `Search` listeners see it — otherwise the first mounted `Search` takes Ctrl+K.
|
|
@@ -1240,15 +1301,52 @@ the `Search` listeners see it — otherwise the first mounted `Search` takes Ctr
|
|
|
1240
1301
|
anchor is an `aria-hidden`, `tabIndex={-1}` overlay (so copy-link and mod-click work) and pin / ↗
|
|
1241
1302
|
are `aria-hidden` siblings. Screen readers use Enter / mod+Enter. Do not nest links inside the
|
|
1242
1303
|
option — axe `nested-interactive` fails.
|
|
1243
|
-
- **The apps section decides "compact" with `matchMedia("(min-width: 48em)")
|
|
1244
|
-
|
|
1245
|
-
|
|
1304
|
+
- **The apps section decides "compact" with `matchMedia("(min-width: 48em)")`, the layout with
|
|
1305
|
+
`(min-width: 62em)`** (`lg`). Both fall back to desktop (apps hidden, two panes) where `matchMedia`
|
|
1306
|
+
is missing (SSR, jsdom), so tests must stub both to see the apps section or the tab bar.
|
|
1307
|
+
- **Below `lg` only the selected tab's panel is mounted.** Pinned / Frequently visited / Recent
|
|
1308
|
+
(Quick access tab) and the apps list (Apps tab) are not in the DOM until their tab is clicked; the
|
|
1309
|
+
Quick access tab is omitted when it would be empty, and a single remaining tab hides the bar. The
|
|
1310
|
+
search view is different: no tab bar, stacked results plus the Quick access rail. A phone or tablet
|
|
1311
|
+
consumer test that looks for an app or a pin must click the tab first.
|
|
1312
|
+
- **`P` on a focused NavLauncher row pins it.** Any other printable key pressed on a row, tab or
|
|
1313
|
+
button (not Space, no modifier) is appended to the query and focuses the field; in the field `P`
|
|
1314
|
+
types the letter.
|
|
1315
|
+
- **Esc is handled by the launcher, not the Drawer** (`closeOnEsc` is off): it clears a non-empty
|
|
1316
|
+
query and keeps focus in the field, and closes only when the query is empty. The keydown is stopped inside
|
|
1317
|
+
the launcher, so a consumer `Escape` listener on `document` never sees it.
|
|
1318
|
+
- **`headerActions` text joins the dialog's accessible name** (header content becomes part of it,
|
|
1319
|
+
as the search value already does: "Menu leave"). Use icon-only buttons with an `aria-label`.
|
|
1320
|
+
- **The result count is `aria-hidden`; the announcement is a separate visually hidden `aria-live="polite"`
|
|
1321
|
+
node, deliberately not `role="status"`**, so `getByRole("status")` still matches only the no-results
|
|
1322
|
+
message and the loading skeleton.
|
|
1323
|
+
- **Left/Right/Home/End in the search field edit the caret** until a result has been navigated by
|
|
1324
|
+
arrow/Page keys (→ at the end of the text enters the grid); any change to the query hands them back
|
|
1325
|
+
to the caret. Do not "fix" this by binding them to results.
|
|
1246
1326
|
- **`NavLauncher` `suggestions` empty falls back to `noResultsHint`** instead of showing chips.
|
|
1247
1327
|
- **`PinToggle` / `NewTabLink` are opacity 0 until row hover or focus-within**, so a consumer's own row must carry `data-group` (or `role="group"`) to reveal them.
|
|
1248
1328
|
- **`LauncherLabels.screensGroup` is unused** by search now (sections replaced the flat group); kept so label objects still type-check.
|
|
1329
|
+
- **`LauncherLabels` gained required keys** (`hintClickNewTab`, `newTabTip`, `resultsCount`, `tabsLabel`,
|
|
1330
|
+
`tabMenu`, `tabQuickAccess`, `tabApps`). The `labels` prop is `Partial<LauncherLabels>` and is fine;
|
|
1331
|
+
a consumer that annotates a *full* `LauncherLabels` object fails to compile.
|
|
1332
|
+
- **`openShortcutHint` and `labels.hintOpenMenu` are deprecated no-ops:** the open chord is no longer
|
|
1333
|
+
shown in the footer. Drop them; the footer's hints are arrows, ENTER, ⌘ ENTER, ⌘ + click, P, ESC.
|
|
1334
|
+
- **`apps` is data-driven.** Gate by plan by changing the array or setting `locked` / `lockedReason`;
|
|
1335
|
+
keep `id`s stable (hues and `key` derive from them), and pass `color` on an app whose mark must not
|
|
1336
|
+
shift when siblings are added or removed. Locked rows are not links, not focusable, `aria-disabled`,
|
|
1337
|
+
tooltip = `lockedReason`. Pass `appsVisibility="always"` to show them above `md`.
|
|
1338
|
+
- **Never use `:has()` in an emotion style.** jsdom's (nwsapi) `getComputedStyle` can throw ("not a valid selector") and takes every consumer test that opens a
|
|
1339
|
+
Drawer down with it. The launcher marks row focus with a `data-row-focus` attribute instead. If you
|
|
1340
|
+
hit that error in your own tests on a newer jsdom, report it with the selector.
|
|
1341
|
+
- **Launcher rows are CSS containers** (`container-type: inline-size`, name `launcher-row`); jsdom
|
|
1342
|
+
drops `@container` rules, so tests assert the style objects (`titleInset`, `WIDE_ROW`), not the
|
|
1343
|
+
stylesheet.
|
|
1344
|
+
- **A search row without `href` (an action with `run`) cannot open in a new tab:** any click, with or
|
|
1345
|
+
without Cmd/Ctrl, runs it and closes the launcher, same as Enter.
|
|
1249
1346
|
- **`LauncherTrigger` does not bind its shortcut**; `shortcut={null}` removes the hint and
|
|
1250
1347
|
`aria-keyshortcuts`. An explicit `aria-label` hides the visible label from assistive tech, so keep
|
|
1251
|
-
the visible text inside the label (WCAG 2.5.3).
|
|
1348
|
+
the visible text inside the label (WCAG 2.5.3). `display="icon"` has no visible text: the label is
|
|
1349
|
+
its accessible name and tooltip.
|
|
1252
1350
|
|
|
1253
1351
|
### `Header`'s desktop submenus open on hover only — no keyboard or click trigger
|
|
1254
1352
|
|
|
@@ -1316,16 +1414,56 @@ error, no warning, `scrollTop` on a non-scrolling parent just stays `0` forever.
|
|
|
1316
1414
|
**Apply:** `ScrollToTop`'s JSX parent must be the actual scroll container, or pass `useWindow` when
|
|
1317
1415
|
the page itself scrolls.
|
|
1318
1416
|
|
|
1319
|
-
### `ProfilePhotoViewer`'s PDF detection breaks the moment a file is picked, and `photoUrl`
|
|
1417
|
+
### `ProfilePhotoViewer`'s PDF detection breaks the moment a file is picked, and a new `photoUrl` discards the preview
|
|
1320
1418
|
|
|
1321
1419
|
PDF detection is a literal `.endsWith(".pdf")` check — picking a file through the built-in editor
|
|
1322
1420
|
replaces the value with a base64 data URI that never ends in `.pdf`, so a freshly-picked PDF renders
|
|
1323
1421
|
as a broken image instead of the intended file badge (only the original URL prop is detected
|
|
1324
|
-
correctly). Separately, `photoUrl`
|
|
1325
|
-
|
|
1422
|
+
correctly). Separately, a changed `photoUrl` re-syncs the component and replaces any locally picked
|
|
1423
|
+
preview (a same-URL re-render keeps it), so a parent that updates `photoUrl` before the upload has
|
|
1424
|
+
finished makes the preview vanish mid-upload.
|
|
1326
1425
|
|
|
1327
1426
|
**Apply:** don't use the built-in edit flow for PDF documents; detect kind from `File.type` in your
|
|
1328
|
-
own handler instead.
|
|
1427
|
+
own handler instead. Update `photoUrl` only once the upload has finished. `key={photoUrl}` is no
|
|
1428
|
+
longer needed.
|
|
1429
|
+
|
|
1430
|
+
### `Tabs` link mode needs `href` on every item, and has no default current item
|
|
1431
|
+
|
|
1432
|
+
One item without `href` makes the whole thing a normal tablist and `href` is ignored everywhere (a
|
|
1433
|
+
dev-only `console.warn` is the only signal). Button tabs select the first enabled item when `value`
|
|
1434
|
+
is missing; link tabs mark nothing `aria-current` until you pass `value`. A plain left-click calls
|
|
1435
|
+
`onChange` but never `preventDefault`s; modifier and middle clicks only open the link. `isDisabled`
|
|
1436
|
+
link items render as a non-link `span`.
|
|
1437
|
+
|
|
1438
|
+
**Apply:** `Tabs items=[{ id, label, href }] linkAs={RouterLink} value={currentId}` with `value`
|
|
1439
|
+
derived from the route, and never hand-build `Box as={Link}` tabs.
|
|
1440
|
+
|
|
1441
|
+
### `BulkSelectionBar` renders nothing at `selectedCount` 0, and `sticky` needs a scroll ancestor
|
|
1442
|
+
|
|
1443
|
+
With no persistent live region it announces count changes itself (the node mounts empty and fills in
|
|
1444
|
+
an effect), so don't also announce from your own region. `placement="sticky"` pins only inside an
|
|
1445
|
+
ancestor scroll container — an ancestor with `overflow: hidden` silently disables it. Combining it
|
|
1446
|
+
with `Table`'s `headerActions` gives two action sets for one selection.
|
|
1447
|
+
|
|
1448
|
+
### `SectionRail` chooses rail vs `Select` with `matchMedia` at render time
|
|
1449
|
+
|
|
1450
|
+
It renders the rail when `matchMedia` is missing (SSR). In the `Select` branch a dirty section's
|
|
1451
|
+
option reads `"Label (dirtyHint)"` — a native `<option>` cannot hold a dot.
|
|
1452
|
+
|
|
1453
|
+
### `CalendarGrid` is fully controlled, ignores time of day, and `renderDayCell` turns multi-day bars off
|
|
1454
|
+
|
|
1455
|
+
Without `onDateChange` / `onViewChange` the toolbar's Today/Prev/Next and the view switcher are
|
|
1456
|
+
hidden and arrow keys stop at the edge of the visible range, so the grid looks static — wire both.
|
|
1457
|
+
`start` / `end` are read as local calendar dates (`end` inclusive): a UTC-midnight `Date` shows the
|
|
1458
|
+
previous day in a negative-offset zone, so build date-only values with `new Date(y, m, d)`.
|
|
1459
|
+
`renderDayCell` replaces each cell's chips wholesale: a 3-day event is passed to it for all 3 days
|
|
1460
|
+
but is no longer drawn as one bar, and `onEventClick` never fires from your own content.
|
|
1461
|
+
|
|
1462
|
+
### `AudioRecorder` needs a secure context, and `onRecorded` is not called after unmount
|
|
1463
|
+
|
|
1464
|
+
`getUserMedia` is undefined on plain http (except localhost), so the component shows its
|
|
1465
|
+
"unsupported" state there, not "permission denied". A parent that navigates away mid-recording gets
|
|
1466
|
+
no `onRecorded` call (the mic is released instead), and an empty recording never calls it either.
|
|
1329
1467
|
|
|
1330
1468
|
### `FilePreview` infers file kind from `fileName`, not `fileUrl`, and never resets zoom/rotation on its own
|
|
1331
1469
|
|
package/DESIGN-GUIDE.md
CHANGED
|
@@ -45,7 +45,7 @@ Borders are the one px value: `borderWidth="1px"`. Fixed widths (side panel, lis
|
|
|
45
45
|
| Section / card title | `Heading as="h2" fontSize="md" fontWeight={600} color={colors.header[900]}`, in `Card`'s `header` prop |
|
|
46
46
|
| Body | `Text fontSize="sm" color={colors.text[800]}` |
|
|
47
47
|
| Label / meta / helper | `Text fontSize="xs" color={colors.textMuted}` |
|
|
48
|
-
| KPI tile | `StatCard` — pass `trend` and `goodDirection`; the delta is green when the change is *good* (a shorter sales cycle is green) |
|
|
48
|
+
| KPI tile | `StatCard` — pass `trend` and `goodDirection`; the delta is green when the change is *good* (a shorter sales cycle is green); `iconTone` tints the icon chip (unset = brand primary) |
|
|
49
49
|
| Numbers in tables/columns | `sx={{ fontVariantNumeric: "tabular-nums" }}` so digits align; `Table` columns take `align: "end"` |
|
|
50
50
|
| Overline (rare) | `fontSize="xs" fontWeight={600} textTransform="uppercase" letterSpacing="0.06em"` |
|
|
51
51
|
|
|
@@ -69,7 +69,7 @@ stay light in dark mode.
|
|
|
69
69
|
| Secondary text, icons | `colors.textMuted` |
|
|
70
70
|
| Brand-colored text/link | `colors.accentText` — never `primary[500]` |
|
|
71
71
|
| Status text/icon | `colors.semanticText.success \| error \| warning \| info` |
|
|
72
|
-
| Status chip | `<Tag label="Won" colorScheme="green" />` (subtle, `sm` by default) — never a painted `Box`. Tone → `colorScheme`: success `green`, warning `orange`, error `red`, info `blue`, neutral `gray
|
|
72
|
+
| Status chip | `<Tag label="Won" colorScheme="green" />` (subtle, `sm` by default) — never a painted `Box`. Tone → `colorScheme`: success `green`, warning `orange`, error `red`, info `blue`, neutral `gray`. Quiet rows: `<Tag tone="success" display="dot" label=… />`; map an app's status vocabulary once with `<StatusTag status map label display />` |
|
|
73
73
|
| Selectable list row (inbox, picker) | `listRowBackgrounds(colors)` for hover/selected + `sx={focusVisibleStyles(colors)}`; pad the list `p={2}` so the ring isn't clipped |
|
|
74
74
|
| Any filled surface you paint | `resolveFilledSurface(colors.<scale>, colors)` → `{ fill, color }` |
|
|
75
75
|
| Icon tint chip, avatar tint | `bg={colors.primary.opacity[8]}` + `color={colors.accentText}` (`opacity` exists on `primary`/`secondary`/`tertiary` only) |
|
|
@@ -154,6 +154,12 @@ sections are `Card` with the title in `header`.
|
|
|
154
154
|
| A person: avatar + name (+ role/email, presence, remove) | `PersonChip` (`orientation="vertical"` for a stacked profile block) |
|
|
155
155
|
| An avatar on its own, or a stack of them | `Avatar` / `AvatarGroup` — never pick the fill, it is derived from `name` |
|
|
156
156
|
| Unsaved-changes bar | `SaveBar isOpen={isDirty} onSave onDiscard isSaving` |
|
|
157
|
+
| Create / edit a record in a side panel | `FormDrawer` (`sections`, `errors`, `isDirty`, `isSubmitting`) — don't hand-assemble Drawer + footer + discard confirm. A nested confirm dialog in any `Drawer` / `Modal` goes in `portalChildren` |
|
|
158
|
+
| Detail or sub-table under a table row | `Table renderRowDetail` (+ optional `expandedRows`) — don't put an Accordion outside the Table |
|
|
159
|
+
| Tabs that navigate between routes | `Tabs items=[{ id, label, href }] linkAs={RouterLink} value={currentId}` — never hand-build `Box as={Link}` tabs |
|
|
160
|
+
| Completion / capacity ring | `CircularProgress` (`ProgressBar` stays for linear) |
|
|
161
|
+
| Per-day activity (leave, tasks, shifts) | `CalendarGrid` with `events` mapped from your data — don't hand-roll a month grid |
|
|
162
|
+
| Voice note capture | `AudioRecorder`; `MediaAudioPlayer` for playback |
|
|
157
163
|
| Previewing an attachment (image, video, audio, PDF, Office, text, archive) | `MediaViewer` (`items`, `isOpen`, `onClose`) — omit `index` and it owns navigation itself |
|
|
158
164
|
| Notification tray | `NotificationBell` (badge, needs `aria-label`) opening a `NotificationPanel` (zones, filters, all four states) |
|
|
159
165
|
| Confirm that needs the name typed to arm it | `ConfirmDialog confirmationText="delete-me"` — `onConfirm` does **not** close; call `onClose` yourself |
|
|
@@ -174,18 +180,31 @@ scrim `Box`. `Loading` is the older API and stays on the heavier house scrim by
|
|
|
174
180
|
unknown, `linkAs` for the router link, and `extraResults` + `isSearching` for remote (entity) results.
|
|
175
181
|
- Open the launcher with `<NavigationBar leading={<LauncherTrigger onClick isOpen />} />` — never inside
|
|
176
182
|
`navigationBarText.text`. Neither component binds the chord: the app owns the `mod+k` listener
|
|
177
|
-
(capture phase) and passes `shortcut={false}` to any page `Search`.
|
|
183
|
+
(capture phase) and passes `shortcut={false}` to any page `Search`. Bind it to *open* the launcher only:
|
|
184
|
+
once open, the launcher refocuses its own field on Cmd/Ctrl+K and `/`, and shows no open-chord hint
|
|
185
|
+
(`openShortcutHint` is deprecated).
|
|
178
186
|
- Pins live in the app (server or local). Pass them to `NavLauncher` (`pinnedIds`, `onTogglePin`) *and*
|
|
179
187
|
to `SideBar pinnedItems` + `onUnpin` so the rail shows what the launcher pinned. Persist collapse with
|
|
180
188
|
`useSidebarPrefs`, and pass `autoCollapse={false}` if the app, not the rail, decides.
|
|
189
|
+
- Replacing a 9-dot app switcher in a nav bar: `<LauncherTrigger display="icon" />` (36px) in `moreIcon`;
|
|
190
|
+
keep the chip variant (`leading`) where there is room.
|
|
191
|
+
- In a phone-width app bar use `<OrgSwitcher display="auto" />` (avatar below `md`) next to the bell,
|
|
192
|
+
`LauncherTrigger display="icon"` and the avatar menu; keep every icon control 36px or less and
|
|
193
|
+
`flexShrink={0}`. The title truncates first.
|
|
194
|
+
- Below `lg` the launcher is a Menu / Quick access / Apps tab bar over one panel (search stays a single
|
|
195
|
+
stacked list), so design for phone and tablet with that structure, not a long scroll. Pass
|
|
196
|
+
`headerActions` for the app's own icon button (help, customise) with an `aria-label`.
|
|
197
|
+
- Show the suite's apps with `appsVisibility="always"` and drive plan gating by editing `apps` /
|
|
198
|
+
`locked`.
|
|
181
199
|
- Show shortcut hints with `KbdShortcut`, never a hand-styled `<kbd>`.
|
|
182
200
|
|
|
183
201
|
## 7. Every page has four states
|
|
184
202
|
|
|
185
203
|
Design all four, not just the happy path.
|
|
186
204
|
|
|
187
|
-
`QueryStateBoundary` picks one per content region (loading > error > empty > data). Pass
|
|
188
|
-
as a function when it reads data that may still be undefined.
|
|
205
|
+
`QueryStateBoundary` picks one per content region (loading > no access > error > empty > data). Pass
|
|
206
|
+
`children` as a function when it reads data that may still be undefined. `isForbidden` shows the
|
|
207
|
+
no-access panel between loading and error; `hasData` keeps content on screen during a refetch.
|
|
189
208
|
|
|
190
209
|
| State | Render |
|
|
191
210
|
| --- | --- |
|
|
@@ -228,9 +247,9 @@ not shipped in this package). Start from the closest one:
|
|
|
228
247
|
| Screen you're building | Reference | Shows |
|
|
229
248
|
| --- | --- | --- |
|
|
230
249
|
| Dashboard / overview | `dashboard.tsx` | `StatCard` row, chart `Card`, stage list with `ProgressBar`, task list, `QueryStateBoundary` |
|
|
231
|
-
| List of records | `records.tsx` | saved views, `FilterBar`, `Table` (`isGlobalSearch={false}`, `Tag` cells),
|
|
250
|
+
| List of records | `records.tsx` | saved views, `FilterBar`, `Table` (`isGlobalSearch={false}`, `Tag` cells, row selection), `BulkSelectionBar` (count, facts, ranked actions, clear), `AlertDialog tone="danger"`, create `Drawer` |
|
|
232
251
|
| One record | `recordDetail.tsx` | `PageHeader` with breadcrumbs/avatar/meta, `Tabs` (declarative `items` with `count` badges), `DescriptionList` (grid + inline side panel), `Timeline` (per-event `icon`/`description`), `ErrorState` + back |
|
|
233
|
-
| Settings / long form | `settings.tsx` |
|
|
252
|
+
| Settings / long form | `settings.tsx` | `SectionRail` (rail from `lg`, `Select` below, dirty dots), `SettingsSection` + `SettingRow`, `SaveBar`, danger zone |
|
|
234
253
|
| Board | `pipeline.tsx` | `KanbanBoard` with custom cards and column totals (`height`/`maxH` to size it) |
|
|
235
254
|
| Inbox / master–detail | `inbox.tsx` | selectable list rows, conversation pane, composer, mobile list↔detail |
|
|
236
255
|
| 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` |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AudioRecorderProps } from "./AudioRecorderProps";
|
|
2
|
+
export declare const formatElapsed: (ms: number) => string;
|
|
3
|
+
export declare const recordingFileName: (mime: string, now: number) => {
|
|
4
|
+
type: string;
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
declare const AudioRecorder: ({ onRecorded, maxDurationSec, isDisabled, labels }: AudioRecorderProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default AudioRecorder;
|