pixelize-design-library 2.4.2-beta.58 → 2.4.2-beta.60

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 (244) hide show
  1. package/CHANGELOG.md +149 -5
  2. package/COMPONENT-GOTCHAS.md +205 -18
  3. package/DESIGN-GUIDE.md +43 -9
  4. package/dist/Components/AppSwitcher/AppSwitcher.js +5 -1
  5. package/dist/Components/AppSwitcher/AppSwitcherProps.d.ts +2 -0
  6. package/dist/Components/AppSwitcher/AppSwitcherProps.js +7 -4
  7. package/dist/Components/AudioRecorder/AudioRecorder.d.ts +8 -0
  8. package/dist/Components/AudioRecorder/AudioRecorder.js +169 -0
  9. package/dist/Components/AudioRecorder/AudioRecorderProps.d.ts +20 -0
  10. package/dist/Components/AudioRecorder/AudioRecorderProps.js +2 -0
  11. package/dist/Components/Breadcrumbs/Breadcrumbs.d.ts +1 -1
  12. package/dist/Components/Breadcrumbs/Breadcrumbs.js +33 -10
  13. package/dist/Components/Breadcrumbs/BreadcrumbsProps.d.ts +16 -2
  14. package/dist/Components/BulkSelectionBar/BulkSelectionBar.d.ts +3 -0
  15. package/dist/Components/BulkSelectionBar/BulkSelectionBar.js +43 -0
  16. package/dist/Components/BulkSelectionBar/BulkSelectionBarProps.d.ts +35 -0
  17. package/dist/Components/BulkSelectionBar/BulkSelectionBarProps.js +2 -0
  18. package/dist/Components/BulkSelectionBar/index.d.ts +2 -0
  19. package/dist/Components/BulkSelectionBar/index.js +8 -0
  20. package/dist/Components/CalendarGrid/CalendarGrid.d.ts +3 -0
  21. package/dist/Components/CalendarGrid/CalendarGrid.js +173 -0
  22. package/dist/Components/CalendarGrid/CalendarGridProps.d.ts +54 -0
  23. package/dist/Components/CalendarGrid/CalendarGridProps.js +2 -0
  24. package/dist/Components/CalendarGrid/calendarMath.d.ts +49 -0
  25. package/dist/Components/CalendarGrid/calendarMath.js +178 -0
  26. package/dist/Components/Card/Card.d.ts +3 -1
  27. package/dist/Components/Card/Card.js +12 -2
  28. package/dist/Components/Card/CardProps.d.ts +5 -1
  29. package/dist/Components/CircularProgress/CircularProgress.d.ts +4 -0
  30. package/dist/Components/CircularProgress/CircularProgress.js +39 -0
  31. package/dist/Components/CircularProgress/CircularProgressProps.d.ts +22 -0
  32. package/dist/Components/CircularProgress/CircularProgressProps.js +2 -0
  33. package/dist/Components/CircularProgress/arc.d.ts +12 -0
  34. package/dist/Components/CircularProgress/arc.js +16 -0
  35. package/dist/Components/Common/fieldStyles.d.ts +1 -0
  36. package/dist/Components/Common/fieldStyles.js +2 -1
  37. package/dist/Components/Drawer/Drawer.d.ts +1 -1
  38. package/dist/Components/Drawer/Drawer.js +4 -2
  39. package/dist/Components/Drawer/DrawerProps.d.ts +3 -1
  40. package/dist/Components/Editor/Editor.js +50 -2
  41. package/dist/Components/Editor/EditorFieldBar.d.ts +15 -0
  42. package/dist/Components/Editor/EditorFieldBar.js +19 -0
  43. package/dist/Components/Editor/EditorProps.d.ts +74 -1
  44. package/dist/Components/Editor/MakeDynamicDialog.d.ts +12 -0
  45. package/dist/Components/Editor/MakeDynamicDialog.js +59 -0
  46. package/dist/Components/Editor/editorLabels.d.ts +4 -0
  47. package/dist/Components/Editor/editorLabels.js +24 -0
  48. package/dist/Components/Editor/editorTokenStyles.d.ts +28 -0
  49. package/dist/Components/Editor/editorTokenStyles.js +30 -0
  50. package/dist/Components/Editor/editorTokens.d.ts +26 -0
  51. package/dist/Components/Editor/editorTokens.js +134 -0
  52. package/dist/Components/Editor/useEditorBridge.d.ts +40 -0
  53. package/dist/Components/Editor/useEditorBridge.js +141 -0
  54. package/dist/Components/EmptyState/EmptyState.js +2 -1
  55. package/dist/Components/EmptyState/EmptyStateProps.d.ts +2 -2
  56. package/dist/Components/FormDrawer/FormDrawer.d.ts +2 -0
  57. package/dist/Components/FormDrawer/FormDrawer.js +97 -0
  58. package/dist/Components/FormDrawer/FormDrawerProps.d.ts +55 -0
  59. package/dist/Components/FormDrawer/FormDrawerProps.js +2 -0
  60. package/dist/Components/Kbd/Kbd.d.ts +6 -0
  61. package/dist/Components/Kbd/Kbd.js +33 -0
  62. package/dist/Components/Kbd/KbdProps.d.ts +17 -0
  63. package/dist/Components/Kbd/KbdProps.js +2 -0
  64. package/dist/Components/LauncherTrigger/LauncherTrigger.d.ts +4 -0
  65. package/dist/Components/LauncherTrigger/LauncherTrigger.js +48 -0
  66. package/dist/Components/LauncherTrigger/LauncherTriggerProps.d.ts +18 -0
  67. package/dist/Components/LauncherTrigger/LauncherTriggerProps.js +2 -0
  68. package/dist/Components/Modal/Modal.d.ts +1 -1
  69. package/dist/Components/Modal/Modal.js +4 -2
  70. package/dist/Components/Modal/ModalProps.d.ts +3 -1
  71. package/dist/Components/Modal/warnDroppedSlotChildren.d.ts +2 -0
  72. package/dist/Components/Modal/warnDroppedSlotChildren.js +45 -0
  73. package/dist/Components/NavLauncher/NavLauncher.d.ts +3 -0
  74. package/dist/Components/NavLauncher/NavLauncher.js +452 -0
  75. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +187 -0
  76. package/dist/Components/NavLauncher/NavLauncherProps.js +2 -0
  77. package/dist/Components/NavLauncher/NewTabLink.d.ts +3 -0
  78. package/dist/Components/NavLauncher/NewTabLink.js +14 -0
  79. package/dist/Components/NavLauncher/PinToggle.d.ts +3 -0
  80. package/dist/Components/NavLauncher/PinToggle.js +21 -0
  81. package/dist/Components/NavLauncher/launcherParts.d.ts +152 -0
  82. package/dist/Components/NavLauncher/launcherParts.js +212 -0
  83. package/dist/Components/NavLauncher/match.d.ts +53 -0
  84. package/dist/Components/NavLauncher/match.js +178 -0
  85. package/dist/Components/NavLauncher/revealStyles.d.ts +4 -0
  86. package/dist/Components/NavLauncher/revealStyles.js +19 -0
  87. package/dist/Components/NavigationBar/NavigationBar.d.ts +1 -1
  88. package/dist/Components/NavigationBar/NavigationBar.js +2 -2
  89. package/dist/Components/NavigationBar/NavigationBarProps.d.ts +2 -0
  90. package/dist/Components/PageHeader/PageHeader.d.ts +1 -1
  91. package/dist/Components/PageHeader/PageHeader.js +9 -2
  92. package/dist/Components/PageHeader/PageHeaderProps.d.ts +8 -0
  93. package/dist/Components/PdfViewer/PdfViewer.d.ts +21 -4
  94. package/dist/Components/PdfViewer/PdfViewer.js +124 -3
  95. package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +5 -0
  96. package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewerProps.d.ts +1 -1
  97. package/dist/Components/QueryState/QueryStateBoundary.d.ts +2 -2
  98. package/dist/Components/QueryState/QueryStateBoundary.js +9 -3
  99. package/dist/Components/QueryState/QueryStateProps.d.ts +19 -2
  100. package/dist/Components/Search/Search.js +2 -1
  101. package/dist/Components/SectionRail/SectionRail.d.ts +3 -0
  102. package/dist/Components/SectionRail/SectionRail.js +49 -0
  103. package/dist/Components/SectionRail/SectionRailProps.d.ts +23 -0
  104. package/dist/Components/SectionRail/SectionRailProps.js +2 -0
  105. package/dist/Components/SectionRail/index.d.ts +2 -0
  106. package/dist/Components/SectionRail/index.js +8 -0
  107. package/dist/Components/SegmentedControl/SegmentedControl.js +9 -14
  108. package/dist/Components/SegmentedControl/segmentedInk.d.ts +0 -2
  109. package/dist/Components/SegmentedControl/segmentedInk.js +1 -5
  110. package/dist/Components/SideBar/SecondaryBar.js +4 -3
  111. package/dist/Components/SideBar/SideBar.d.ts +1 -1
  112. package/dist/Components/SideBar/SideBar.js +30 -9
  113. package/dist/Components/SideBar/SideBarProps.d.ts +32 -0
  114. package/dist/Components/SideBar/components/MenuItemBox.d.ts +6 -0
  115. package/dist/Components/SideBar/components/MenuItemBox.js +8 -4
  116. package/dist/Components/SideBar/components/MenuPopoverContent.js +3 -2
  117. package/dist/Components/SideBar/components/OtherApps.js +2 -1
  118. package/dist/Components/SideBar/components/PinnedSection.d.ts +17 -0
  119. package/dist/Components/SideBar/components/PinnedSection.js +98 -0
  120. package/dist/Components/SideBar/sidebarFocus.d.ts +23 -0
  121. package/dist/Components/SideBar/sidebarFocus.js +20 -0
  122. package/dist/Components/StageProgress/StageProgress.js +2 -2
  123. package/dist/Components/StageProgress/StageProgressProps.d.ts +2 -0
  124. package/dist/Components/StageProgress/StepperStage.js +5 -2
  125. package/dist/Components/StatCard/StatCard.d.ts +1 -1
  126. package/dist/Components/StatCard/StatCard.js +6 -2
  127. package/dist/Components/StatCard/StatCardProps.d.ts +3 -0
  128. package/dist/Components/StatusTag/StatusTag.d.ts +4 -0
  129. package/dist/Components/StatusTag/StatusTag.js +24 -0
  130. package/dist/Components/StatusTag/StatusTagProps.d.ts +20 -0
  131. package/dist/Components/StatusTag/StatusTagProps.js +2 -0
  132. package/dist/Components/Table/Table.d.ts +1 -1
  133. package/dist/Components/Table/Table.js +6 -2
  134. package/dist/Components/Table/TableProps.d.ts +20 -0
  135. package/dist/Components/Table/components/TableBody.d.ts +1 -1
  136. package/dist/Components/Table/components/TableBody.js +43 -18
  137. package/dist/Components/Table/components/TableHeader.d.ts +1 -1
  138. package/dist/Components/Table/components/TableHeader.js +7 -5
  139. package/dist/Components/Table/components/rowDetail.d.ts +32 -0
  140. package/dist/Components/Table/components/rowDetail.js +20 -0
  141. package/dist/Components/Table/hooks/useRowExpansion.d.ts +7 -0
  142. package/dist/Components/Table/hooks/useRowExpansion.js +27 -0
  143. package/dist/Components/Tabs/Tabs.d.ts +1 -1
  144. package/dist/Components/Tabs/Tabs.js +15 -2
  145. package/dist/Components/Tabs/TabsProps.d.ts +6 -2
  146. package/dist/Components/Tabs/components/Tab.js +4 -33
  147. package/dist/Components/Tabs/components/TabLinkList.d.ts +7 -0
  148. package/dist/Components/Tabs/components/TabLinkList.js +34 -0
  149. package/dist/Components/Tabs/useTabAppearance.d.ts +17 -0
  150. package/dist/Components/Tabs/useTabAppearance.js +67 -0
  151. package/dist/Components/Tag/Tag.js +11 -1
  152. package/dist/Components/Tag/TagProps.d.ts +4 -0
  153. package/dist/Components/Tag/tagDot.d.ts +3 -0
  154. package/dist/Components/Tag/tagDot.js +21 -0
  155. package/dist/Components/WizardDialog/WizardDialog.d.ts +9 -0
  156. package/dist/Components/WizardDialog/WizardDialog.js +102 -0
  157. package/dist/Components/WizardDialog/WizardDialogProps.d.ts +48 -0
  158. package/dist/Components/WizardDialog/WizardDialogProps.js +2 -0
  159. package/dist/Components/WizardDialog/useWizardFlow.d.ts +22 -0
  160. package/dist/Components/WizardDialog/useWizardFlow.js +47 -0
  161. package/dist/Components/WizardDialog/wizardSteps.d.ts +31 -0
  162. package/dist/Components/WizardDialog/wizardSteps.js +40 -0
  163. package/dist/esm/Components/AppSwitcher/AppSwitcher.js +2 -1
  164. package/dist/esm/Components/AppSwitcher/AppSwitcherProps.js +5 -3
  165. package/dist/esm/Components/AudioRecorder/AudioRecorder.js +161 -0
  166. package/dist/esm/Components/AudioRecorder/AudioRecorderProps.js +1 -0
  167. package/dist/esm/Components/Breadcrumbs/Breadcrumbs.js +35 -12
  168. package/dist/esm/Components/BulkSelectionBar/BulkSelectionBar.js +38 -0
  169. package/dist/esm/Components/BulkSelectionBar/BulkSelectionBarProps.js +1 -0
  170. package/dist/esm/Components/BulkSelectionBar/index.js +1 -0
  171. package/dist/esm/Components/CalendarGrid/CalendarGrid.js +168 -0
  172. package/dist/esm/Components/CalendarGrid/CalendarGridProps.js +1 -0
  173. package/dist/esm/Components/CalendarGrid/calendarMath.js +155 -0
  174. package/dist/esm/Components/Card/Card.js +12 -2
  175. package/dist/esm/Components/CircularProgress/CircularProgress.js +37 -0
  176. package/dist/esm/Components/CircularProgress/CircularProgressProps.js +1 -0
  177. package/dist/esm/Components/CircularProgress/arc.js +11 -0
  178. package/dist/esm/Components/Common/fieldStyles.js +2 -1
  179. package/dist/esm/Components/Drawer/Drawer.js +4 -2
  180. package/dist/esm/Components/Editor/Editor.js +48 -3
  181. package/dist/esm/Components/Editor/EditorFieldBar.js +13 -0
  182. package/dist/esm/Components/Editor/MakeDynamicDialog.js +20 -0
  183. package/dist/esm/Components/Editor/editorLabels.js +20 -0
  184. package/dist/esm/Components/Editor/editorTokenStyles.js +25 -0
  185. package/dist/esm/Components/Editor/editorTokens.js +121 -0
  186. package/dist/esm/Components/Editor/useEditorBridge.js +138 -0
  187. package/dist/esm/Components/EmptyState/EmptyState.js +2 -1
  188. package/dist/esm/Components/FormDrawer/FormDrawer.js +58 -0
  189. package/dist/esm/Components/FormDrawer/FormDrawerProps.js +1 -0
  190. package/dist/esm/Components/Kbd/Kbd.js +28 -0
  191. package/dist/esm/Components/Kbd/KbdProps.js +1 -0
  192. package/dist/esm/Components/LauncherTrigger/LauncherTrigger.js +41 -0
  193. package/dist/esm/Components/LauncherTrigger/LauncherTriggerProps.js +1 -0
  194. package/dist/esm/Components/Modal/Modal.js +4 -2
  195. package/dist/esm/Components/Modal/warnDroppedSlotChildren.js +38 -0
  196. package/dist/esm/Components/NavLauncher/NavLauncher.js +413 -0
  197. package/dist/esm/Components/NavLauncher/NavLauncherProps.js +1 -0
  198. package/dist/esm/Components/NavLauncher/NewTabLink.js +11 -0
  199. package/dist/esm/Components/NavLauncher/PinToggle.js +18 -0
  200. package/dist/esm/Components/NavLauncher/launcherParts.js +187 -0
  201. package/dist/esm/Components/NavLauncher/match.js +172 -0
  202. package/dist/esm/Components/NavLauncher/revealStyles.js +15 -0
  203. package/dist/esm/Components/NavigationBar/NavigationBar.js +3 -3
  204. package/dist/esm/Components/PageHeader/PageHeader.js +9 -2
  205. package/dist/esm/Components/PdfViewer/PdfViewer.js +123 -2
  206. package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +5 -0
  207. package/dist/esm/Components/QueryState/QueryStateBoundary.js +10 -4
  208. package/dist/esm/Components/Search/Search.js +3 -2
  209. package/dist/esm/Components/SectionRail/SectionRail.js +44 -0
  210. package/dist/esm/Components/SectionRail/SectionRailProps.js +1 -0
  211. package/dist/esm/Components/SectionRail/index.js +1 -0
  212. package/dist/esm/Components/SegmentedControl/SegmentedControl.js +11 -16
  213. package/dist/esm/Components/SegmentedControl/segmentedInk.js +0 -3
  214. package/dist/esm/Components/SideBar/SecondaryBar.js +4 -3
  215. package/dist/esm/Components/SideBar/SideBar.js +31 -10
  216. package/dist/esm/Components/SideBar/components/MenuItemBox.js +4 -2
  217. package/dist/esm/Components/SideBar/components/MenuPopoverContent.js +3 -2
  218. package/dist/esm/Components/SideBar/components/OtherApps.js +2 -1
  219. package/dist/esm/Components/SideBar/components/PinnedSection.js +92 -0
  220. package/dist/esm/Components/SideBar/sidebarFocus.js +15 -0
  221. package/dist/esm/Components/StageProgress/StageProgress.js +2 -2
  222. package/dist/esm/Components/StageProgress/StepperStage.js +5 -2
  223. package/dist/esm/Components/StatCard/StatCard.js +6 -2
  224. package/dist/esm/Components/StatusTag/StatusTag.js +19 -0
  225. package/dist/esm/Components/StatusTag/StatusTagProps.js +1 -0
  226. package/dist/esm/Components/Table/Table.js +7 -3
  227. package/dist/esm/Components/Table/components/TableBody.js +43 -18
  228. package/dist/esm/Components/Table/components/TableHeader.js +7 -5
  229. package/dist/esm/Components/Table/components/rowDetail.js +14 -0
  230. package/dist/esm/Components/Table/hooks/useRowExpansion.js +23 -0
  231. package/dist/esm/Components/Tabs/Tabs.js +15 -2
  232. package/dist/esm/Components/Tabs/components/Tab.js +4 -33
  233. package/dist/esm/Components/Tabs/components/TabLinkList.js +29 -0
  234. package/dist/esm/Components/Tabs/useTabAppearance.js +63 -0
  235. package/dist/esm/Components/Tag/Tag.js +12 -2
  236. package/dist/esm/Components/Tag/tagDot.js +17 -0
  237. package/dist/esm/Components/WizardDialog/WizardDialog.js +58 -0
  238. package/dist/esm/Components/WizardDialog/WizardDialogProps.js +1 -0
  239. package/dist/esm/Components/WizardDialog/useWizardFlow.js +44 -0
  240. package/dist/esm/Components/WizardDialog/wizardSteps.js +35 -0
  241. package/dist/esm/index.js +14 -0
  242. package/dist/index.d.ts +35 -6
  243. package/dist/index.js +38 -4
  244. package/package.json +1 -1
