@zeyos/zx 4.4.0 → 5.2.0

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 (252) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/README.md +44 -11
  3. package/dist/types/components/account-menu/account-menu.d.ts +8 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +31 -1
  5. package/dist/types/components/app-icon/app-icon.d.ts +14 -2
  6. package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
  7. package/dist/types/components/avatar/avatar.d.ts +46 -7
  8. package/dist/types/components/badge/badge.d.ts +21 -1
  9. package/dist/types/components/button/button.d.ts +15 -0
  10. package/dist/types/components/calendar/calendar.d.ts +67 -5
  11. package/dist/types/components/card-view/card-view.d.ts +7 -4
  12. package/dist/types/components/card-view/record-card.d.ts +11 -0
  13. package/dist/types/components/chart/chart.d.ts +10 -0
  14. package/dist/types/components/checklist/checklist.d.ts +9 -0
  15. package/dist/types/components/code-editor/code-editor.d.ts +6 -17
  16. package/dist/types/components/context-menu/context-menu.d.ts +6 -2
  17. package/dist/types/components/data-filter/data-filter.d.ts +4 -4
  18. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  19. package/dist/types/components/datebox/datebox.d.ts +7 -2
  20. package/dist/types/components/dialog/dialog.d.ts +46 -6
  21. package/dist/types/components/dropdown/dropdown.d.ts +24 -9
  22. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  23. package/dist/types/components/field/field.d.ts +54 -2
  24. package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
  25. package/dist/types/components/fieldset/fieldset.d.ts +26 -1
  26. package/dist/types/components/file-item/file-item.d.ts +34 -6
  27. package/dist/types/components/file-list/file-list.d.ts +35 -3
  28. package/dist/types/components/filter/filter.d.ts +127 -3
  29. package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
  30. package/dist/types/components/form/form.d.ts +12 -1
  31. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  32. package/dist/types/components/launcher/launcher.d.ts +268 -15
  33. package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
  34. package/dist/types/components/menu-button/menu-button.d.ts +39 -5
  35. package/dist/types/components/message/message.d.ts +132 -17
  36. package/dist/types/components/modal/modal.d.ts +43 -2
  37. package/dist/types/components/module-bar/module-bar.d.ts +395 -0
  38. package/dist/types/components/pagination/pagination.d.ts +17 -3
  39. package/dist/types/components/select/color.d.ts +12 -0
  40. package/dist/types/components/select/glyphs.d.ts +124 -0
  41. package/dist/types/components/select/select.d.ts +17 -3
  42. package/dist/types/components/sheet/sheet.d.ts +15 -0
  43. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  44. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  45. package/dist/types/components/table/table.d.ts +311 -18
  46. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  47. package/dist/types/components/tree/tree.d.ts +13 -0
  48. package/dist/types/components/view/record-view.d.ts +59 -2
  49. package/dist/types/components/view-settings/view-settings.d.ts +879 -0
  50. package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
  51. package/dist/types/core/breakpoint.d.ts +1 -1
  52. package/dist/types/core/component.d.ts +12 -2
  53. package/dist/types/core/i18n.d.ts +27 -3
  54. package/dist/types/core/icons.d.ts +23 -25
  55. package/dist/types/core/keyboard.d.ts +98 -1
  56. package/dist/types/core/messages.d.ts +382 -0
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +42 -1
  59. package/dist/types/core/overlay-stack.d.ts +260 -0
  60. package/dist/types/index.d.ts +10 -5
  61. package/dist/types/internal/app-rail.d.ts +38 -0
  62. package/dist/types/internal/menu-items.d.ts +71 -3
  63. package/dist/types/zeyos/connect.d.ts +6 -0
  64. package/dist/types/zeyos/form.d.ts +50 -9
  65. package/dist/types/zeyos/index.d.ts +2 -1
  66. package/dist/types/zeyos/launcher.d.ts +9 -0
  67. package/dist/types/zeyos/module-bar.d.ts +176 -0
  68. package/dist/types/zeyos/query.d.ts +110 -5
  69. package/dist/types/zeyos/schema.d.ts +24 -1
  70. package/dist/types/zeyos/table.d.ts +345 -22
  71. package/dist/types/zeyos/view.d.ts +26 -2
  72. package/dist/zx-zeyos.esm.js +6178 -1008
  73. package/dist/zx-zeyos.esm.min.js +53 -5
  74. package/dist/zx.css +2861 -827
  75. package/dist/zx.esm.js +8909 -1480
  76. package/dist/zx.esm.min.js +55 -7
  77. package/dist/zx.global.js +8911 -1482
  78. package/dist/zx.global.min.js +55 -7
  79. package/dist/zx.min.css +1 -1
  80. package/docs/api.json +1079 -117
  81. package/docs/llms.md +956 -154
  82. package/docs/llms.txt +9 -5
  83. package/package.json +1 -1
  84. package/src/components/account-menu/account-menu.css +35 -10
  85. package/src/components/account-menu/account-menu.js +12 -0
  86. package/src/components/activity-item/activity-item.css +1 -1
  87. package/src/components/activity-item/activity-item.js +3 -0
  88. package/src/components/activity-list/activity-list.css +38 -3
  89. package/src/components/activity-list/activity-list.js +61 -1
  90. package/src/components/app-icon/app-icon.css +14 -3
  91. package/src/components/app-icon/app-icon.js +11 -1
  92. package/src/components/app-rail/app-rail.css +59 -79
  93. package/src/components/app-sidebar/app-sidebar.css +304 -168
  94. package/src/components/app-sidebar/app-sidebar.js +667 -46
  95. package/src/components/aurora/aurora.css +5 -3
  96. package/src/components/avatar/avatar.css +32 -7
  97. package/src/components/avatar/avatar.js +72 -8
  98. package/src/components/badge/badge.css +54 -2
  99. package/src/components/badge/badge.js +29 -2
  100. package/src/components/breadcrumb/breadcrumb.css +1 -1
  101. package/src/components/button/button.css +67 -9
  102. package/src/components/button/button.js +38 -0
  103. package/src/components/calendar/calendar.css +47 -47
  104. package/src/components/calendar/calendar.js +26 -52
  105. package/src/components/card/card.css +4 -4
  106. package/src/components/card-view/card-view.css +14 -12
  107. package/src/components/card-view/card-view.js +13 -9
  108. package/src/components/card-view/record-card.js +14 -3
  109. package/src/components/chart/chart.css +4 -1
  110. package/src/components/chart/chart.js +12 -0
  111. package/src/components/check-button/check-button.css +10 -2
  112. package/src/components/checklist/checklist.css +9 -2
  113. package/src/components/checklist/checklist.js +11 -5
  114. package/src/components/checklist/field-adapter.js +3 -1
  115. package/src/components/code-editor/code-editor.css +7 -6
  116. package/src/components/code-editor/code-editor.js +19 -1
  117. package/src/components/code-editor/field-adapter.js +9 -2
  118. package/src/components/context-menu/context-menu.css +9 -4
  119. package/src/components/context-menu/context-menu.js +8 -2
  120. package/src/components/copy/copy-button.css +6 -0
  121. package/src/components/copy/copy-input.css +9 -4
  122. package/src/components/data-filter/data-filter.css +5 -4
  123. package/src/components/data-filter/data-filter.js +2 -2
  124. package/src/components/date-picker/date-picker.css +6 -6
  125. package/src/components/date-picker/date-range-picker.css +3 -3
  126. package/src/components/date-picker/time-picker.css +3 -3
  127. package/src/components/datebox/date-range-box.css +79 -2
  128. package/src/components/datebox/date-range-box.js +13 -2
  129. package/src/components/datebox/datebox.css +79 -2
  130. package/src/components/datebox/datebox.js +13 -2
  131. package/src/components/dialog/dialog.css +26 -0
  132. package/src/components/dialog/dialog.js +99 -17
  133. package/src/components/dock/dock.css +5 -4
  134. package/src/components/dropdown/dropdown.css +1 -1
  135. package/src/components/dropdown/dropdown.js +159 -72
  136. package/src/components/empty-state/empty-state.css +2 -2
  137. package/src/components/entity-ref/entity-ref.css +1 -1
  138. package/src/components/entity-ref/entity-ref.js +1 -1
  139. package/src/components/field/field.css +28 -9
  140. package/src/components/field/field.js +101 -29
  141. package/src/components/field-upload/field-upload.css +1 -1
  142. package/src/components/fieldset/external-adapter.js +363 -0
  143. package/src/components/fieldset/fieldset.css +5 -3
  144. package/src/components/fieldset/fieldset.js +38 -64
  145. package/src/components/file-item/file-item.js +144 -10
  146. package/src/components/file-list/file-list.js +144 -21
  147. package/src/components/filter/filter.css +86 -2
  148. package/src/components/filter/filter.js +307 -11
  149. package/src/components/filter-panel/filter-panel.css +1 -1
  150. package/src/components/filter-panel/filter-panel.js +149 -32
  151. package/src/components/finder/finder.css +16 -14
  152. package/src/components/form/form.css +5 -4
  153. package/src/components/form/form.js +11 -2
  154. package/src/components/grid/grid.css +1 -1
  155. package/src/components/groupbox/groupbox.css +1 -1
  156. package/src/components/kanban-view/kanban-card.js +11 -3
  157. package/src/components/kanban-view/kanban-view.css +42 -18
  158. package/src/components/kanban-view/kanban-view.js +9 -6
  159. package/src/components/launcher/launcher.css +108 -26
  160. package/src/components/launcher/launcher.js +530 -42
  161. package/src/components/list-toolbar/list-toolbar.css +13 -8
  162. package/src/components/list-toolbar/list-toolbar.js +40 -14
  163. package/src/components/master-panel/master-panel.css +2 -0
  164. package/src/components/master-panel/master-panel.js +0 -1
  165. package/src/components/menu-button/menu-button.css +72 -4
  166. package/src/components/menu-button/menu-button.js +160 -18
  167. package/src/components/message/message.css +33 -3
  168. package/src/components/message/message.js +264 -22
  169. package/src/components/modal/modal.css +1 -1
  170. package/src/components/modal/modal.js +83 -15
  171. package/src/components/module-bar/module-bar.css +486 -0
  172. package/src/components/module-bar/module-bar.js +1074 -0
  173. package/src/components/multi-value-editor/multi-value-editor.css +1 -1
  174. package/src/components/navigation-bar/navigation-bar.css +18 -9
  175. package/src/components/number-field/field-adapter.js +3 -1
  176. package/src/components/number-field/number-field.css +70 -0
  177. package/src/components/number-field/number-field.js +2 -0
  178. package/src/components/pagination/pagination.css +13 -11
  179. package/src/components/pagination/pagination.js +20 -4
  180. package/src/components/panel/panel.css +4 -3
  181. package/src/components/questionnaire/questionnaire.css +4 -4
  182. package/src/components/rating/field-adapter.js +2 -1
  183. package/src/components/rating/rating.css +8 -1
  184. package/src/components/search/search.css +4 -1
  185. package/src/components/select/color.js +22 -0
  186. package/src/components/select/field-adapter.js +2 -0
  187. package/src/components/select/glyphs.js +165 -0
  188. package/src/components/select/select.css +138 -45
  189. package/src/components/select/select.js +92 -47
  190. package/src/components/sheet/sheet.css +12 -9
  191. package/src/components/sheet/sheet.js +74 -32
  192. package/src/components/slider/field-adapter.js +6 -1
  193. package/src/components/slider/slider.css +18 -4
  194. package/src/components/slider/slider.js +1 -1
  195. package/src/components/sort-control/sort-control.css +4 -1
  196. package/src/components/split-view/split-view.css +1 -1
  197. package/src/components/stat-tile/stat-tile.css +42 -47
  198. package/src/components/stat-tile/stat-tile.js +17 -6
  199. package/src/components/stepper/stepper.css +1 -1
  200. package/src/components/tabbox/tabbox.css +41 -32
  201. package/src/components/tabbox/tabbox.js +5 -1
  202. package/src/components/table/table.css +187 -37
  203. package/src/components/table/table.js +718 -73
  204. package/src/components/tag-picker/field-adapter.js +2 -1
  205. package/src/components/tag-picker/tag-picker.css +83 -5
  206. package/src/components/tag-picker/tag-picker.js +25 -8
  207. package/src/components/timebox/timebox.css +22 -2
  208. package/src/components/toggle/toggle.css +12 -1
  209. package/src/components/toolbar/toolbar.js +2 -2
  210. package/src/components/tooltip/tooltip.css +4 -0
  211. package/src/components/tooltip/tooltip.js +44 -19
  212. package/src/components/tree/tree.css +26 -17
  213. package/src/components/tree/tree.js +31 -0
  214. package/src/components/value-list/value-list.css +3 -3
  215. package/src/components/view/record-view.css +1 -1
  216. package/src/components/view/record-view.js +178 -18
  217. package/src/components/view-settings/view-settings.css +285 -0
  218. package/src/components/view-settings/view-settings.js +1830 -0
  219. package/src/components/workspace-switcher/workspace-switcher.css +188 -0
  220. package/src/components/workspace-switcher/workspace-switcher.js +463 -0
  221. package/src/core/breakpoint.js +1 -1
  222. package/src/core/component.js +88 -17
  223. package/src/core/fontawesome.js +5 -1
  224. package/src/core/i18n.js +47 -4
  225. package/src/core/icons.js +204 -61
  226. package/src/core/keyboard.js +206 -11
  227. package/src/core/messages.js +427 -0
  228. package/src/core/motion.js +147 -0
  229. package/src/core/overlay-host.js +77 -3
  230. package/src/core/overlay-stack.js +487 -0
  231. package/src/core/position.js +22 -3
  232. package/src/index.js +10 -5
  233. package/src/internal/app-rail.js +135 -55
  234. package/src/internal/menu-items.js +61 -3
  235. package/src/zeyos/connect.js +6 -2
  236. package/src/zeyos/form.js +69 -27
  237. package/src/zeyos/index.js +4 -1
  238. package/src/zeyos/launcher.js +8 -3
  239. package/src/zeyos/module-bar.js +181 -0
  240. package/src/zeyos/query.js +197 -13
  241. package/src/zeyos/schema.js +144 -27
  242. package/src/zeyos/table.js +287 -84
  243. package/src/zeyos/view.js +49 -93
  244. package/styles/base.css +72 -1
  245. package/styles/icons.css +11 -1
  246. package/styles/print.css +1 -1
  247. package/styles/tokens/dark.css +117 -37
  248. package/styles/tokens/global.css +121 -27
  249. package/styles/tokens/semantic.css +140 -43
  250. package/styles/tokens/themes.css +7 -6
  251. package/styles/zx.css +3 -1
  252. package/styles/tokens/density.css +0 -9
