@zeyos/zx 4.4.0 → 5.0.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 (182) hide show
  1. package/CHANGELOG.md +283 -0
  2. package/README.md +42 -10
  3. package/dist/types/components/activity-list/activity-list.d.ts +22 -0
  4. package/dist/types/components/app-sidebar/app-sidebar.d.ts +5 -3
  5. package/dist/types/components/avatar/avatar.d.ts +46 -7
  6. package/dist/types/components/badge/badge.d.ts +21 -1
  7. package/dist/types/components/button/button.d.ts +15 -0
  8. package/dist/types/components/chart/chart.d.ts +10 -0
  9. package/dist/types/components/checklist/checklist.d.ts +9 -0
  10. package/dist/types/components/code-editor/code-editor.d.ts +6 -17
  11. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  12. package/dist/types/components/datebox/datebox.d.ts +7 -2
  13. package/dist/types/components/dialog/dialog.d.ts +46 -6
  14. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  15. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  16. package/dist/types/components/field/field.d.ts +24 -2
  17. package/dist/types/components/fieldset/fieldset.d.ts +19 -1
  18. package/dist/types/components/file-item/file-item.d.ts +34 -6
  19. package/dist/types/components/form/form.d.ts +12 -1
  20. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  21. package/dist/types/components/launcher/launcher.d.ts +260 -15
  22. package/dist/types/components/list-toolbar/list-toolbar.d.ts +1 -1
  23. package/dist/types/components/menu-button/menu-button.d.ts +33 -5
  24. package/dist/types/components/message/message.d.ts +119 -17
  25. package/dist/types/components/modal/modal.d.ts +13 -2
  26. package/dist/types/components/select/color.d.ts +12 -0
  27. package/dist/types/components/select/glyphs.d.ts +124 -0
  28. package/dist/types/components/select/select.d.ts +11 -3
  29. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  30. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  31. package/dist/types/components/table/table.d.ts +46 -1
  32. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  33. package/dist/types/components/tree/tree.d.ts +13 -0
  34. package/dist/types/core/breakpoint.d.ts +1 -1
  35. package/dist/types/core/icons.d.ts +23 -25
  36. package/dist/types/core/motion.d.ts +59 -0
  37. package/dist/types/core/overlay-host.d.ts +15 -1
  38. package/dist/types/index.d.ts +5 -3
  39. package/dist/types/zeyos/connect.d.ts +6 -0
  40. package/dist/types/zeyos/form.d.ts +50 -9
  41. package/dist/types/zeyos/index.d.ts +1 -1
  42. package/dist/types/zeyos/query.d.ts +79 -0
  43. package/dist/types/zeyos/schema.d.ts +24 -1
  44. package/dist/types/zeyos/table.d.ts +100 -14
  45. package/dist/types/zeyos/view.d.ts +10 -0
  46. package/dist/zx-zeyos.esm.js +1621 -443
  47. package/dist/zx-zeyos.esm.min.js +53 -5
  48. package/dist/zx.css +1411 -660
  49. package/dist/zx.esm.js +1976 -703
  50. package/dist/zx.esm.min.js +56 -8
  51. package/dist/zx.global.js +1978 -705
  52. package/dist/zx.global.min.js +56 -8
  53. package/dist/zx.min.css +1 -1
  54. package/docs/api.json +219 -34
  55. package/docs/llms.md +472 -110
  56. package/docs/llms.txt +3 -3
  57. package/package.json +1 -1
  58. package/src/components/account-menu/account-menu.css +35 -10
  59. package/src/components/activity-item/activity-item.css +1 -1
  60. package/src/components/activity-item/activity-item.js +3 -0
  61. package/src/components/activity-list/activity-list.css +38 -3
  62. package/src/components/activity-list/activity-list.js +34 -0
  63. package/src/components/app-icon/app-icon.css +1 -1
  64. package/src/components/app-rail/app-rail.css +40 -79
  65. package/src/components/app-sidebar/app-sidebar.css +109 -174
  66. package/src/components/app-sidebar/app-sidebar.js +21 -6
  67. package/src/components/aurora/aurora.css +5 -3
  68. package/src/components/avatar/avatar.css +32 -7
  69. package/src/components/avatar/avatar.js +72 -8
  70. package/src/components/badge/badge.css +54 -2
  71. package/src/components/badge/badge.js +29 -2
  72. package/src/components/button/button.css +67 -9
  73. package/src/components/button/button.js +38 -0
  74. package/src/components/calendar/calendar.css +46 -46
  75. package/src/components/card/card.css +2 -2
  76. package/src/components/card-view/card-view.css +9 -7
  77. package/src/components/chart/chart.css +4 -1
  78. package/src/components/chart/chart.js +12 -0
  79. package/src/components/check-button/check-button.css +10 -2
  80. package/src/components/checklist/checklist.css +9 -2
  81. package/src/components/checklist/checklist.js +11 -5
  82. package/src/components/checklist/field-adapter.js +3 -1
  83. package/src/components/code-editor/code-editor.css +6 -5
  84. package/src/components/code-editor/code-editor.js +19 -1
  85. package/src/components/context-menu/context-menu.css +9 -4
  86. package/src/components/copy/copy-button.css +6 -0
  87. package/src/components/copy/copy-input.css +8 -3
  88. package/src/components/data-filter/data-filter.css +5 -4
  89. package/src/components/date-picker/date-picker.css +6 -6
  90. package/src/components/date-picker/date-range-picker.css +3 -3
  91. package/src/components/date-picker/time-picker.css +1 -1
  92. package/src/components/datebox/date-range-box.css +79 -2
  93. package/src/components/datebox/date-range-box.js +13 -2
  94. package/src/components/datebox/datebox.css +79 -2
  95. package/src/components/datebox/datebox.js +13 -2
  96. package/src/components/dialog/dialog.css +26 -0
  97. package/src/components/dialog/dialog.js +99 -17
  98. package/src/components/dropdown/dropdown.css +1 -1
  99. package/src/components/dropdown/dropdown.js +5 -3
  100. package/src/components/empty-state/empty-state.css +2 -2
  101. package/src/components/entity-ref/entity-ref.css +1 -1
  102. package/src/components/entity-ref/entity-ref.js +1 -1
  103. package/src/components/field/field.css +28 -9
  104. package/src/components/field/field.js +68 -26
  105. package/src/components/field-upload/field-upload.css +1 -1
  106. package/src/components/fieldset/fieldset.css +5 -3
  107. package/src/components/fieldset/fieldset.js +28 -5
  108. package/src/components/file-item/file-item.js +144 -10
  109. package/src/components/filter/filter.js +7 -3
  110. package/src/components/filter-panel/filter-panel.css +1 -1
  111. package/src/components/finder/finder.css +15 -13
  112. package/src/components/form/form.css +5 -4
  113. package/src/components/form/form.js +11 -2
  114. package/src/components/grid/grid.css +1 -1
  115. package/src/components/groupbox/groupbox.css +1 -1
  116. package/src/components/kanban-view/kanban-card.js +11 -3
  117. package/src/components/kanban-view/kanban-view.css +39 -15
  118. package/src/components/kanban-view/kanban-view.js +4 -4
  119. package/src/components/launcher/launcher.css +107 -25
  120. package/src/components/launcher/launcher.js +503 -41
  121. package/src/components/list-toolbar/list-toolbar.css +13 -8
  122. package/src/components/list-toolbar/list-toolbar.js +1 -1
  123. package/src/components/master-panel/master-panel.css +2 -0
  124. package/src/components/menu-button/menu-button.css +9 -4
  125. package/src/components/menu-button/menu-button.js +65 -9
  126. package/src/components/message/message.css +33 -3
  127. package/src/components/message/message.js +217 -15
  128. package/src/components/modal/modal.js +18 -3
  129. package/src/components/navigation-bar/navigation-bar.css +18 -9
  130. package/src/components/number-field/field-adapter.js +1 -0
  131. package/src/components/number-field/number-field.css +70 -0
  132. package/src/components/number-field/number-field.js +2 -0
  133. package/src/components/pagination/pagination.css +8 -6
  134. package/src/components/panel/panel.css +1 -1
  135. package/src/components/rating/rating.css +7 -0
  136. package/src/components/select/color.js +22 -0
  137. package/src/components/select/field-adapter.js +2 -0
  138. package/src/components/select/glyphs.js +165 -0
  139. package/src/components/select/select.css +138 -45
  140. package/src/components/select/select.js +83 -47
  141. package/src/components/sheet/sheet.css +11 -8
  142. package/src/components/slider/slider.css +16 -2
  143. package/src/components/slider/slider.js +1 -1
  144. package/src/components/sort-control/sort-control.css +4 -1
  145. package/src/components/stat-tile/stat-tile.css +41 -46
  146. package/src/components/stat-tile/stat-tile.js +17 -6
  147. package/src/components/tabbox/tabbox.css +32 -27
  148. package/src/components/tabbox/tabbox.js +5 -1
  149. package/src/components/table/table.css +104 -29
  150. package/src/components/table/table.js +90 -16
  151. package/src/components/tag-picker/tag-picker.css +83 -5
  152. package/src/components/tag-picker/tag-picker.js +25 -8
  153. package/src/components/timebox/timebox.css +22 -2
  154. package/src/components/toggle/toggle.css +12 -1
  155. package/src/components/toolbar/toolbar.js +2 -2
  156. package/src/components/tooltip/tooltip.css +4 -0
  157. package/src/components/tooltip/tooltip.js +10 -3
  158. package/src/components/tree/tree.css +25 -16
  159. package/src/components/tree/tree.js +31 -0
  160. package/src/components/value-list/value-list.css +3 -3
  161. package/src/components/view/record-view.js +1 -1
  162. package/src/core/breakpoint.js +1 -1
  163. package/src/core/icons.js +193 -60
  164. package/src/core/motion.js +147 -0
  165. package/src/core/overlay-host.js +21 -2
  166. package/src/index.js +5 -3
  167. package/src/internal/app-rail.js +1 -2
  168. package/src/zeyos/connect.js +6 -2
  169. package/src/zeyos/form.js +67 -27
  170. package/src/zeyos/index.js +1 -1
  171. package/src/zeyos/query.js +153 -0
  172. package/src/zeyos/schema.js +144 -27
  173. package/src/zeyos/table.js +68 -5
  174. package/src/zeyos/view.js +6 -0
  175. package/styles/base.css +56 -0
  176. package/styles/icons.css +11 -1
  177. package/styles/tokens/dark.css +113 -37
  178. package/styles/tokens/global.css +121 -27
  179. package/styles/tokens/semantic.css +100 -31
  180. package/styles/tokens/themes.css +7 -6
  181. package/styles/zx.css +0 -1
  182. 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