package/DESIGN-GUIDE.md CHANGED
@@ -29,7 +29,7 @@ Calm, dense, professional SaaS admin — Linear/Stripe-dashboard, not a landing
29
29
  | Page padding | `px={{ base: 4, md: 6 }} py={{ base: 4, md: 5 }}` |
30
30
  | Between page sections | `gap={5}` (a `Flex direction="column"`) |
31
31
  | Between cards in a grid | `gap={4}` |
32
- | Inside a card | `Card`'s own padding — don't add more; `bodyProps={{ p: 0 }}` for edge-to-edge content |
32
+ | Inside a card | `Card`'s own padding — don't add more; `padding="none"` (or `"2xs"`/`"xs"` for dense) instead of `sx={{ p }}`, `bodyProps={{ p: 0 }}` for edge-to-edge content |
33
33
  | Between form fields | `gap={4}` vertical, `gap={3}` side by side |
34
34
  | Inline clusters (tags, meta, icon + text) | `gap={1.5}`–`gap={2}` |
35
35
  | Page header → content | the section gap (`gap={5}`) — no extra margin |
@@ -41,11 +41,11 @@ Borders are the one px value: `borderWidth="1px"`. Fixed widths (side panel, lis
41
41
 
42
42
  | Role | Props |
43
43
  | --- | --- |
44
- | Page title + subtitle | `PageHeader` (`lg`/600 h1, `sm` muted subtitle) — don't hand-build it |
44
+ | Page title + subtitle | `PageHeader` (18px/600 h1 by default, `sm` muted subtitle; `titleSize="xl"` for a top-level page) — don't hand-build it |
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) |
@@ -139,17 +139,27 @@ the row title is the label: `<Text as="label" htmlFor={id}>` pointing at the fie
139
139
  ```
140
140
 
141
141
  `PageHeader` also takes `breadcrumbs`, `leading` (avatar), `titleAddon` (status `Tag`), `meta` (icon
142
- row) and `tabs` — a record page's whole header. KPI rows are a `SimpleGrid` of `StatCard`s; card
142
+ row) and `tabs` — a record page's whole header. Actions wrap under the title on their own when they do
143
+ not fit (`actionsLayout="auto"`), so don't hand-build a stacked variant. KPI rows are a `SimpleGrid` of `StatCard`s; card
143
144
  sections are `Card` with the title in `header`.
144
145
 
145
146
  | Pattern | Component — don't hand-build it |
146
147
  | --- | --- |
148
+ | 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 |
149
+ | Rich-text template with `${field}` placeholders | `Editor mergeFields={…}` — not a `Select` + caret tracking + dialog beside it; use its `label` / `helperText` / `errorMessage` |
150
+ | 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 |
147
151
  | Toolbar: search · filters · active-filter chips · count · actions | `FilterBar` (`search`, children, `activeFilters` + `onRemoveFilter`/`onClearAll`, `summary`, `actions`) |
148
152
  | Read-only label/value grid (record details, key facts) | `DescriptionList` (`items`, `columns`, `layout="inline"` for a side panel, `copyable`, `isLoading`) |
149
153
  | Settings row (title + description left, control right) | `SettingRow` inside a `SettingsSection` (`isDanger` for the danger zone, `action` for a header-right button) |
150
154
  | A person: avatar + name (+ role/email, presence, remove) | `PersonChip` (`orientation="vertical"` for a stacked profile block) |
151
155
  | An avatar on its own, or a stack of them | `Avatar` / `AvatarGroup` — never pick the fill, it is derived from `name` |
152
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 |
153
163
  | Previewing an attachment (image, video, audio, PDF, Office, text, archive) | `MediaViewer` (`items`, `isOpen`, `onClose`) — omit `index` and it owns navigation itself |
154
164
  | Notification tray | `NotificationBell` (badge, needs `aria-label`) opening a `NotificationPanel` (zones, filters, all four states) |
155
165
  | Confirm that needs the name typed to arm it | `ConfirmDialog confirmationText="delete-me"` — `onConfirm` does **not** close; call `onClose` yourself |
@@ -160,12 +170,33 @@ sections are `Card` with the title in `header`.
160
170
  scrim `Box`. `Loading` is the older API and stays on the heavier house scrim by default; pass
161
171
  `variant="dim"` to route it through `Backdrop`, or just use `Backdrop` directly.
162
172
 
173
+ ### Navigation shell
174
+
175
+ `SideBar` is the rail, `NavigationBar` the top bar, `NavLauncher` the full-height "go anywhere" panel.
176
+ **The app owns the registry, permissions and persistence**; the library owns the look and the keyboard.
177
+
178
+ - Keep one registry (modules → screens) and derive the rail, the launcher and route guards from it.
179
+ Filter by permission *before* passing `sections` to `NavLauncher`; use `isLoading` while access is
180
+ unknown, `linkAs` for the router link, and `extraResults` + `isSearching` for remote (entity) results.
181
+ - Open the launcher with `<NavigationBar leading={<LauncherTrigger onClick isOpen />} />` — never inside
182
+ `navigationBarText.text`. Neither component binds the chord: the app owns the `mod+k` listener
183
+ (capture phase) and passes `shortcut={false}` to any page `Search`.
184
+ - Pins live in the app (server or local). Pass them to `NavLauncher` (`pinnedIds`, `onTogglePin`) *and*
185
+ to `SideBar pinnedItems` + `onUnpin` so the rail shows what the launcher pinned. Persist collapse with
186
+ `useSidebarPrefs`, and pass `autoCollapse={false}` if the app, not the rail, decides.
187
+ - Replacing a 9-dot app switcher in a nav bar: `<LauncherTrigger display="icon" />` (36px) in `moreIcon`;
188
+ keep the chip variant (`leading`) where there is room.
189
+ - Show the suite's apps with `appsVisibility="always"` and drive plan gating by editing `apps` /
190
+ `locked`.
191
+ - Show shortcut hints with `KbdShortcut`, never a hand-styled `<kbd>`.
192
+
163
193
  ## 7. Every page has four states
164
194
 
165
195
  Design all four, not just the happy path.
166
196
 
167
- `QueryStateBoundary` picks one per content region (loading > error > empty > data). Pass `children`
168
- as a function when it reads data that may still be undefined.
197
+ `QueryStateBoundary` picks one per content region (loading > no access > error > empty > data). Pass
198
+ `children` as a function when it reads data that may still be undefined. `isForbidden` shows the
199
+ no-access panel between loading and error; `hasData` keeps content on screen during a refetch.
169
200
 
170
201
  | State | Render |
171
202
  | --- | --- |
@@ -208,11 +239,14 @@ not shipped in this package). Start from the closest one:
208
239
  | Screen you're building | Reference | Shows |
209
240
  | --- | --- | --- |
210
241
  | Dashboard / overview | `dashboard.tsx` | `StatCard` row, chart `Card`, stage list with `ProgressBar`, task list, `QueryStateBoundary` |
211
- | List of records | `records.tsx` | saved views, `FilterBar`, `Table` (`isGlobalSearch={false}`, `Tag` cells), bulk actions, `AlertDialog tone="danger"`, create `Drawer` |
242
+ | 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` |
212
243
  | 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 |