package/docs/llms.txt CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  > Xenon is a dependency-free design system for browser applications; Zx is its product-agnostic
4
4
  > vanilla-JavaScript implementation. ES2022 modules, WAI-ARIA-accessible native
5
- > controls, semantic `--zx-*` design tokens with light/dark/density theming, and lifecycle-safe
6
- > components. Source modules run directly in the browser; a build step emits ESM, IIFE-global,
5
+ > controls, semantic `--zx-*` design tokens with light/dark theming and one token-driven geometry,
6
+ > and lifecycle-safe components. Source modules run directly in the browser; a build step emits ESM, IIFE-global,
7
7
  > ZeyOS binding, and CSS bundles. Zero runtime dependencies.
8
8
 
9
9
  Instantiate every component as `new Component(target, options)` where `target` is an Element, a
@@ -35,7 +35,9 @@ exports and as `window.zx.*`.
35
35
  drag and resize, `optimistic: true|false`), backend-neutral nested `Filter`, simple client-side
36
36
  `DataFilter`, `FilterPanel` (typed filter form built from a server's own field metadata;
37
37
  `mode: 'draft'|'live'`, `serialize`), `ListToolbar` (search, live result count, view switch, and
38
- tools whose badge is part of the accessible name), `Pagination` (+ pure `paginationRange()`),
38
+ tools whose badge is part of the accessible name), `ViewSettings` (view-settings drawer content:
39
+ reorder, hide, and add fields, grouping, board axes; `bind()` to a record view),
40
+ `Pagination` (+ pure `paginationRange()`),
39
41
  `SortControl` (searchable field selector with a direction toggle, one atomic `{id,dir}`),