@@ -56,7 +56,7 @@ exports and as `window.zx.*`.
56
56
  - 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
57
 
58
58
  ## 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.
59
+ 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
60
 
61
61
  ## Talking to ZeyOS
62
62
  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.0.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 {
@@ -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
+ }
@@ -9,6 +9,13 @@ import { ActivityItem } from '../activity-item/activity-item.js';
9
9
  /** @typedef {string|number|Node|Component|null} ActivityListContent */
10
10
  /** @typedef {string|((item:ActivityItemOptions,index:number)=>unknown)|null} ActivityGroupReader */
11
11
  /** @typedef {(group:unknown,items:ActivityItemOptions[])=>ActivityListContent} ActivityGroupLabel */
12
+ /**
13
+ * Entry layout. `'default'` gives each entry its avatar, a heading row, and room for content;
14
+ * below 30rem the time moves under the heading. `'compact'` is one line per entry at any width —
15
+ * actor and summary reading as one sentence, the time at the trailing end, a small avatar, and
16
+ * metadata as a muted second line — for a summary card narrower than 30rem.
17
+ * @typedef {'default'|'compact'} ActivityListVariant
18
+ */
12
19
 
13
20
  /**
14
21
  * @typedef {Object} ActivityGroup
@@ -22,6 +29,8 @@ import { ActivityItem } from '../activity-item/activity-item.js';
22
29
  * @property {ActivityGroupReader} [groupBy=null] Property or resolver used for stable grouping.
23
30
  * @property {ActivityGroupLabel|null} [groupLabel=null] Group-heading renderer.
24
31
  * @property {string} [label='Activity'] Accessible collection name.
32
+ * @property {ActivityListVariant} [variant='default'] Entry layout, reflected as `data-variant` on
33
+ * the root. `'compact'` also makes an avatar descriptor without a `size` small.
25
34
  * @property {boolean} [loading=false] Whether initial or incremental activity is loading.
26
35
  * @property {number} [loadingCount=3] Placeholder rows shown for an empty loading list.
27
36
  * @property {string} [loadingText='Loading activity'] Loading status text.
@@ -48,6 +57,7 @@ export class ActivityList extends Component {
48
57
  groupBy: null,
49
58
  groupLabel: null,
50
59
  label: 'Activity',
60
+ variant: 'default',
51
61
  loading: false,
52
62
  loadingCount: 3,
53
63
  loadingText: 'Loading activity',
@@ -78,6 +88,8 @@ export class ActivityList extends Component {
78
88
  this._itemComponents = [];
79
89
  this._groupSequence = 0;
80
90
  root.setAttribute('aria-label', String(this.options.label || 'Activity'));
91
+ this._variant = normalizeActivityListVariant(this.options.variant);
92
+ if (this._variant === 'compact') root.dataset.variant = 'compact';
81
93
  try {
82
94
  this._renderList();
83
95
  return root;
@@ -229,6 +241,7 @@ export class ActivityList extends Component {
229
241
  for (const item of group.items) {
230
242
  const component = new ActivityItem(null, {
231
243
  ...item,
244
+ avatar: this._variant === 'compact' ? compactAvatar(item.avatar) : item.avatar,
232
245
  headingLevel: item.headingLevel ?? this.options.itemHeadingLevel
233
246
  });
234
247
  this._itemComponents.push(component);
@@ -362,6 +375,27 @@ function normalizeHeadingLevel(value, fallback) {
362
375
  return /** @type {1|2|3|4|5|6} */ (level >= 1 && level <= 6 ? level : fallback);