213
- | Settings / long form | `settings.tsx` | section nav, `SettingsSection` + `SettingRow`, `SaveBar`, danger zone |
244
+ | Settings / long form | `settings.tsx` | `SectionRail` (rail from `lg`, `Select` below, dirty dots), `SettingsSection` + `SettingRow`, `SaveBar`, danger zone |
214
245
  | Board | `pipeline.tsx` | `KanbanBoard` with custom cards and column totals (`height`/`maxH` to size it) |
215
246
  | Inbox / master–detail | `inbox.tsx` | selectable list rows, conversation pane, composer, mobile list↔detail |
247
+ | 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` |
248
+ | 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` |
249
+ | 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 |
216
250
  | 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 |
217
251
 
218
252
  ## 10. Shop screens (e-commerce)
@@ -32,6 +32,9 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  const jsx_runtime_1 = require("react/jsx-runtime");
37
40
  const react_1 = require("react");
@@ -43,6 +46,7 @@ const accentText_1 = require("../../Theme/tokens/builders/accentText");
43
46
  const outlineRung_1 = require("../../Theme/tokens/builders/outlineRung");
44
47
  const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
45
48
  const useExclusiveOverlay_1 = require("../../Hooks/useExclusiveOverlay");
49
+ const Kbd_1 = __importDefault(require("../Kbd/Kbd"));
46
50
  const AppMark_1 = __importStar(require("./AppMark"));