40
42
  `TreeView` (APG tree), `Finder` (Miller columns).
41
43
  - Forms: `Form`, `Fieldset`, `Questionnaire` (guided one-question-at-a-time flow with branching), `Field` (type registry), `CodeEditor` (structured text in a native textarea; never executes or injects the value), `ValueList`, `MultiValueEditor`, `FieldUpload`.
@@ -47,7 +49,9 @@ exports and as `window.zx.*`.
47
49
  canvas light; shared material roles govern chrome and overlays), `Avatar`, `Panel` (header/footer action
48
50
  buttons), `MasterPanel`, `Tabbox`
49
51
  (`variant: 'divided'|'bracket'|'line'|'segmented'`), `NavigationBar`, `AppSidebar` (expanded and
50
- minimized vertical states plus horizontal rail layouts), `AccountMenu`, `Launcher`,
52
+ minimized vertical states plus horizontal rail layouts; sections; a modules layout), `ModuleBar`
53
+ (colour-coded module strip or bar with flyout menus and an "All modules" catalogue), `AccountMenu`,
54
+ `WorkspaceSwitcher` (workspace tile, name and menu at the head of a navigation column), `Launcher`,
51
55
  `spinner()`/`ProgressBar`/`InlineLoading`, `skeleton()`/`skeletonText()`/`skeletonTable()`,
52
56
  `Toolbar` (APG toolbar with overflow menu), `SplitView`, `Dock`
53
57
  (collapsible resizable pane stack; tab groups, regions, adopts a `Sheet`), `Stepper`,
@@ -56,7 +60,7 @@ exports and as `window.zx.*`.
56
60
  - Helpers: `formatNumber`/`formatCurrency`/`formatPercent`/`formatFileSize`/`formatRelativeTime` (locale-aware, cached `Intl`), `debounce`/`throttle`, `groupBy`/`sortBy`/`uniqueBy`, `escapeRegExp`/`highlightMatch`, `storage()` (namespaced JSON, degrades to memory), `toCsv`/`downloadBlob`/`copyToClipboard`, `truncate()`/`isTruncated()` (line clamp with the full value on hover).
57
61
 
58
62
  ## Theming
59
- Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, density, font, type size, radius, control height, and CSS-first glass material. Compact menus, pickers, tooltips, column menus, and toasts share responsive compact glass; Launcher, Modal, Dialog, and floating Sheets share a calmer reading panel. Both preserve scoped theme inheritance and refract Aurora/page content; docked Sheets and dense data surfaces remain opaque. Never use tier-1 palette tokens or raw color literals in application CSS.
63
+ Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) on any ancestor. There is one geometry: spacing steps are multiples of `--zx-space-unit` (4px), radius steps are fractions of `--zx-radius` (4px: 2/3/4/6px), and controls are `--zx-control-height` (30px) tall — override those to tighten or loosen an application. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, spacing, font, type size, radius, control height, and CSS-first glass material. Compact menus, pickers, tooltips, column menus, and toasts share responsive compact glass; Launcher, Modal, Dialog, and floating Sheets share a calmer reading panel. Both preserve scoped theme inheritance and refract Aurora/page content; docked Sheets and dense data surfaces remain opaque. Never use tier-1 palette tokens or raw color literals in application CSS.
60
64
 
61
65
  ## Talking to ZeyOS