363
376
  }
364
377
 
378
+ /**
379
+ * Resolves the entry layout; anything but `'compact'` is the default layout.
380
+ * @param {unknown} value Requested variant.
381
+ * @returns {ActivityListVariant}
382
+ */
383
+ export function normalizeActivityListVariant(value) {
384
+ return value === 'compact' ? 'compact' : 'default';
385
+ }
386
+
387
+ /**
388
+ * A compact entry is one line tall, so an avatar descriptor that leaves its size open gets the
389
+ * small one. An explicit size, an Element, or no avatar passes through untouched.
390
+ * @param {ActivityItemOptions['avatar']} avatar Avatar descriptor, Element, or null.
391
+ * @returns {ActivityItemOptions['avatar']}
392
+ */
393
+ function compactAvatar(avatar) {
394
+ if (!isPlainObject(avatar)) return avatar;
395
+ const descriptor = /** @type {import('../avatar/avatar.js').AvatarOptions} */ (avatar);
396
+ return descriptor.size == null ? { ...descriptor, size: 'sm' } : descriptor;
397
+ }
398
+
365
399
  /** @param {unknown} value @returns {number} */
366
400
  function normalizeLoadingCount(value) {
367
401
  const count = Math.trunc(Number(value));
@@ -153,7 +153,7 @@
153
153
  background: var(--zx-color-danger);
154
154
  color: var(--zx-color-text-invert);
155
155
  font-size: 9px;
156
- font-weight: 700;
156
+ font-weight: var(--zx-weight-semibold);
157
157
  line-height: 1;
158
158
  }