47
51
  const AppSwitcherProps_1 = require("./AppSwitcherProps");
48
52
  const useAppSwitcherShortcut_1 = require("./useAppSwitcherShortcut");
@@ -225,7 +229,7 @@ const AppSwitcher = ({ apps, currentAppId, onSelect, onOpenChange, isSearchable,
225
229
  return;
226
230
  }
227
231
  handlePanelKeyDown(e);
228
- }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Grip, { size: s.icon + 1 }) }) }), (0, jsx_runtime_1.jsx)(react_2.PopoverContent, { w: width, maxW: "calc(100vw - 1.5rem)", textAlign: "start", onKeyDown: handlePanelKeyDown, bg: theme.colors.surfaceRaised, borderRadius: "xl", overflow: "hidden", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}`, _focus: { outline: "none", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}` }, children: (0, jsx_runtime_1.jsxs)(react_2.PopoverBody, { p: 0, children: [(0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, px: 3, pt: 3, pb: showSearch ? 2 : 1, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.6875rem", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: theme.colors.textMuted, children: HEADING }), (0, jsx_runtime_1.jsx)(react_2.Box, { flex: "1" }), hint && ((0, jsx_runtime_1.jsx)(react_2.Text, { as: "kbd", fontSize: "0.6875rem", fontFamily: "inherit", color: theme.colors.textMuted, border: "1px solid", borderRadius: "full", px: 1.5, py: 0.5, children: hint }))] }), showSearch && ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 3, pb: 2, children: (0, jsx_runtime_1.jsxs)(react_2.InputGroup, { size: "sm", children: [(0, jsx_runtime_1.jsx)(react_2.InputLeftElement, { pointerEvents: "none", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, color: theme.colors.textMuted }) }), (0, jsx_runtime_1.jsx)(react_2.Input, { ref: searchRef, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps", borderRadius: "lg", "aria-label": "Search apps", onKeyDown: (e) => {
232
+ }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Grip, { size: s.icon + 1 }) }) }), (0, jsx_runtime_1.jsx)(react_2.PopoverContent, { w: width, maxW: "calc(100vw - 1.5rem)", textAlign: "start", onKeyDown: handlePanelKeyDown, bg: theme.colors.surfaceRaised, borderRadius: "xl", overflow: "hidden", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}`, _focus: { outline: "none", boxShadow: `0 10px 30px ${theme.colors.boxShadow.default}` }, children: (0, jsx_runtime_1.jsxs)(react_2.PopoverBody, { p: 0, children: [(0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, px: 3, pt: 3, pb: showSearch ? 2 : 1, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "0.6875rem", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: theme.colors.textMuted, children: HEADING }), (0, jsx_runtime_1.jsx)(react_2.Box, { flex: "1" }), hint && (0, jsx_runtime_1.jsx)(Kbd_1.default, { size: "xs", children: hint })] }), showSearch && ((0, jsx_runtime_1.jsx)(react_2.Box, { px: 3, pb: 2, children: (0, jsx_runtime_1.jsxs)(react_2.InputGroup, { size: "sm", children: [(0, jsx_runtime_1.jsx)(react_2.InputLeftElement, { pointerEvents: "none", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, color: theme.colors.textMuted }) }), (0, jsx_runtime_1.jsx)(react_2.Input, { ref: searchRef, value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps", borderRadius: "lg", "aria-label": "Search apps", onKeyDown: (e) => {
229
233
  if (e.key === "ArrowDown" && visible.length > 0) {
230
234
  e.preventDefault();
231
235
  focusItem(0);
@@ -52,6 +52,8 @@ export declare const filterApps: (apps: AppOption[], query: string) => AppOption
52
52
  export type ShortcutEvent = Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">;
53
53
  /** "mod" is Cmd on Apple platforms and Ctrl everywhere else. */
54
54
  export declare const matchesShortcut: (event: ShortcutEvent, shortcut: string, isApple?: boolean) => boolean;
55
+ /** One label per key in a chord, so a named key such as `enter` stays a single cap. */
56
+ export declare const shortcutKeys: (shortcut: string, isApple?: boolean) => string[];
55
57
  export declare const formatShortcut: (shortcut: string, isApple?: boolean) => string;
56
58
  /** Input to `buildSuiteApps`: an `AppOption` keyed by its subdomain label, with `url` optional since it can be derived. */
57
59
  export interface SuiteAppSource extends Omit<AppOption, "url"> {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildSuiteApps = exports.formatShortcut = exports.matchesShortcut = exports.filterApps = exports.appInitials = void 0;
3
+ exports.buildSuiteApps = exports.formatShortcut = exports.shortcutKeys = exports.matchesShortcut = exports.filterApps = exports.appInitials = void 0;
4
4
  const NON_WORD = /[^\p{L}\p{N}]+/u;
5
5
  /** Up to two initials, so a mark stays legible without an icon. */
6
6
  const appInitials = (name) => {
@@ -48,12 +48,15 @@ const matchesShortcut = (event, shortcut, isApple = typeof navigator !== "undefi
48
48
  exports.matchesShortcut = matchesShortcut;
49
49
  const APPLE_SYMBOLS = { mod: "⌘", cmd: "⌘", meta: "⌘", ctrl: "⌃", shift: "⇧", alt: "⌥" };
50
50
  const OTHER_LABELS = { mod: "Ctrl", cmd: "Win", meta: "Win", ctrl: "Ctrl", shift: "Shift", alt: "Alt" };
51
- const formatShortcut = (shortcut, isApple = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform)) => {
51
+ const isAppleNow = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
52
+ /** One label per key in a chord, so a named key such as `enter` stays a single cap. */
53
+ const shortcutKeys = (shortcut, isApple = isAppleNow()) => {
52
54
  const parts = shortcut.toLowerCase().split("+").map((p) => p.trim()).filter(Boolean);
53
55
  const table = isApple ? APPLE_SYMBOLS : OTHER_LABELS;
54
- const rendered = parts.map((p, i) => { var _a; return i === parts.length - 1 ? p.toUpperCase() : ((_a = table[p]) !== null && _a !== void 0 ? _a : p.toUpperCase()); });
55
- return isApple ? rendered.join("") : rendered.join("+");
56
+ return parts.map((p, i) => { var _a; return (i === parts.length - 1 ? p.toUpperCase() : ((_a = table[p]) !== null && _a !== void 0 ? _a : p.toUpperCase())); });
56
57
  };
58
+ exports.shortcutKeys = shortcutKeys;
59
+ const formatShortcut = (shortcut, isApple = isAppleNow()) => (0, exports.shortcutKeys)(shortcut, isApple).join(isApple ? "" : "+");
57
60
  exports.formatShortcut = formatShortcut;
58
61
  /**
59
62
  * Derives every app's origin from the host the consumer is already served on, so one deployment
@@ -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;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.recordingFileName = exports.formatElapsed = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const react_2 = require("@chakra-ui/react");
10
+ const Button_1 = __importDefault(require("../Button/Button"));
11
+ const useCustomTheme_1 = require("../../Theme/useCustomTheme");
12
+ const DEFAULT_LABELS = {
13
+ start: "Start recording",
14
+ stop: "Stop recording",
15
+ recording: "Recording",
16
+ recorded: "Recorded",
17
+ permissionDenied: "Microphone access was denied. Allow it in your browser settings and try again.",
18
+ unsupported: "Audio recording is not supported in this browser.",
19
+ };
20
+ const PREFERRED_MIME = "audio/webm";
21
+ const TICK_MS = 250;
22
+ const EXTENSIONS = {
23
+ "audio/webm": "webm",
24
+ "audio/wav": "wav",
25
+ "audio/wave": "wav",
26
+ "audio/x-wav": "wav",
27
+ "audio/ogg": "ogg",
28
+ "audio/mp4": "m4a",
29
+ "audio/mpeg": "mp3",
30
+ };
31
+ const formatElapsed = (ms) => {
32
+ const total = Math.floor(ms / 1000);
33
+ return `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
34
+ };
35
+ exports.formatElapsed = formatElapsed;
36
+ const recordingFileName = (mime, now) => {
37
+ var _a, _b;
38
+ const type = mime.split(";")[0].trim().toLowerCase() || PREFERRED_MIME;
39
+ return { type, name: `recording-${now}.${(_a = EXTENSIONS[type]) !== null && _a !== void 0 ? _a : (((_b = type.split("/")[1]) === null || _b === void 0 ? void 0 : _b.replace(/^x-/, "")) || "webm")}` };
40
+ };
41
+ exports.recordingFileName = recordingFileName;
42
+ const isSupported = () => { var _a; return typeof navigator !== "undefined" && !!((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia) && typeof MediaRecorder !== "undefined"; };
43
+ const AudioRecorder = ({ onRecorded, maxDurationSec, isDisabled = false, labels }) => {
44
+ const { colors } = (0, useCustomTheme_1.useCustomTheme)();
45
+ const text = { ...DEFAULT_LABELS, ...labels };
46
+ const [status, setStatus] = (0, react_1.useState)("idle");
47
+ const [elapsedMs, setElapsedMs] = (0, react_1.useState)(0);
48
+ const [announcement, setAnnouncement] = (0, react_1.useState)("");
49
+ const recorderRef = (0, react_1.useRef)(null);
50
+ const streamRef = (0, react_1.useRef)(null);
51
+ const chunksRef = (0, react_1.useRef)([]);
52
+ const timerRef = (0, react_1.useRef)(null);
53
+ const startedAtRef = (0, react_1.useRef)(0);
54
+ const mountedRef = (0, react_1.useRef)(true);
55
+ const startingRef = (0, react_1.useRef)(false);
56
+ const onRecordedRef = (0, react_1.useRef)(onRecorded);
57
+ const textRef = (0, react_1.useRef)(text);
58
+ onRecordedRef.current = onRecorded;
59
+ textRef.current = text;
60
+ const release = (0, react_1.useCallback)(() => {
61
+ var _a;
62
+ if (timerRef.current !== null)
63
+ clearInterval(timerRef.current);
64
+ timerRef.current = null;
65
+ (_a = streamRef.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((track) => track.stop());
66
+ streamRef.current = null;
67
+ recorderRef.current = null;
68
+ }, []);
69
+ (0, react_1.useEffect)(() => {
70
+ mountedRef.current = true;
71
+ if (!isSupported())
72
+ setStatus("unsupported");
73
+ return () => {
74
+ mountedRef.current = false;
75
+ const recorder = recorderRef.current;
76
+ if (recorder) {
77
+ recorder.ondataavailable = null;
78
+ recorder.onstop = null;
79
+ recorder.onerror = null;
80
+ if (recorder.state !== "inactive") {
81
+ try {
82
+ recorder.stop();
83
+ }
84
+ catch {
85
+ // NOTE: a recorder the browser already tore down throws on stop.
86
+ }
87
+ }
88
+ }
89
+ release();
90
+ };
91
+ }, [release]);
92
+ const stop = (0, react_1.useCallback)(() => {
93
+ const recorder = recorderRef.current;
94
+ if (recorder && recorder.state !== "inactive")
95
+ recorder.stop();
96
+ }, []);
97
+ const start = (0, react_1.useCallback)(async () => {
98
+ var _a, _b;
99
+ if (startingRef.current)
100
+ return;
101
+ if (!isSupported()) {
102
+ setStatus("unsupported");
103
+ return;
104
+ }
105
+ startingRef.current = true;
106
+ try {
107
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
108
+ if (!mountedRef.current) {
109
+ stream.getTracks().forEach((track) => track.stop());
110
+ return;
111
+ }
112
+ streamRef.current = stream;
113
+ chunksRef.current = [];
114
+ const useWebm = (_b = (_a = MediaRecorder.isTypeSupported) === null || _a === void 0 ? void 0 : _a.call(MediaRecorder, PREFERRED_MIME)) !== null && _b !== void 0 ? _b : false;
115
+ const recorder = new MediaRecorder(stream, useWebm ? { mimeType: PREFERRED_MIME } : undefined);
116
+ recorderRef.current = recorder;
117
+ recorder.ondataavailable = (event) => {
118
+ if (event.data.size > 0)
119
+ chunksRef.current.push(event.data);
120
+ };
121
+ recorder.onerror = () => {
122
+ release();
123
+ setStatus("idle");
124
+ };
125
+ recorder.onstop = () => {
126
+ const duration = Date.now() - startedAtRef.current;
127
+ const { type, name } = (0, exports.recordingFileName)(recorder.mimeType || PREFERRED_MIME, Date.now());
128
+ const file = new File(chunksRef.current, name, { type });
129
+ release();
130
+ setStatus("idle");
131
+ setElapsedMs(duration);
132
+ setAnnouncement(`${textRef.current.recorded} ${(0, exports.formatElapsed)(duration)}`);
133
+ if (file.size > 0)
134
+ onRecordedRef.current(file);
135
+ };
136
+ recorder.start();
137
+ startedAtRef.current = Date.now();
138
+ setElapsedMs(0);
139
+ setStatus("recording");
140
+ setAnnouncement(textRef.current.recording);
141
+ timerRef.current = setInterval(() => {
142
+ const elapsed = Date.now() - startedAtRef.current;
143
+ if (maxDurationSec !== undefined && elapsed >= maxDurationSec * 1000) {
144
+ setElapsedMs(maxDurationSec * 1000);
145
+ stop();
146
+ return;
147
+ }
148
+ setElapsedMs(elapsed);
149
+ }, TICK_MS);
150
+ }
151
+ catch (error) {
152
+ release();
153
+ if (!mountedRef.current)
154
+ return;
155
+ const name = error === null || error === void 0 ? void 0 : error.name;
156
+ setStatus(name === "NotFoundError" ? "unsupported" : "denied");
157
+ }
158
+ finally {
159
+ startingRef.current = false;
160
+ }
161
+ }, [maxDurationSec, release, stop]);
162
+ const isRecording = status === "recording";
163
+ const blocked = status === "unsupported";
164
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { children: [(0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 3, children: [(0, jsx_runtime_1.jsx)(Button_1.default, { label: isRecording ? text.stop : text.start, size: "sm", colorScheme: isRecording ? "red" : undefined, variant: isRecording ? "solid" : "outline", "aria-pressed": isRecording, isDisabled: isRecording ? false : isDisabled || blocked, onClick: isRecording ? stop : () => void start() }), isRecording ? ((0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, children: [(0, jsx_runtime_1.jsx)(react_2.Box, { "data-testid": "audio-recorder-dot", boxSize: "0.5rem", borderRadius: "full", bg: colors.semanticText.error, "aria-hidden": true, sx: {
165
+ "@media (prefers-reduced-motion: no-preference)": { animation: "audio-recorder-pulse 1.2s ease-in-out infinite" },
166
+ "@keyframes audio-recorder-pulse": { "0%, 100%": { opacity: 1 }, "50%": { opacity: 0.35 } },
167
+ } }), (0, jsx_runtime_1.jsx)(react_2.Text, { as: "span", role: "timer", fontSize: "0.875rem", color: colors.header[900], sx: { fontVariantNumeric: "tabular-nums" }, children: (0, exports.formatElapsed)(elapsedMs) })] })) : null] }), (0, jsx_runtime_1.jsx)(react_2.VisuallyHidden, { role: "status", "aria-live": "polite", children: announcement }), status === "denied" || status === "unsupported" ? ((0, jsx_runtime_1.jsx)(react_2.Text, { role: "alert", mt: 1, fontSize: "0.75rem", color: colors.semanticText.error, children: status === "denied" ? text.permissionDenied : text.unsupported })) : null] }));
168
+ };
169
+ exports.default = AudioRecorder;
@@ -0,0 +1,20 @@
1
+ /** Visible text and announcements; every key falls back to English. */
2
+ export type AudioRecorderLabels = {
3
+ start: string;
4
+ stop: string;
5
+ recording: string;
6
+ /** Announced when a recording finishes, followed by its duration. */
7
+ recorded: string;
8
+ permissionDenied: string;
9
+ unsupported: string;
10
+ };
11
+ /** Props for the microphone recorder: one toggle button, a timer, and a `File` on stop. */
12
+ export interface AudioRecorderProps {
13
+ /** Receives the finished recording; never called for an empty one or after unmount. */
14
+ onRecorded: (file: File) => void;
15
+ /** Stops the recording by itself after this many seconds. No limit when omitted. */
16
+ maxDurationSec?: number;
17
+ /** Blocks Start only; a recording already in progress can always be stopped. */
18
+ isDisabled?: boolean;
19
+ labels?: Partial<AudioRecorderLabels>;
20
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,2 +1,2 @@
1
1
  import { BreadcrumbsProps } from "./BreadcrumbsProps";
2
- export default function Breadcrumbs({ spacing, separator, fontWeight, fontSize, isCurrentPage, handleClick, items, }: BreadcrumbsProps): import("react/jsx-runtime").JSX.Element;
2
+ export default function Breadcrumbs({ spacing, separator, fontWeight, fontSize, isCurrentPage, handleClick, items, overflow, maxItems, labels, "data-testid": testId, }: BreadcrumbsProps): import("react/jsx-runtime").JSX.Element;
@@ -4,19 +4,42 @@ exports.default = Breadcrumbs;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("@chakra-ui/react");
6
6
  const useCustomTheme_1 = require("../../Theme/useCustomTheme");
7
+ const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
7
8
  const breadcrumbInk_1 = require("./breadcrumbInk");
8
- function Breadcrumbs({ spacing, separator, fontWeight, fontSize, isCurrentPage, handleClick, items, }) {
9
+ const DEFAULT_MAX_ITEMS = 3;
10
+ const MIN_MAX_ITEMS = 2;
11
+ const OVERFLOW_SX = {
12
+ nowrap: undefined,
13
+ wrap: { "& ol": { flexWrap: "wrap", rowGap: "4px" }, "& li": { maxWidth: "100%" } },
14
+ truncate: { "& ol": { flexWrap: "nowrap" }, "& li": { minWidth: 0 } },
15
+ };
16
+ const collapse = (entries, maxItems) => {
17
+ const keep = Math.max(MIN_MAX_ITEMS, Math.floor(maxItems));
18
+ if (entries.length <= keep)
19
+ return { head: entries, hidden: [], tail: [] };
20
+ return { head: entries.slice(0, 1), hidden: entries.slice(1, entries.length - (keep - 1)), tail: entries.slice(entries.length - (keep - 1)) };
21
+ };
22
+ function Breadcrumbs({ spacing, separator, fontWeight, fontSize, isCurrentPage, handleClick, items, overflow = "nowrap", maxItems = DEFAULT_MAX_ITEMS, labels, "data-testid": testId, }) {
9
23
  const theme = (0, react_1.useTheme)();
10
24
  const { colors } = (0, useCustomTheme_1.useCustomTheme)();
11
25
  const ink = (0, breadcrumbInk_1.breadcrumbInk)(colors);
12
26
  const effectiveSeparator = separator !== null && separator !== void 0 ? separator : ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "span", color: ink.separator, children: "/" }));