62
66
  Use the dedicated ZeyOS client library **`@zeyos/client`** (`npm install @zeyos/client`) for ZeyOS business data: `createZeyosClient({ platform, auth })`, typed `client.api.list/get/create/update<Resource>` over the full OpenAPI surface, and `normalizeListResult`. Zx accepts the client by injection and does not bundle it. See the `website/layouts/zeyos-invoices.layout.js` layout for a Zx-components-on-@zeyos/client reference. Zx also ships a minimal `Http`/`zeyosService` for ad-hoc in-app `remotecall`, but `@zeyos/client` is the recommended default.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeyos/zx",
3
- "version": "4.4.0",
3
+ "version": "5.2.0",
4
4
  "description": "Dependency-free JavaScript implementation of the Xenon Design System.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,26 +3,50 @@
3
3
  min-inline-size: 0;
4
4
  }
5
5
 
6
- .zx-account-menu__trigger {
6
+ /*
7
+ * The trigger is a flat row, not a button. MenuButton makes it a `.zx-btn.zx-menu-button`, and
8
+ * both of those stylesheets load after this one — `menu-button.css` centres it as an inline-flex
9
+ * pill and `button.css` gives it a border, a glass fill, a backdrop blur and a shadow. Every rule
10
+ * here is scoped under the root so it out-specifies them instead of depending on load order, and
11
+ * takes back each property they set rather than only the ones that were visible at the time.
12
+ */
13
+ .zx-account-menu .zx-account-menu__trigger {
7
14
  display: grid;
8
15
  grid-template-columns: auto minmax(0, 1fr) auto;
9
16
  align-items: center;
10
- gap: var(--zx-space-3);
17
+ justify-content: stretch;
18
+ gap: var(--zx-space-2);
11
19
  inline-size: 100%;
12
20
  min-block-size: calc(var(--zx-control-height) + var(--zx-space-2));
13
- border-color: transparent;
21
+ border: 0;
22
+ border-radius: var(--zx-radius-md);
14
23
  background: transparent;
15
- padding: var(--zx-space-2);
24
+ -webkit-backdrop-filter: none;
25
+ backdrop-filter: none;
26
+ box-shadow: none;
27
+ padding: var(--zx-space-1) var(--zx-space-2);
16
28
  color: inherit;
29
+ font-weight: var(--zx-weight-medium);
30
+ line-height: 1.3;
17
31
  text-align: start;
18
32
  }
19
33
 
20
- .zx-account-menu__trigger:hover,
21
- .zx-account-menu__trigger:focus-visible,
22
- .zx-account-menu__trigger[aria-expanded="true"] {
34
+ .zx-account-menu .zx-account-menu__trigger:is(:hover, [aria-expanded="true"]):not(:disabled) {
23
35
  background: var(--zx-color-bg-hover);
24
36
  }
25
37
 
38
+ .zx-account-menu .zx-account-menu__trigger:active:not(:disabled) {
39
+ background: var(--zx-color-bg-current);
40
+ }
41
+
42
+ .zx-account-menu .zx-account-menu__trigger:focus-visible {
43
+ box-shadow: var(--zx-focus-ring);
44
+ }
45
+
46
+ .zx-account-menu__chevron {
47
+ color: var(--zx-color-text-subtle);
48
+ }
49
+
26
50
  .zx-account-menu__identity {
27
51
  display: grid;
28
52
  min-inline-size: 0;
@@ -36,8 +60,8 @@
36
60
  white-space: nowrap;
37
61
  }
38
62
 
39
- .zx-account-menu__name { font-weight: 650; }
40
- .zx-account-menu__secondary { color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); }
63
+ .zx-account-menu__name { font-weight: var(--zx-weight-semibold); }
64
+ .zx-account-menu__secondary { color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); font-weight: 400; }
41
65
  .zx-account-menu__chevron { display: inline-flex; }
42
66
 
43
67
  .zx-account-menu[data-compact="true"] {
@@ -48,6 +72,7 @@
48
72
  display: inline-grid;
49
73
  grid-template-columns: auto;
50
74
  inline-size: auto;
75
+ padding: var(--zx-space-1);
51
76
  }
52
77
 
53
78
  .zx-account-menu[data-compact="true"] .zx-account-menu__identity,
@@ -61,7 +86,7 @@
61
86
  gap: 2px;
62
87
  margin: calc(-1 * var(--zx-space-1)) calc(-1 * var(--zx-space-1)) var(--zx-space-1);
63
88
  border-block-end: 1px solid var(--zx-color-overlay-divider);
64
- padding: var(--zx-space-3);
89
+ padding: var(--zx-space-2) var(--zx-space-3);
65
90
  }
66
91
 