159
159
 
@@ -48,13 +48,19 @@
48
48
  text-decoration: none;
49
49
  }
50
50
 
51
+ /* Hover, an open flyout, and a branch over the active route are one light step; the current page
52
+ is the neutral current fill. Neither is the accent: where you are is not something you picked. */
51
53
  .zx-app-rail__item:hover,
52
54
  .zx-app-rail__item:focus-visible,
53
55
  .zx-app-rail__item[aria-expanded="true"],
54
- .zx-app-rail__item[data-active-descendant="true"],
55
- .zx-app-rail__item[aria-current="page"] {
56
+ .zx-app-rail__item[data-active-descendant="true"] {
56
57
  background: var(--zx-color-bg-hover);
57
- color: var(--zx-color-accent);
58
+ color: var(--zx-color-text);
59
+ }
60
+
61
+ .zx-app-rail__item[aria-current="page"] {
62
+ background: var(--zx-color-bg-current);
63
+ color: var(--zx-color-text);
58
64
  }
59
65
 
60
66
  .zx-app-rail__item[aria-disabled="true"] {
@@ -86,44 +92,44 @@
86
92
  * A minimized sidebar is the same navigation as an expanded one, so the panel a rail item opens
87
93
  * has to read as a slice of the tree rather than as a menu that happens to hang off it: the same
88
94
  * icon gutter, the same item height, labels that wrap instead of truncating, and the same active
89
- * recipe — filled pill, docked marker, heavier label.
95
+ * recipe — the neutral current fill and a heavier label.
90
96
  *
91
97
  * The panel is portaled to the overlay host, so `.zx-app-sidebar` descendant selectors and the
92
98
  * `--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.
99
+ * against the same tier-2 control and spacing tokens the sidebar derives it from, so the two
100
+ * surfaces move together when a token moves rather than agreeing on a number frozen twice.
95
101
  */
96
102
  .zx-app-rail__popover {
97
103
  --zx-app-rail-gutter: var(--zx-space-2);
98
104
  --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);
105
+ --zx-app-rail-item-gap: var(--zx-space-2);
106
+ --zx-app-rail-item-height: var(--zx-control-height);
107
+ --zx-app-rail-pad-x: var(--zx-space-2);
108
+ --zx-app-rail-row-gap: 2px;
109
+ --zx-app-rail-radius: var(--zx-radius-md);
110
+ --zx-app-rail-hover: var(--zx-color-bg-hover);
111
+ --zx-app-rail-current: var(--zx-color-bg-current);
112
+ --zx-app-rail-press: color-mix(in srgb, var(--zx-color-text) 12%, transparent);
107
113
  min-inline-size: 240px;
108
114
  }
109
115
 
110
116
  /* `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
117
+ to take back from the generic overlay surface — the padding, which is the tree's own gutter,
118
+ and the width the panel may grow to, which is the tree's own 280px — have to
113
119
  out-specify it instead of merely following it. The viewport clamp the dropdown guarantees is
114
120
  carried over rather than dropped. */
115
121
  .zx-dropdown.zx-app-rail__popover {
116
122
  max-inline-size: min(280px, calc(100vw - (2 * var(--zx-space-3))));
117
- padding: var(--zx-space-2) var(--zx-app-rail-gutter);
123
+ padding: var(--zx-space-1) var(--zx-app-rail-gutter);
118
124
  }
119
125
 
120
126
  /* The branch's own label, naming what the panel is a slice of. It aligns with the row padding
121
127
  rather than with the labels under it: a caption for the list, not a row in it. */
122
128
  .zx-app-rail__flyout-title {
123
- padding: var(--zx-space-1) var(--zx-app-rail-pad-x) var(--zx-space-2);
129
+ padding: var(--zx-space-1) var(--zx-app-rail-pad-x);
124
130
  color: var(--zx-color-text-muted);
125
131
  font-size: var(--zx-text-xs);
126
- font-weight: 700;
132
+ font-weight: var(--zx-weight-semibold);
127
133
  line-height: 1.3;
128
134
  }
129
135
 
@@ -152,7 +158,9 @@
152
158
  border: 0;
153
159
  border-radius: var(--zx-app-rail-radius);
154
160
  background: transparent;
155
- padding-block: var(--zx-space-1);
161
+ /* The icon column fills the row exactly; a row without one keeps a space-1 margin round its
162
+ label. Either way a single-line row is one control tall, and a wrapped one grows. */
163
+ padding-block: min(var(--zx-space-1), (var(--zx-app-rail-item-height) - var(--zx-app-rail-icon)) / 2);
156
164
  padding-inline: var(--zx-app-rail-pad-x);
157
165
  color: var(--zx-color-text);
158
166
  font-size: var(--zx-text-md);
@@ -166,39 +174,6 @@
166
174
  .zx-app-rail__flyout-item { min-block-size: 40px; }
167
175
  }
168
176
 
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
177
  .zx-app-rail__flyout-item-icon {
203
178
  grid-column: 1;
204
179
  display: flex;
@@ -247,34 +222,28 @@
247
222
  }
248
223
 
249
224
  .zx-app-rail__flyout-item[data-active-descendant="true"][aria-expanded="false"] {
250
- color: var(--zx-color-accent);
251
- font-weight: 600;
225
+ font-weight: var(--zx-weight-semibold);
252
226
  }
253
227
 
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;
228
+ .zx-app-rail__flyout-item[data-active-descendant="true"][aria-expanded="false"] .zx-app-rail__flyout-item-icon {
229
+ color: var(--zx-color-text);
258
230
  }
259
231
 
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));
232
+ /* The current page, as in the tree: the neutral current fill, the full text colour and a heavier
233
+ label, with the shared focus ring drawn on top when a keyboard user arrives on it. */
234
+ .zx-app-rail__flyout-item[aria-current="page"] {
235
+ background: var(--zx-app-rail-current);
236
+ color: var(--zx-color-text);
237
+ font-weight: var(--zx-weight-semibold);
262
238
  }