13
- return ((0, jsx_runtime_1.jsx)(react_1.Breadcrumb, { spacing: spacing, separator: effectiveSeparator, fontWeight: fontWeight, fontSize: fontSize, fontFamily: theme.fonts.body, children: items.map((item, index) => {
14
- const isLast = isCurrentPage && index === items.length - 1;
15
- return ((0, jsx_runtime_1.jsx)(react_1.BreadcrumbItem, { isCurrentPage: isLast, children: (0, jsx_runtime_1.jsx)(react_1.BreadcrumbLink, { href: item.path, color: isLast ? ink.current : ink.link, fontWeight: isLast ? 600 : fontWeight, sx: {
16
- "&:hover, &:active": { textDecoration: "none", color: isLast ? ink.current : ink.linkHover },
17
- }, onClick: (event) => {
18
- event.preventDefault();
19
- handleClick(item.path);
20
- }, children: item.label }) }, index));
21
- }) }));
27
+ const clip = overflow !== "nowrap";
28
+ const clipProps = clip ? { noOfLines: 1 } : {};
29
+ const renderCrumb = ({ crumb, index }) => {
30
+ const isLast = Boolean(isCurrentPage) && index === items.length - 1;
31
+ const color = isLast ? ink.current : ink.link;
32
+ const common = { ...clipProps, title: clip ? crumb.label : undefined, color, fontWeight: isLast ? 600 : fontWeight };
33
+ return ((0, jsx_runtime_1.jsx)(react_1.BreadcrumbItem, { isCurrentPage: isLast, children: crumb.path === undefined ? ((0, jsx_runtime_1.jsx)(react_1.Box, { as: "span", "aria-current": isLast ? "page" : undefined, ...common, children: crumb.label })) : ((0, jsx_runtime_1.jsx)(react_1.BreadcrumbLink, { href: crumb.path, ...common, sx: { "&:hover, &:active": { textDecoration: "none", color: isLast ? ink.current : ink.linkHover } }, onClick: (event) => {
34
+ event.preventDefault();
35
+ handleClick(crumb.path);
36
+ }, children: crumb.label })) }, index));
37
+ };
38
+ const renderMenu = (hidden) => {
39
+ var _a;
40
+ return ((0, jsx_runtime_1.jsx)(react_1.BreadcrumbItem, { sx: { flexShrink: 0 }, children: (0, jsx_runtime_1.jsxs)(react_1.Menu, { isLazy: true, placement: "bottom-start", children: [(0, jsx_runtime_1.jsx)(react_1.MenuButton, { type: "button", "aria-label": ((_a = labels === null || labels === void 0 ? void 0 : labels.showMore) !== null && _a !== void 0 ? _a : ((n) => `Show ${n} more`))(hidden.length), color: ink.link, borderRadius: "md", px: 1, _hover: { color: ink.linkHover }, sx: (0, focusRing_styles_1.focusVisibleStyles)(colors), children: "\u2026" }), (0, jsx_runtime_1.jsx)(react_1.MenuList, { children: hidden.map(({ crumb, index }) => ((0, jsx_runtime_1.jsx)(react_1.MenuItem, { isDisabled: crumb.path === undefined, onClick: () => crumb.path !== undefined && handleClick(crumb.path), children: crumb.label }, index))) })] }) }, "overflow"));
41
+ };
42
+ const entries = items.map((crumb, index) => ({ crumb, index }));
43
+ const { head, hidden, tail } = overflow === "truncate" ? collapse(entries, maxItems) : { head: entries, hidden: [], tail: [] };
44
+ return ((0, jsx_runtime_1.jsxs)(react_1.Breadcrumb, { spacing: spacing, separator: effectiveSeparator, fontWeight: fontWeight, fontSize: fontSize, fontFamily: theme.fonts.body, sx: OVERFLOW_SX[overflow], "data-testid": testId, children: [head.map(renderCrumb), hidden.length > 0 && renderMenu(hidden), tail.map(renderCrumb)] }));
22
45
  }