67
92
  .zx-account-menu__panel-identity span {
@@ -124,6 +124,18 @@ export class AccountMenu extends Component {
124
124
  return this;
125
125
  }
126
126
 
127
+ /**
128
+ * Shows the avatar alone or the avatar with the name and secondary line — for a navigation
129
+ * column that narrows to icons and widens again. The trigger keeps its full accessible name
130
+ * either way.
131
+ * @param {boolean} compact
132
+ * @returns {this}
133
+ */
134
+ setCompact(compact) {
135
+ this.el.dataset.compact = String(Boolean(compact));
136
+ return this;
137
+ }
138
+
127
139
  /** Replaces account actions. @param {MenuButtonItem[]} items @returns {this} */
128
140
  setItems(items) {
129
141
  this._menu.setItems(items);
@@ -48,7 +48,7 @@
48
48
  }
49
49
 
50
50
  .zx-activity-item__actor {
51
- font-weight: 700;
51
+ font-weight: var(--zx-weight-semibold);
52
52
  }
53
53
 
54
54
  .zx-activity-item__title {
@@ -200,6 +200,9 @@ export class ActivityItem extends Component {
200
200
  headingChildren.push(h('span', { class: 'zx-activity-item__actor' }, actor));
201
201
  }
202
202
  if (title !== null) {
203
+ // The flex gap separates the two on screen; the space separates them in the heading's text,
204
+ // which is what a screen reader reads ("Jonas Weber set…", not "Jonas Weberset…").
205
+ if (headingChildren.length) headingChildren.push(' ');
203
206
  headingChildren.push(h('span', { class: 'zx-activity-item__title' }, title));
204
207
  }
205
208
  const heading = headingChildren.length ? h(`h${normalizeHeadingLevel(activity.headingLevel)}`, {
@@ -15,8 +15,7 @@
15
15
  margin: 0;
16
16
  color: var(--zx-color-text-muted);
17
17
  font-size: var(--zx-text-sm);
18
- font-weight: 700;
19
- letter-spacing: 0.02em;
18
+ font-weight: var(--zx-weight-semibold);
20
19
  }
21
20
 
22
21
  .zx-activity-list__items {
@@ -28,7 +27,7 @@
28
27
  }
29
28
 
30
29
  .zx-activity-list__items > .zx-activity-item:not(:last-child) {
31
- border-block-end: 1px solid var(--zx-color-border);
30
+ border-block-end: 1px solid var(--zx-color-border-subtle);
32
31
  }
33
32
 
34
33
  .zx-activity-list__empty,
@@ -62,3 +61,39 @@
62
61
  .zx-activity-list__skeleton > .zx-skeleton-text {
63
62
  padding-block-start: var(--zx-space-1);
64
63
  }
64
+
65
+ /* ------------------------------------------------------------------ compact -- */
66
+
67
+ /*
68
+ * `variant: 'compact'` — one line per entry at any width, for a summary card narrower than 30rem:
69
+ * who and what read as one sentence that wraps as text, the time keeps the trailing end of the
70
+ * first line instead of stacking under it, and metadata follows as a muted second line. These
71
+ * rules outrank the entry's own narrow-container layout, which would stack the header.
72
+ */
73
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__article {
74
+ gap: var(--zx-space-2);
75
+ padding-block: var(--zx-space-2);
76
+ }
77
+
78
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__main {
79
+ gap: calc(var(--zx-space-1) / 2);
80
+ }
81
+
82
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__header {
83
+ display: flex;
84
+ flex-wrap: nowrap;
85
+ align-items: baseline;
86
+ justify-content: flex-start;
87
+ gap: var(--zx-space-2);
88
+ }
89
+
90
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__heading {
91
+ display: block;
92
+ flex: 1 1 auto;
93
+ }
94
+
95
+ /* The state badge sits on the first line with the time, not centred against a wrapped sentence. */
96
+ .zx-activity-list[data-variant="compact"] .zx-activity-item__state {
97
+ align-self: baseline;
98
+ margin-inline-start: 0;
99
+ }
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
  import { Component } from '../../core/component.js';
3
3
  import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
4
+ import { captureItemFocus, restoreItemFocus } from '../../core/keyboard.js';
4
5
  import { uid } from '../../core/util.js';
5
6
  import { skeleton, skeletonText } from '../skeleton/skeleton.js';
6
7
  import { ActivityItem } from '../activity-item/activity-item.js';
@@ -9,6 +10,13 @@ import { ActivityItem } from '../activity-item/activity-item.js';
9
10
  /** @typedef {string|number|Node|Component|null} ActivityListContent */
10
11
  /** @typedef {string|((item:ActivityItemOptions,index:number)=>unknown)|null} ActivityGroupReader */
11
12
  /** @typedef {(group:unknown,items:ActivityItemOptions[])=>ActivityListContent} ActivityGroupLabel */
13
+ /**
14
+ * Entry layout. `'default'` gives each entry its avatar, a heading row, and room for content;
15
+ * below 30rem the time moves under the heading. `'compact'` is one line per entry at any width —
16
+ * actor and summary reading as one sentence, the time at the trailing end, a small avatar, and
17
+ * metadata as a muted second line — for a summary card narrower than 30rem.
18
+ * @typedef {'default'|'compact'} ActivityListVariant
19
+ */
12
20
 
13
21
  /**
14
22
  * @typedef {Object} ActivityGroup
@@ -22,6 +30,8 @@ import { ActivityItem } from '../activity-item/activity-item.js';
22
30
  * @property {ActivityGroupReader} [groupBy=null] Property or resolver used for stable grouping.
23
31
  * @property {ActivityGroupLabel|null} [groupLabel=null] Group-heading renderer.
24
32
  * @property {string} [label='Activity'] Accessible collection name.
33
+ * @property {ActivityListVariant} [variant='default'] Entry layout, reflected as `data-variant` on
34
+ * the root. `'compact'` also makes an avatar descriptor without a `size` small.
25
35
  * @property {boolean} [loading=false] Whether initial or incremental activity is loading.
26
36
  * @property {number} [loadingCount=3] Placeholder rows shown for an empty loading list.
27
37
  * @property {string} [loadingText='Loading activity'] Loading status text.
@@ -48,6 +58,7 @@ export class ActivityList extends Component {
48
58
  groupBy: null,
49
59
  groupLabel: null,
50
60
  label: 'Activity',
61
+ variant: 'default',
51
62
  loading: false,
52
63
  loadingCount: 3,
53
64
  loadingText: 'Loading activity',
@@ -78,6 +89,8 @@ export class ActivityList extends Component {
78
89
  this._itemComponents = [];
79
90
  this._groupSequence = 0;
80
91
  root.setAttribute('aria-label', String(this.options.label || 'Activity'));
92
+ this._variant = normalizeActivityListVariant(this.options.variant);
93
+ if (this._variant === 'compact') root.dataset.variant = 'compact';
81
94
  try {
82
95
  this._renderList();
83
96
  return root;
@@ -196,8 +209,33 @@ export class ActivityList extends Component {
196
209
  if (!this._createdRoot) restoreTarget(this.el, this._original);
197
210
  }
198
211
 
199
- /** @private @returns {void} */
212
+ /**
213
+ * Rebuilds the list. Items are regrouped on every mutation, so the rows are new; focus that sat
214
+ * on an item's control is put back on the same control of the same activity (matched by `id`,
215
+ * else by position), so a live feed prepending entries does not throw the reader to `<body>`.
216
+ * @private
217
+ * @returns {void}
218
+ */
200
219
  _renderList() {
220
+ const root = /** @type {HTMLElement} */ (this.el);
221
+ const focus = captureItemFocus(root, (element) => {
222
+ const index = this._itemComponents.findIndex((item) => item.el?.contains(element));
223
+ if (index < 0) return null;
224
+ const component = this._itemComponents[index];
225
+ return { key: { id: component.getActivity().id ?? null, index }, element: component.el };
226
+ });
227
+ this._renderItems();
228
+ restoreItemFocus(root, focus, (key) => {
229
+ const memo = /** @type {{id: unknown, index: number}} */ (key);
230
+ const match = memo.id === null ? undefined
231
+ : this._itemComponents.find((item) => Object.is(item.getActivity().id ?? null, memo.id));
232
+ const fallback = match ?? this._itemComponents[Math.min(memo.index, this._itemComponents.length - 1)];
233
+ return fallback?.el ?? null;
234
+ });
235
+ }
236
+
237
+ /** @private @returns {void} */
238
+ _renderItems() {
201
239
  this._destroyItems();
202
240
  this._groupSequence = 0;
203
241
  const root = /** @type {HTMLElement} */ (this.el);
@@ -229,6 +267,7 @@ export class ActivityList extends Component {
229
267
  for (const item of group.items) {
230
268
  const component = new ActivityItem(null, {
231
269
  ...item,
270
+ avatar: this._variant === 'compact' ? compactAvatar(item.avatar) : item.avatar,
232
271
  headingLevel: item.headingLevel ?? this.options.itemHeadingLevel
233
272
  });
234
273
  this._itemComponents.push(component);
@@ -362,6 +401,27 @@ function normalizeHeadingLevel(value, fallback) {
362
401
  return /** @type {1|2|3|4|5|6} */ (level >= 1 && level <= 6 ? level : fallback);
363
402
  }
364
403
 
404
+ /**
405
+ * Resolves the entry layout; anything but `'compact'` is the default layout.
406
+ * @param {unknown} value Requested variant.
407
+ * @returns {ActivityListVariant}
408
+ */
409
+ export function normalizeActivityListVariant(value) {
410
+ return value === 'compact' ? 'compact' : 'default';
411
+ }
412
+
413
+ /**
414
+ * A compact entry is one line tall, so an avatar descriptor that leaves its size open gets the
415
+ * small one. An explicit size, an Element, or no avatar passes through untouched.
416
+ * @param {ActivityItemOptions['avatar']} avatar Avatar descriptor, Element, or null.
417
+ * @returns {ActivityItemOptions['avatar']}
418
+ */
419
+ function compactAvatar(avatar) {
420
+ if (!isPlainObject(avatar)) return avatar;
421
+ const descriptor = /** @type {import('../avatar/avatar.js').AvatarOptions} */ (avatar);
422
+ return descriptor.size == null ? { ...descriptor, size: 'sm' } : descriptor;
423
+ }
424
+
365
425
  /** @param {unknown} value @returns {number} */
366
426
  function normalizeLoadingCount(value) {
367
427
  const count = Math.trunc(Number(value));
@@ -26,6 +26,17 @@
26
26
  border-radius: var(--zx-radius-full);
27
27
  }
28
28
 
29
+ /* A tone is identity from the theme's categorical palette. An inline `color` overrides it. */
30
+ .zx-app-icon[data-tone="blue"] { --zx-app-icon-color: var(--zx-color-tone-blue-solid); }
31
+ .zx-app-icon[data-tone="teal"] { --zx-app-icon-color: var(--zx-color-tone-teal-solid); }
32
+ .zx-app-icon[data-tone="green"] { --zx-app-icon-color: var(--zx-color-tone-green-solid); }
33
+ .zx-app-icon[data-tone="amber"] { --zx-app-icon-color: var(--zx-color-tone-amber-solid); }
34
+ .zx-app-icon[data-tone="orange"] { --zx-app-icon-color: var(--zx-color-tone-orange-solid); }
35
+ .zx-app-icon[data-tone="red"] { --zx-app-icon-color: var(--zx-color-tone-red-solid); }
36
+ .zx-app-icon[data-tone="pink"] { --zx-app-icon-color: var(--zx-color-tone-pink-solid); }
37
+ .zx-app-icon[data-tone="violet"] { --zx-app-icon-color: var(--zx-color-tone-violet-solid); }
38
+ .zx-app-icon[data-tone="slate"] { --zx-app-icon-color: var(--zx-color-tone-slate-solid); }
39
+
29
40
  .zx-app-icon__surface {
30
41
  position: absolute;
31
42
  z-index: 0;
@@ -62,7 +73,7 @@
62
73
  .zx-app-icon[data-glass="none"] .zx-app-icon__surface::after { display: none; }
63
74
 
64
75
  .zx-app-icon[data-selected] .zx-app-icon__surface {
65
- outline: none;
76
+ outline-color: transparent;
66
77
  box-shadow: 0 0 0 1px var(--zx-color-bg-page),
67
78
  0 0 0 3px var(--zx-app-icon-selected-ring),
68
79
  var(--zx-app-icon-shadow-hover),
@@ -153,7 +164,7 @@
153
164
  background: var(--zx-color-danger);
154
165
  color: var(--zx-color-text-invert);
155
166
  font-size: 9px;
156
- font-weight: 700;
167
+ font-weight: var(--zx-weight-semibold);
157
168
  line-height: 1;
158
169
  }
159
170
 
@@ -166,7 +177,7 @@
166
177
 
167
178
  .zx-app-icon:is(a, button, [role="button"]):not(:disabled):not([aria-disabled="true"]):focus-visible .zx-app-icon__surface,
168
179
  :is(a, button, [role="button"]):not(:disabled):not([aria-disabled="true"]):focus-visible .zx-app-icon__surface {
169
- outline: none;
180
+ outline-color: transparent;
170
181
  box-shadow: var(--zx-focus-ring), var(--zx-app-icon-shadow-hover), var(--zx-app-icon-halo);
171
182
  }
172
183
 
@@ -3,10 +3,16 @@ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
3
3
  import { icon as renderIcon } from '../../core/icons.js';
4
4
  import { isCssColor } from '../../core/util.js';
5
5
 
6
+ /** @typedef {'blue'|'teal'|'green'|'amber'|'orange'|'red'|'pink'|'violet'|'slate'} AppIconTone */
7
+ /** The categorical tones a `tone` option accepts. @type {ReadonlyArray<AppIconTone>} */
8
+ export const APP_ICON_TONES = Object.freeze(['blue', 'teal', 'green', 'amber', 'orange', 'red', 'pink', 'violet', 'slate']);
9
+
6
10
  /**
7
11
  * @typedef {Object} AppIconOptions
8
12
  * @property {string|Node|(()=>string|Node)|null} [icon=null] Icon name, node, or lazy renderer.
9
- * @property {string|null} [color=null] Identity colour; falls back to the accent colour.
13
+ * @property {string|null} [color=null] Identity colour, any CSS colour; takes precedence over `tone`.
14
+ * @property {AppIconTone|null} [tone=null] Categorical identity colour from the theme's tone palette,
15
+ * drawn with its `--zx-color-tone-<tone>-solid` fill. Without `color` or `tone` the icon takes the accent.
10
16
  * @property {number|string} [size=36] Edge length in pixels or any CSS length.
11
17
  * @property {number|string} [iconSize='52%'] Glyph size.
12
18
  * @property {string|null} [label=null] Accessible image label; null makes the icon decorative.
@@ -28,6 +34,7 @@ export class AppIcon extends Component {
28
34
  static defaults = {
29
35
  icon: null,
30
36
  color: null,
37
+ tone: null,
31
38
  size: 36,
32
39
  iconSize: '52%',
33
40
  label: null,
@@ -80,6 +87,9 @@ export class AppIcon extends Component {
80
87
  this.el.style.setProperty('--zx-app-icon-glyph-size', glyphSize);
81
88
  if (isCssColor(state.color)) this.el.style.setProperty('--zx-app-icon-color', String(state.color));
82
89
  else this.el.style.removeProperty('--zx-app-icon-color');
90
+ // An inline colour outranks the tone rule in the stylesheet, which is what makes `color` win.
91
+ if (APP_ICON_TONES.includes(state.tone)) this.el.dataset.tone = state.tone;
92
+ else delete this.el.dataset.tone;
83
93
  this.el.dataset.glass = normalizeGlass(state.glass);
84
94
  this.el.dataset.shape = normalizeShape(state.shape);
85
95
  this.el.toggleAttribute('data-selected', Boolean(state.selected));
@@ -34,6 +34,16 @@
34
34
  .zx-app-rail[data-orientation="horizontal"] .zx-app-rail__list { flex-direction: row; }
35
35
  .zx-app-rail__row { display: flex; }
36
36
 
37
+ /* Where a section of the tree ends on a rail: a short rule, since a rail has no room for a heading. */
38
+ .zx-app-rail__separator {
39
+ flex: none;
40
+ align-self: center;
41
+ background: var(--zx-color-border-subtle);
42
+ }
43
+
44
+ .zx-app-rail[data-orientation="vertical"] .zx-app-rail__separator { inline-size: var(--zx-space-4); block-size: 1px; margin-block: var(--zx-space-1); }
45
+ .zx-app-rail[data-orientation="horizontal"] .zx-app-rail__separator { inline-size: 1px; block-size: var(--zx-space-4); margin-inline: var(--zx-space-1); }
46
+
37
47
  .zx-app-rail__item {
38
48
  position: relative;
39
49
  display: inline-grid;
@@ -48,13 +58,19 @@
48
58
  text-decoration: none;
49
59
  }
50
60
 
61
+ /* Hover, an open flyout, and a branch over the active route are one light step; the current page
62
+ is the neutral current fill. Neither is the accent: where you are is not something you picked. */
51
63
  .zx-app-rail__item:hover,
52
64
  .zx-app-rail__item:focus-visible,
53
65
  .zx-app-rail__item[aria-expanded="true"],
54
- .zx-app-rail__item[data-active-descendant="true"],
55
- .zx-app-rail__item[aria-current="page"] {
66
+ .zx-app-rail__item[data-active-descendant="true"] {
56
67
  background: var(--zx-color-bg-hover);
57
- color: var(--zx-color-accent);
68
+ color: var(--zx-color-text);
69
+ }
70
+
71
+ .zx-app-rail__item[aria-current="page"] {
72
+ background: var(--zx-color-bg-current);
73
+ color: var(--zx-color-text);
58
74
  }
59
75
 
60
76
  .zx-app-rail__item[aria-disabled="true"] {
@@ -86,44 +102,53 @@
86
102
  * A minimized sidebar is the same navigation as an expanded one, so the panel a rail item opens
87
103
  * has to read as a slice of the tree rather than as a menu that happens to hang off it: the same
88
104
  * icon gutter, the same item height, labels that wrap instead of truncating, and the same active
89
- * recipe — filled pill, docked marker, heavier label.
105
+ * recipe — the neutral current fill and a heavier label.
90
106
  *
91
107
  * The panel is portaled to the overlay host, so `.zx-app-sidebar` descendant selectors and the
92
108
  * `--zx-app-sidebar-*` scale declared on that root cannot reach it. The scale is re-declared here
93
- * against the same density-aware tier-2 tokens the sidebar derives it from, so the two surfaces
94
- * move together when a token moves rather than agreeing on a number frozen twice.
109
+ * against the same tier-2 control and spacing tokens the sidebar derives it from, so the two
110
+ * surfaces move together when a token moves rather than agreeing on a number frozen twice.
95
111
  */
96
112
  .zx-app-rail__popover {
97
113
  --zx-app-rail-gutter: var(--zx-space-2);
98
114
  --zx-app-rail-icon: var(--zx-space-6);
99
- --zx-app-rail-item-gap: var(--zx-space-3);
100
- --zx-app-rail-item-height: calc(var(--zx-control-height) + var(--zx-space-2));
101
- --zx-app-rail-pad-x: var(--zx-space-3);
102
- --zx-app-rail-row-gap: var(--zx-space-1);
103
- --zx-app-rail-radius: var(--zx-radius-xl);
104
- --zx-app-rail-marker: 3px;
105
- --zx-app-rail-hover: color-mix(in srgb, var(--zx-color-text) 7%, transparent);
106
- --zx-app-rail-press: color-mix(in srgb, var(--zx-color-text) 14%, transparent);
115
+ --zx-app-rail-item-gap: var(--zx-space-2);
116
+ --zx-app-rail-item-height: var(--zx-control-height);
117
+ --zx-app-rail-pad-x: var(--zx-space-2);
118
+ --zx-app-rail-row-gap: 2px;
119
+ --zx-app-rail-radius: var(--zx-radius-md);
120
+ --zx-app-rail-hover: var(--zx-color-bg-hover);
121
+ --zx-app-rail-current: var(--zx-color-bg-current);
122
+ --zx-app-rail-press: color-mix(in srgb, var(--zx-color-text) 12%, transparent);
107
123
  min-inline-size: 240px;
108
124
  }
109
125
 
110
126
  /* `styles/zx.css` imports this file before `dropdown.css`, so the two properties the flyout has
111
- to take back from the generic overlay surface — the padding, which is the gutter the marker
112
- docks into, and the width the panel may grow to, which is the tree's own 280px — have to
127
+ to take back from the generic overlay surface — the padding, which is the tree's own gutter,
128
+ and the width the panel may grow to, which is the tree's own 280px — have to
113
129
  out-specify it instead of merely following it. The viewport clamp the dropdown guarantees is
114
130
  carried over rather than dropped. */
115
131
  .zx-dropdown.zx-app-rail__popover {
116
132
  max-inline-size: min(280px, calc(100vw - (2 * var(--zx-space-3))));
117
- padding: var(--zx-space-2) var(--zx-app-rail-gutter);
133
+ padding: var(--zx-space-1) var(--zx-app-rail-gutter);
118
134
  }
119
135
 
120
136
  /* The branch's own label, naming what the panel is a slice of. It aligns with the row padding
121
137
  rather than with the labels under it: a caption for the list, not a row in it. */
122
138
  .zx-app-rail__flyout-title {
123
- padding: var(--zx-space-1) var(--zx-app-rail-pad-x) var(--zx-space-2);
139
+ padding: var(--zx-space-1) var(--zx-app-rail-pad-x);
124
140
  color: var(--zx-color-text-muted);
125
141
  font-size: var(--zx-text-xs);
126
- font-weight: 700;
142
+ font-weight: var(--zx-weight-semibold);
143
+ line-height: 1.3;
144
+ }
145
+
146
+ /* A section inside a flyout keeps its heading, in the same quiet caption as the panel's title. */
147
+ .zx-app-rail__flyout-section {
148
+ padding: var(--zx-space-2) var(--zx-app-rail-pad-x) var(--zx-space-1);
149
+ color: var(--zx-color-text-muted);
150
+ font-size: var(--zx-text-xs);
151
+ font-weight: var(--zx-weight-semibold);
127
152
  line-height: 1.3;
128
153
  }
129
154
 
@@ -152,7 +177,9 @@
152
177
  border: 0;
153
178
  border-radius: var(--zx-app-rail-radius);
154
179
  background: transparent;
155
- padding-block: var(--zx-space-1);
180
+ /* The icon column fills the row exactly; a row without one keeps a space-1 margin round its
181
+ label. Either way a single-line row is one control tall, and a wrapped one grows. */
182
+ padding-block: min(var(--zx-space-1), (var(--zx-app-rail-item-height) - var(--zx-app-rail-icon)) / 2);
156
183
  padding-inline: var(--zx-app-rail-pad-x);
157
184
  color: var(--zx-color-text);
158
185
  font-size: var(--zx-text-md);
@@ -166,39 +193,6 @@
166
193
  .zx-app-rail__flyout-item { min-block-size: 40px; }
167
194
  }
168
195
 
169
- /* The marker, docked to the panel's own leading edge exactly as the tree's is docked to the
170
- sidebar's: it sits in the panel's gutter, so every row lines up however deep the flyout chain
171
- runs. On a right-hand rail the panel opens toward the workspace and the marker moves to the
172
- edge facing the screen, which is the rule `.zx-app-sidebar[data-side="right"]` states. */
173
- .zx-app-rail__flyout-item::before {
174
- content: "";
175
- position: absolute;
176
- inset-block: 50%;
177
- inset-inline-start: calc(-1 * var(--zx-app-rail-gutter));
178
- inline-size: var(--zx-app-rail-marker);
179
- border-start-end-radius: var(--zx-radius-full);
180
- border-end-end-radius: var(--zx-radius-full);
181
- background: var(--zx-color-accent);
182
- opacity: 0;
183
- }
184
-
185
- .zx-app-rail__popover[data-side="right"] .zx-app-rail__flyout-item::before {
186
- inset-inline: auto calc(-1 * var(--zx-app-rail-gutter));
187
- border-radius: 0;
188
- border-start-start-radius: var(--zx-radius-full);
189
- border-end-start-radius: var(--zx-radius-full);
190
- }
191
-
192
- .zx-app-rail__flyout-item[aria-current="page"]::before {
193
- inset-block: 25%;
194
- opacity: 1;
195
- }
196
-
197
- .zx-app-rail__flyout-item[data-active-descendant="true"][aria-expanded="false"]::before {
198
- inset-block: 38%;
199
- opacity: .55;
200
- }
201
-
202
196
  .zx-app-rail__flyout-item-icon {
203
197
  grid-column: 1;
204
198
  display: flex;
@@ -247,34 +241,28 @@
247
241
  }
248
242
 
249
243
  .zx-app-rail__flyout-item[data-active-descendant="true"][aria-expanded="false"] {
250
- color: var(--zx-color-accent);
251
- font-weight: 600;
244
+ font-weight: var(--zx-weight-semibold);
252
245
  }
253
246
 
254
- .zx-app-rail__flyout-item[aria-current="page"] {
255
- background: var(--zx-color-accent);
256
- color: var(--zx-color-on-accent);
257
- font-weight: 600;
247
+ .zx-app-rail__flyout-item[data-active-descendant="true"][aria-expanded="false"] .zx-app-rail__flyout-item-icon {
248
+ color: var(--zx-color-text);
258
249
  }
259
250
 
260
- .zx-app-rail__flyout-item[aria-current="page"]:hover {
261
- background: color-mix(in srgb, var(--zx-color-text) 8%, var(--zx-color-accent));
251
+ /* The current page, as in the tree: the neutral current fill, the full text colour and a heavier
252
+ label, with the shared focus ring drawn on top when a keyboard user arrives on it. */
253
+ .zx-app-rail__flyout-item[aria-current="page"] {
254
+ background: var(--zx-app-rail-current);
255
+ color: var(--zx-color-text);
256
+ font-weight: var(--zx-weight-semibold);
262
257
  }
263
258
 
264
259
  .zx-app-rail__flyout-item[aria-current="page"]:active {
265
- background: color-mix(in srgb, var(--zx-color-text) 16%, var(--zx-color-accent));
266
- }
267
-
268
- /* An accent glow around an accent fill is a ring nobody can see, so the pill draws an inner line
269
- in its own text colour first — the same repair the tree makes. */
270
- .zx-app-rail__flyout-item[aria-current="page"]:focus-visible {
271
- box-shadow: inset 0 0 0 2px var(--zx-color-on-accent), var(--zx-focus-ring);
260
+ background: var(--zx-app-rail-press);
272
261
  }
273
262
 
274
263
  .zx-app-rail__flyout-item[aria-current="page"] .zx-app-rail__flyout-item-icon,
275
- .zx-app-rail__flyout-item[aria-current="page"] .zx-app-rail__flyout-item-badge,
276
264
  .zx-app-rail__flyout-item[aria-current="page"] .zx-app-rail__flyout-item-disclosure {
277
- color: inherit;
265
+ color: var(--zx-color-text);
278
266
  }
279
267
 
280
268
  /* Unavailable, and still readable: the icon carries the dimming so the label can keep the muted
@@ -296,23 +284,15 @@
296
284
  box-shadow var(--zx-dur-fast) var(--zx-ease);
297
285
  }
298
286
 
299
- .zx-app-rail__flyout-item::before {
300
- transition: opacity var(--zx-dur-base) var(--zx-ease),
301
- inset-block var(--zx-dur-base) var(--zx-ease);
302
- }
303
-
304
287
  .zx-app-rail__flyout-item:active { transform: scale(.985); }
305
288
  }
306
289
 
307
- /* Forced colours drop the fill and the marker together; the system's selected pair carries the
308
- state there instead. */
290
+ /* Forced colours drop the fill; the system's selected pair carries the state there instead. */
309
291
  @media (forced-colors: active) {
310
292
  .zx-app-rail__flyout-item[aria-current="page"] {
311
293
  background: Highlight;
312
294
  color: HighlightText;
313
295
  }
314
-
315
- .zx-app-rail__flyout-item::before { background: Highlight; }
316
296
  }
317
297
 
318
298
  @media (prefers-reduced-transparency: reduce) {