263
239
 
264
240
  .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);
241
+ background: var(--zx-app-rail-press);
272
242
  }
273
243
 
274
244
  .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
245
  .zx-app-rail__flyout-item[aria-current="page"] .zx-app-rail__flyout-item-disclosure {
277
- color: inherit;
246
+ color: var(--zx-color-text);
278
247
  }
279
248
 
280
249
  /* Unavailable, and still readable: the icon carries the dimming so the label can keep the muted
@@ -296,23 +265,15 @@
296
265
  box-shadow var(--zx-dur-fast) var(--zx-ease);
297
266
  }
298
267
 
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
268
  .zx-app-rail__flyout-item:active { transform: scale(.985); }
305
269
  }
306
270
 
307
- /* Forced colours drop the fill and the marker together; the system's selected pair carries the
308
- state there instead. */
271
+ /* Forced colours drop the fill; the system's selected pair carries the state there instead. */
309
272
  @media (forced-colors: active) {
310
273
  .zx-app-rail__flyout-item[aria-current="page"] {
311
274
  background: Highlight;
312
275
  color: HighlightText;
313
276
  }
314
-
315
- .zx-app-rail__flyout-item::before { background: Highlight; }
316
277
  }
317
278
 
318
279
  @media (prefers-reduced-transparency: reduce) {