@@ -4,11 +4,25 @@ export type BreadcrumbsProps = Pick<BreadcrumbProps, "spacing" | "separator" | "
4
4
  isCurrentPage?: boolean;
5
5
  /** Accepted but never read by Breadcrumbs — has no effect at all. */
6
6
  isLastChild?: boolean;
7
+ /** Called with a crumb's path; never called for a label-only crumb (no `path`). */
7
8
  handleClick: (path: string) => void;
8
9
  items: BreadcrumbItem[];
10
+ /** "wrap" flows crumbs onto new lines; "truncate" keeps one line and folds middle crumbs into a "…" menu. Default "nowrap". */
11
+ overflow?: "nowrap" | "wrap" | "truncate";
12
+ /** With overflow "truncate": most crumbs shown (first plus the last `maxItems - 1`); the rest go in the menu. Default 3, minimum 2. */
13
+ maxItems?: number;
14
+ /** Visible-string overrides; `showMore` is the collapsed menu button's aria-label. */
15
+ labels?: BreadcrumbsLabels;
16
+ "data-testid"?: string;
9
17
  };
10
- /** One crumb: `label` is the visible text, `path` is passed to both the link's href and `handleClick`. */
18
+ /** Visible strings, each with an English default. */
19
+ export type BreadcrumbsLabels = {
20
+ /** Default: `Show ${count} more`. */
21
+ showMore?: (count: number) => string;
22
+ };
23
+ /** One crumb: `label` is the visible text; `path` goes to the link's href and `handleClick`. */
11
24
  export type BreadcrumbItem = {
12
- path: string;
25
+ /** Omit for a label-only crumb: rendered as text, not a link, never passed to `handleClick`. */
26
+ path?: string;
13
27
  label: string;
14
28
  };
@@ -0,0 +1,3 @@
1
+ import type { BulkSelectionBarProps } from "./BulkSelectionBarProps";
2
+ declare const BulkSelectionBar: ({ selectedCount, facts, actions, onClearSelection, clearLabel, countLabel, placement, "aria-label": ariaLabel, "data-testid": testId, }: BulkSelectionBarProps) => import("react/jsx-runtime").JSX.Element | null;
3
+ export default BulkSelectionBar;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const react_2 = require("@chakra-ui/react");
9
+ const lucide_react_1 = require("lucide-react");
10
+ const Button_1 = __importDefault(require("../Button/Button"));
11
+ const useCustomTheme_1 = require("../../Theme/useCustomTheme");
12
+ const FACT_SEPARATOR = " · ";
13
+ const VARIANT_BY_TONE = {
14
+ default: "outline",
15
+ primary: "solid",
16
+ danger: "outline",
17
+ };
18
+ const SCHEME_BY_TONE = {
19
+ default: "gray",
20
+ primary: "primary",
21
+ danger: "red",
22
+ };
23
+ const TOUCH_SX = { "@media (pointer: coarse)": { minHeight: "44px", minWidth: "44px" } };
24
+ const BulkSelectionBar = ({ selectedCount, facts, actions, onClearSelection, clearLabel = "Clear selection", countLabel, placement = "sticky", "aria-label": ariaLabel = "Selection actions", "data-testid": testId = "bulk-selection-bar", }) => {
25
+ const { colors } = (0, useCustomTheme_1.useCustomTheme)();
26
+ const isActive = selectedCount > 0;
27
+ const summary = countLabel !== null && countLabel !== void 0 ? countLabel : `${selectedCount} selected`;
28
+ const [announcement, setAnnouncement] = (0, react_1.useState)("");
29
+ (0, react_1.useEffect)(() => {
30
+ setAnnouncement(isActive ? summary : "");
31
+ }, [isActive, summary]);
32
+ if (!isActive)
33
+ return null;
34
+ const resolvedFacts = (facts !== null && facts !== void 0 ? facts : []).filter(Boolean);
35
+ const isSticky = placement === "sticky";
36
+ return ((0, jsx_runtime_1.jsxs)(react_2.Flex, { role: "region", "aria-label": ariaLabel, "data-testid": testId, align: "center", gap: 3, wrap: "wrap", px: 4, py: 2, w: "100%", borderRadius: "lg", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, position: isSticky ? "sticky" : "relative", bottom: isSticky ? 0 : undefined, zIndex: isSticky ? 2 : undefined, children: [(0, jsx_runtime_1.jsx)(react_2.VisuallyHidden, { role: "status", "aria-live": "polite", "data-testid": "bulk-selection-announcement", children: announcement }), (0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "baseline", gap: 1, flex: "1", minW: "180px", wrap: "wrap", children: [(0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: "sm", fontWeight: 600, color: colors.text[800], "data-testid": "bulk-selection-count", children: summary }), resolvedFacts.length ? ((0, jsx_runtime_1.jsxs)(react_2.Text, { fontSize: "sm", color: colors.textMuted, "data-testid": "bulk-selection-facts", children: [FACT_SEPARATOR, resolvedFacts.join(FACT_SEPARATOR)] })) : null] }), (0, jsx_runtime_1.jsxs)(react_2.Flex, { align: "center", gap: 2, wrap: "wrap", children: [actions.map((action) => {
37
+ var _a;
38
+ const tone = (_a = action.tone) !== null && _a !== void 0 ? _a : "default";
39
+ const Icon = action.icon;
40
+ return ((0, jsx_runtime_1.jsx)(Button_1.default, { size: "sm", variant: VARIANT_BY_TONE[tone], colorScheme: SCHEME_BY_TONE[tone], onClick: action.onClick, isDisabled: action.isDisabled, isLoading: action.isLoading, loadingText: action.label, leftIcon: Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, "aria-hidden": "true" }) : undefined, sx: TOUCH_SX, "data-testid": `bulk-action-${action.id}`, "data-tone": tone, children: action.label }, action.id));
41
+ }), (0, jsx_runtime_1.jsx)(react_2.Box, { children: (0, jsx_runtime_1.jsx)(Button_1.default, { size: "sm", variant: "ghost", colorScheme: "gray", onClick: onClearSelection, leftIcon: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 16, "aria-hidden": "true" }), sx: TOUCH_SX, "data-testid": "bulk-selection-clear", children: clearLabel }) })] })] }));
42
+ };
43
+ exports.default = BulkSelectionBar;
@@ -0,0 +1,35 @@
1
+ import type { ElementType } from "react";
2
+ /** Emphasis of a bulk action: "primary" is the affirmative one, "danger" the destructive one, "default" the rest. */
3
+ export type BulkActionTone = "default" | "primary" | "danger";
4
+ /** One button in the bar; actions render in array order. */
5
+ export interface BulkAction {
6
+ /** Stable key; also the suffix of the button's `data-testid` (`bulk-action-<id>`). */
7
+ id: string;
8
+ label: string;
9
+ onClick: () => void;
10
+ /** Defaults to "default". */
11
+ tone?: BulkActionTone;
12
+ /** Icon component (e.g. a lucide icon); rendered at 16px, decorative. */
13
+ icon?: ElementType;
14
+ isDisabled?: boolean;
15
+ /** Spinner on this action; the label stays as the loading text. */
16
+ isLoading?: boolean;
17
+ }
18
+ /** Props for the selection bar shown while table rows are selected; it renders nothing at zero. */
19
+ export interface BulkSelectionBarProps {
20
+ /** Rows selected; the bar renders nothing at 0 and announces each change politely. */
21
+ selectedCount: number;
22
+ /** Short claims true of the whole selection, e.g. ["all leave", "no clashes"]; joined after the count with " · ". */
23
+ facts?: string[];
24
+ actions: BulkAction[];
25
+ onClearSelection: () => void;
26
+ /** Clear button text; defaults to "Clear selection". */
27
+ clearLabel?: string;
28
+ /** Replaces the default "{n} selected" text, and what is announced. */
29
+ countLabel?: string;
30
+ /** "sticky" (default) pins to the bottom of the nearest scroll container; "inline" stays in flow. */
31
+ placement?: "inline" | "sticky";
32
+ /** Accessible name of the region; defaults to "Selection actions". */
33
+ "aria-label"?: string;
34
+ "data-testid"?: string;
35
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export { default as BulkSelectionBar } from "./BulkSelectionBar";
2
+ export type { BulkAction, BulkActionTone, BulkSelectionBarProps } from "./BulkSelectionBarProps";
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BulkSelectionBar = void 0;
7
+ var BulkSelectionBar_1 = require("./BulkSelectionBar");
8
+ Object.defineProperty(exports, "BulkSelectionBar", { enumerable: true, get: function () { return __importDefault(BulkSelectionBar_1).default; } });
@@ -0,0 +1,3 @@
1
+ import type { CalendarGridProps } from "./CalendarGridProps";
2
+ declare const CalendarGrid: ({ view, date, events, onViewChange, onDateChange, onEventClick, renderDayCell, weekStartsOn, locale, isLoading, labels, }: CalendarGridProps) => import("react/jsx-runtime").JSX.Element;
3
+ export default CalendarGrid;