@zeyos/zx 4.3.4 → 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.
- package/CHANGELOG.md +438 -0
- package/README.md +49 -10
- package/dist/types/components/activity-item/activity-item.d.ts +351 -0
- package/dist/types/components/activity-list/activity-list.d.ts +217 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +33 -3
- package/dist/types/components/avatar/avatar.d.ts +46 -7
- package/dist/types/components/badge/badge.d.ts +21 -1
- package/dist/types/components/button/button.d.ts +23 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -0
- package/dist/types/components/card/card.d.ts +7 -0
- package/dist/types/components/card-view/card-view.d.ts +11 -3
- package/dist/types/components/card-view/record-card.d.ts +7 -1
- package/dist/types/components/chart/chart.d.ts +10 -0
- package/dist/types/components/checklist/checklist.d.ts +9 -0
- package/dist/types/components/code-editor/code-editor.d.ts +149 -0
- package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
- package/dist/types/components/context-menu/context-menu.d.ts +18 -2
- package/dist/types/components/date-picker/date-picker.d.ts +8 -0
- package/dist/types/components/date-picker/time-picker.d.ts +7 -0
- package/dist/types/components/datebox/date-range-box.d.ts +5 -1
- package/dist/types/components/datebox/datebox.d.ts +7 -2
- package/dist/types/components/dialog/dialog.d.ts +75 -6
- package/dist/types/components/dropdown/dropdown.d.ts +2 -2
- package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
- package/dist/types/components/field/field.d.ts +24 -2
- package/dist/types/components/fieldset/fieldset.d.ts +19 -1
- package/dist/types/components/file-item/file-item.d.ts +262 -0
- package/dist/types/components/file-list/file-list.d.ts +171 -0
- package/dist/types/components/filter/filter.d.ts +91 -36
- package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +146 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
- package/dist/types/components/launcher/launcher.d.ts +278 -26
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
- package/dist/types/components/menu-button/menu-button.d.ts +37 -42
- package/dist/types/components/message/message.d.ts +126 -17
- package/dist/types/components/modal/modal.d.ts +61 -2
- package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
- package/dist/types/components/panel/panel.d.ts +7 -0
- package/dist/types/components/search/search.d.ts +7 -0
- package/dist/types/components/select/color.d.ts +12 -0
- package/dist/types/components/select/glyphs.d.ts +124 -0
- package/dist/types/components/select/select.d.ts +15 -3
- package/dist/types/components/sort-control/sort-control.d.ts +105 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +288 -0
- package/dist/types/components/tabbox/tabbox.d.ts +16 -0
- package/dist/types/components/table/table.d.ts +190 -1
- package/dist/types/components/table-view/table-view.d.ts +11 -5
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/timebox/timebox.d.ts +7 -0
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/value-list/value-list.d.ts +8 -0
- package/dist/types/components/view/record-view.d.ts +96 -5
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/icons.d.ts +37 -22
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +15 -1
- package/dist/types/index.d.ts +16 -3
- package/dist/types/internal/app-rail.d.ts +10 -0
- package/dist/types/internal/menu-items.d.ts +134 -0
- package/dist/types/zeyos/connect.d.ts +6 -0
- package/dist/types/zeyos/form.d.ts +50 -9
- package/dist/types/zeyos/index.d.ts +1 -1
- package/dist/types/zeyos/query.d.ts +79 -0
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +100 -14
- package/dist/types/zeyos/view.d.ts +10 -0
- package/dist/zx-zeyos.esm.js +2206 -489
- package/dist/zx-zeyos.esm.min.js +56 -1
- package/dist/zx.css +3322 -724
- package/dist/zx.esm.js +12244 -5809
- package/dist/zx.esm.min.js +58 -3
- package/dist/zx.global.js +12246 -5811
- package/dist/zx.global.min.js +58 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1683 -146
- package/docs/llms.md +777 -110
- package/docs/llms.txt +14 -8
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/activity-item/activity-item.css +137 -0
- package/src/components/activity-item/activity-item.js +602 -0
- package/src/components/activity-list/activity-list.css +99 -0
- package/src/components/activity-list/activity-list.js +409 -0
- package/src/components/app-icon/app-icon.css +1 -1
- package/src/components/app-rail/app-rail.css +191 -22
- package/src/components/app-sidebar/app-sidebar.css +349 -24
- package/src/components/app-sidebar/app-sidebar.js +86 -11
- package/src/components/aurora/aurora.css +5 -3
- package/src/components/avatar/avatar.css +32 -7
- package/src/components/avatar/avatar.js +72 -8
- package/src/components/badge/badge.css +54 -2
- package/src/components/badge/badge.js +29 -2
- package/src/components/button/button.css +74 -8
- package/src/components/button/button.js +44 -1
- package/src/components/calendar/calendar.css +46 -46
- package/src/components/calendar/calendar.js +33 -3
- package/src/components/card/card.css +2 -2
- package/src/components/card/card.js +12 -1
- package/src/components/card-view/card-view.css +10 -7
- package/src/components/card-view/card-view.js +25 -9
- package/src/components/card-view/record-card.js +7 -3
- package/src/components/chart/chart.css +4 -1
- package/src/components/chart/chart.js +12 -0
- package/src/components/check-button/check-button.css +10 -2
- package/src/components/checklist/checklist.css +9 -2
- package/src/components/checklist/checklist.js +11 -5
- package/src/components/checklist/field-adapter.js +3 -1
- package/src/components/code-editor/code-editor.css +68 -0
- package/src/components/code-editor/code-editor.js +306 -0
- package/src/components/code-editor/field-adapter.js +34 -0
- package/src/components/context-menu/context-menu.css +87 -5
- package/src/components/context-menu/context-menu.js +22 -30
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +8 -3
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-picker.js +22 -8
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/month-picker.js +3 -3
- package/src/components/date-picker/time-picker.css +1 -1
- package/src/components/date-picker/time-picker.js +20 -8
- package/src/components/datebox/date-range-box.css +79 -2
- package/src/components/datebox/date-range-box.js +13 -2
- package/src/components/datebox/datebox.css +79 -2
- package/src/components/datebox/datebox.js +13 -2
- package/src/components/dialog/dialog.css +26 -0
- package/src/components/dialog/dialog.js +174 -70
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +7 -5
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +182 -0
- package/src/components/entity-ref/entity-ref.js +496 -0
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +68 -26
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +28 -5
- package/src/components/file-item/file-item.css +88 -0
- package/src/components/file-item/file-item.js +505 -0
- package/src/components/file-list/file-list.css +51 -0
- package/src/components/file-list/file-list.js +320 -0
- package/src/components/filter/filter.css +27 -1
- package/src/components/filter/filter.js +205 -47
- package/src/components/filter-panel/filter-panel.css +104 -0
- package/src/components/filter-panel/filter-panel.js +964 -0
- package/src/components/finder/finder.css +15 -13
- package/src/components/form/form.css +5 -4
- package/src/components/form/form.js +11 -2
- package/src/components/grid/grid.css +1 -1
- package/src/components/groupbox/groupbox.css +1 -1
- package/src/components/kanban-view/kanban-card.js +247 -0
- package/src/components/kanban-view/kanban-view.css +226 -8
- package/src/components/kanban-view/kanban-view.js +255 -12
- package/src/components/launcher/launcher.css +107 -25
- package/src/components/launcher/launcher.js +557 -63
- package/src/components/list-toolbar/list-toolbar.css +194 -0
- package/src/components/list-toolbar/list-toolbar.js +851 -0
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/menu-button/menu-button.css +87 -5
- package/src/components/menu-button/menu-button.js +86 -46
- package/src/components/message/message.css +38 -5
- package/src/components/message/message.js +230 -17
- package/src/components/modal/modal.css +15 -0
- package/src/components/modal/modal.js +159 -3
- package/src/components/navigation-bar/navigation-bar.css +95 -11
- package/src/components/navigation-bar/navigation-bar.js +171 -4
- package/src/components/number-field/field-adapter.js +1 -0
- package/src/components/number-field/number-field.css +70 -0
- package/src/components/number-field/number-field.js +2 -0
- package/src/components/pagination/pagination.css +8 -6
- package/src/components/panel/panel.css +1 -1
- package/src/components/panel/panel.js +13 -2
- package/src/components/rating/rating.css +7 -0
- package/src/components/search/search.js +17 -5
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +6 -1
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +86 -47
- package/src/components/sheet/sheet.css +11 -8
- package/src/components/slider/slider.css +16 -2
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +20 -0
- package/src/components/sort-control/sort-control.js +140 -0
- package/src/components/stat-tile/stat-tile.css +208 -0
- package/src/components/stat-tile/stat-tile.js +601 -0
- package/src/components/tabbox/tabbox.css +32 -27
- package/src/components/tabbox/tabbox.js +17 -2
- package/src/components/table/table.css +155 -31
- package/src/components/table/table.js +386 -33
- package/src/components/table-view/table-view.css +8 -2
- package/src/components/table-view/table-view.js +21 -6
- package/src/components/tag-picker/tag-picker.css +86 -6
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/timebox/timebox.js +18 -5
- package/src/components/toggle/toggle.css +12 -1
- package/src/components/toolbar/toolbar.js +2 -2
- package/src/components/tooltip/tooltip.css +4 -0
- package/src/components/tooltip/tooltip.js +10 -3
- package/src/components/tree/tree.css +25 -16
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/value-list/value-list.js +22 -6
- package/src/components/view/record-view.css +5 -0
- package/src/components/view/record-view.js +100 -7
- package/src/core/breakpoint.js +1 -1
- package/src/core/icons.js +259 -57
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +21 -2
- package/src/index.js +30 -3
- package/src/internal/app-rail.js +18 -1
- package/src/internal/menu-items.js +142 -0
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +67 -27
- package/src/zeyos/index.js +1 -1
- package/src/zeyos/query.js +153 -0
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +68 -5
- package/src/zeyos/view.js +6 -0
- package/styles/base.css +56 -0
- package/styles/icons.css +11 -1
- package/styles/print.css +192 -0
- package/styles/tokens/dark.css +113 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +100 -31
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +11 -1
- package/styles/tokens/density.css +0 -9
|
@@ -1,4 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* AppSidebar — the surface an application is judged by before it is read.
|
|
3
|
+
*
|
|
4
|
+
* Everything here hangs off one spacing scale declared on the root. Item height, icon gutter,
|
|
5
|
+
* row gap, group gap and indentation are derived from the tier-2 control and spacing tokens
|
|
6
|
+
* rather than picked per rule, so a theme that changes `--zx-space-unit` or
|
|
7
|
+
* `--zx-control-height` changes the rhythm of the whole nav instead of one row of it.
|
|
8
|
+
*
|
|
9
|
+
* Three deliberate choices worth stating:
|
|
10
|
+
*
|
|
11
|
+
* - **Labels wrap, they never truncate.** German runs 20-35% longer than English and a nav is
|
|
12
|
+
* where that shows first: "Belege & Rechnungen" is two lines in a 16.5rem sidebar. An ellipsis
|
|
13
|
+
* there hides the word that distinguishes one destination from another, so the row grows
|
|
14
|
+
* instead and the fixed icon column keeps the second line aligned under the first.
|
|
15
|
+
* - **The active state never rests on colour alone.** It is a neutral fill and a heavier label —
|
|
16
|
+
* where you are, not something you picked, so it takes the `--zx-color-bg-current` role rather
|
|
17
|
+
* than the accent. The weight survives greyscale and a reduced-transparency page, and forced
|
|
18
|
+
* colours swap the fill for the system's own selected pair. Nothing is docked to the edge.
|
|
19
|
+
* - **A child never starts further in than its parent's label.** A text-only child lines up with
|
|
20
|
+
* the words above it; a child with its own icon steps in only far enough for that icon to clear
|
|
21
|
+
* the guide line. A parent list without icons indents its children by a short text step.
|
|
22
|
+
*/
|
|
23
|
+
|
|
1
24
|
.zx-app-sidebar {
|
|
25
|
+
/* The scale. A row is exactly one control tall, the icon column is as wide as the 24px module
|
|
26
|
+
chips at the default unit, and every gap and inset is a step of the spacing scale. */
|
|
27
|
+
--zx-app-sidebar-gutter: var(--zx-space-2);
|
|
28
|
+
--zx-app-sidebar-icon: var(--zx-space-6);
|
|
29
|
+
--zx-app-sidebar-item-gap: var(--zx-space-2);
|
|
30
|
+
--zx-app-sidebar-item-height: var(--zx-control-height);
|
|
31
|
+
--zx-app-sidebar-pad-x: var(--zx-space-2);
|
|
32
|
+
--zx-app-sidebar-row-gap: 2px;
|
|
33
|
+
--zx-app-sidebar-group-gap: var(--zx-space-3);
|
|
34
|
+
/* The indent under a parent list that has no icon column to hand down. */
|
|
35
|
+
--zx-app-sidebar-indent: var(--zx-space-3);
|
|
36
|
+
/* The indent of a child list that brings icons of its own: enough for them to clear the guide,
|
|
37
|
+
not a whole icon column, so a deep branch never walks across the sidebar. */
|
|
38
|
+
--zx-app-sidebar-icon-indent: var(--zx-space-4);
|
|
39
|
+
--zx-app-sidebar-radius: var(--zx-radius-md);
|
|
40
|
+
/* Three steps of one overlay: hover, the current row, and press. The hover and current roles
|
|
41
|
+
mix the *text* colour into whatever sits behind, which moves the fill away from the page in
|
|
42
|
+
whichever direction the theme runs, so a sidebar on the grey chrome tone and one on a white
|
|
43
|
+
surface both read correctly. Hover stays below the current fill, so pointing at a row never
|
|
44
|
+
looks like being on it; press goes one step past it. */
|
|
45
|
+
--zx-app-sidebar-hover: var(--zx-color-bg-hover);
|
|
46
|
+
--zx-app-sidebar-current: var(--zx-color-bg-current);
|
|
47
|
+
--zx-app-sidebar-press: color-mix(in srgb, var(--zx-color-text) 12%, transparent);
|
|
2
48
|
display: flex;
|
|
3
49
|
min-block-size: 0;
|
|
4
50
|
border: 1px solid var(--zx-color-glass-border);
|
|
@@ -29,57 +75,336 @@
|
|
|
29
75
|
.zx-app-sidebar__expanded[hidden],
|
|
30
76
|
.zx-app-sidebar__rail[hidden] { display: none; }
|
|
31
77
|
|
|
78
|
+
/* The header carries no rule. It is a brand slot above a nav, not a boundary between two kinds
|
|
79
|
+
of content, and a hairline there reads as leftover scaffolding. The footer keeps its rule:
|
|
80
|
+
an identity control below navigation is a different region and the line is doing work. */
|
|
32
81
|
.zx-app-sidebar__header {
|
|
33
82
|
display: grid;
|
|
34
83
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
35
84
|
align-items: center;
|
|
36
85
|
gap: var(--zx-space-2);
|
|
37
|
-
min-block-size: calc(var(--zx-control-height) + var(--zx-space-
|
|
38
|
-
|
|
39
|
-
padding: var(--zx-space-2);
|
|
86
|
+
min-block-size: calc(var(--zx-control-height) + var(--zx-space-4));
|
|
87
|
+
padding: var(--zx-space-2) var(--zx-space-3);
|
|
40
88
|
}
|
|
41
89
|
|
|
42
90
|
.zx-app-sidebar__header-content { min-inline-size: 0; }
|
|
43
|
-
|
|
91
|
+
|
|
92
|
+
/* `collapsible: false` hides the minimise button; the icon-button display rule must not show it. */
|
|
93
|
+
.zx-app-sidebar .zx-app-sidebar__collapse[hidden] { display: none; }
|
|
94
|
+
|
|
95
|
+
/* The block padding is not decoration: the focus ring is a 3px outer glow, and a nav that padded
|
|
96
|
+
its scroll box by less would clip the ring off the first and last row. */
|
|
97
|
+
.zx-app-sidebar__nav {
|
|
98
|
+
min-block-size: 0;
|
|
99
|
+
overflow: auto;
|
|
100
|
+
padding: var(--zx-space-2) var(--zx-app-sidebar-gutter) var(--zx-space-3);
|
|
101
|
+
}
|
|
102
|
+
|
|
44
103
|
.zx-app-sidebar__list,
|
|
45
|
-
.zx-app-sidebar__sublist {
|
|
46
|
-
|
|
104
|
+
.zx-app-sidebar__sublist {
|
|
105
|
+
display: grid;
|
|
106
|
+
gap: var(--zx-app-sidebar-row-gap);
|
|
107
|
+
margin: 0;
|
|
108
|
+
padding: 0;
|
|
109
|
+
list-style: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* A list whose items carry no icons should not pay for an empty gutter; one that has even a
|
|
113
|
+
single icon keeps the column on every row so the labels stay in one line down the list. Each
|
|
114
|
+
list answers for its own rows only — a branch of plain labels under iconned parents drops the
|
|
115
|
+
column without taking it from the parents. */
|
|
116
|
+
.zx-app-sidebar__list:not(:has(> .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon)),
|
|
117
|
+
.zx-app-sidebar__sublist:not(:has(> .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon)) {
|
|
118
|
+
--zx-app-sidebar-icon: 0px;
|
|
119
|
+
--zx-app-sidebar-item-gap: 0px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Where a branch's children start, measured from the row padding, and where the guide that scopes
|
|
123
|
+
them runs. Both are resolved on the parent row — where the parent's own icon column is still
|
|
124
|
+
the one in force — and inherited from there, so a child list that drops its column cannot pull
|
|
125
|
+
the indent or the guide back with it. A row's step is its icon column, or the text indent when
|
|
126
|
+
it has none; level 3 adds its own step to the one level 2 already sits at. */
|
|
127
|
+
.zx-app-sidebar__row[data-level="1"] {
|
|
128
|
+
--zx-app-sidebar-step-1: max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon) + var(--zx-app-sidebar-item-gap));
|
|
129
|
+
--zx-app-sidebar-guide: calc(var(--zx-app-sidebar-pad-x) + max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon)) / 2);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.zx-app-sidebar__row[data-level="2"] {
|
|
133
|
+
--zx-app-sidebar-step-2: calc(var(--zx-app-sidebar-step-1) + max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon) + var(--zx-app-sidebar-item-gap)));
|
|
134
|
+
--zx-app-sidebar-guide: calc(var(--zx-app-sidebar-pad-x) + var(--zx-app-sidebar-step-1) + max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon)) / 2);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Children with icons of their own step in by the shorter icon indent instead: their icons sit
|
|
138
|
+
just past the guide rather than under the parent's label. Text-only children keep lining up
|
|
139
|
+
with the words above them. */
|
|
140
|
+
.zx-app-sidebar__row[data-level="1"]:has(> .zx-app-sidebar__sublist > .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon) {
|
|
141
|
+
--zx-app-sidebar-step-1: max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon-indent));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.zx-app-sidebar__row[data-level="2"]:has(> .zx-app-sidebar__sublist > .zx-app-sidebar__row > .zx-app-sidebar__item .zx-app-sidebar__item-icon) {
|
|
145
|
+
--zx-app-sidebar-step-2: calc(var(--zx-app-sidebar-step-1) + max(var(--zx-app-sidebar-indent), var(--zx-app-sidebar-icon-indent)));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.zx-app-sidebar__sublist { position: relative; margin-block-start: var(--zx-app-sidebar-row-gap); }
|
|
47
149
|
.zx-app-sidebar__sublist[hidden] { display: none; }
|
|
48
150
|
|
|
151
|
+
/* The one rule in the component that carries meaning: a guide dropped from the centre of the
|
|
152
|
+
parent's icon that scopes the open branch to the row that opened it. */
|
|
153
|
+
.zx-app-sidebar__sublist::before {
|
|
154
|
+
content: "";
|
|
155
|
+
position: absolute;
|
|
156
|
+
inset-block: 2px;
|
|
157
|
+
inset-inline-start: var(--zx-app-sidebar-guide);
|
|
158
|
+
inline-size: 1px;
|
|
159
|
+
border-radius: var(--zx-radius-full);
|
|
160
|
+
background: var(--zx-color-border);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Groups are separated by space, not by rules: a top-level branch opens a group, and the row
|
|
164
|
+
after that group closes it. The first row never gets the gap. */
|
|
165
|
+
.zx-app-sidebar__list > .zx-app-sidebar__row:has(> .zx-app-sidebar__sublist),
|
|
166
|
+
.zx-app-sidebar__list > .zx-app-sidebar__row:has(> .zx-app-sidebar__sublist) + .zx-app-sidebar__row {
|
|
167
|
+
margin-block-start: var(--zx-app-sidebar-group-gap);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.zx-app-sidebar__list > .zx-app-sidebar__row:first-child { margin-block-start: 0; }
|
|
171
|
+
|
|
49
172
|
.zx-app-sidebar__item {
|
|
173
|
+
position: relative;
|
|
50
174
|
display: grid;
|
|
51
|
-
|
|
175
|
+
/* The icon track is composed here and not in a variable on the root: a custom property
|
|
176
|
+
substitutes its `var()`s where it is declared, so a track precomputed on the component would
|
|
177
|
+
ignore the list-level override below and keep an empty 36px gutter on an icon-less sidebar. */
|
|
178
|
+
grid-template-columns: calc(var(--zx-app-sidebar-icon) + var(--zx-app-sidebar-item-gap)) minmax(0, 1fr) auto auto;
|
|
52
179
|
align-items: center;
|
|
53
|
-
gap:
|
|
180
|
+
column-gap: 0;
|
|
54
181
|
inline-size: 100%;
|
|
55
|
-
min-block-size: var(--zx-
|
|
56
|
-
border
|
|
57
|
-
border-radius: var(--zx-radius
|
|
182
|
+
min-block-size: var(--zx-app-sidebar-item-height);
|
|
183
|
+
border: 0;
|
|
184
|
+
border-radius: var(--zx-app-sidebar-radius);
|
|
58
185
|
background: transparent;
|
|
59
|
-
|
|
60
|
-
|
|
186
|
+
/* The icon column fills the row exactly; a row without one keeps a space-1 margin round its
|
|
187
|
+
label. Either way a single-line row is one control tall, and a wrapped one grows. */
|
|
188
|
+
padding-block: min(var(--zx-space-1), (var(--zx-app-sidebar-item-height) - var(--zx-app-sidebar-icon)) / 2);
|
|
189
|
+
padding-inline: var(--zx-app-sidebar-pad-x);
|
|
190
|
+
color: var(--zx-color-text);
|
|
191
|
+
font-size: var(--zx-text-md);
|
|
192
|
+
font-weight: 500;
|
|
193
|
+
line-height: 1.35;
|
|
61
194
|
text-align: start;
|
|
62
195
|
text-decoration: none;
|
|
63
196
|
}
|
|
64
197
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.zx-app-sidebar__item:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.zx-app-
|
|
72
|
-
.zx-app-sidebar__item-
|
|
73
|
-
|
|
74
|
-
.zx-app-sidebar__item-
|
|
198
|
+
/* A nav is a touch target before it is a dense list. The drawn row is one control tall, and a
|
|
199
|
+
coarse pointer puts the hit area back to 40px whatever the theme's control height says. */
|
|
200
|
+
@media (pointer: coarse) {
|
|
201
|
+
.zx-app-sidebar__item { min-block-size: 40px; }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.zx-app-sidebar__row[data-level="2"] > .zx-app-sidebar__item { padding-inline-start: calc(var(--zx-app-sidebar-pad-x) + var(--zx-app-sidebar-step-1)); }
|
|
205
|
+
.zx-app-sidebar__row[data-level="3"] > .zx-app-sidebar__item { padding-inline-start: calc(var(--zx-app-sidebar-pad-x) + var(--zx-app-sidebar-step-2)); }
|
|
206
|
+
|
|
207
|
+
.zx-app-sidebar__item-icon {
|
|
208
|
+
grid-column: 1;
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
inline-size: var(--zx-app-sidebar-icon);
|
|
213
|
+
color: var(--zx-color-text-muted);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* No nowrap and no ellipsis: the row grows instead. `hyphens` is what makes that bearable for a
|
|
217
|
+
German compound — where the document declares a language the browser breaks "Rechnungsausgang"
|
|
218
|
+
at a syllable, and `overflow-wrap` is only the last resort behind it for a word that fits
|
|
219
|
+
nowhere at all. */
|
|
220
|
+
.zx-app-sidebar__item-label {
|
|
221
|
+
grid-column: 2;
|
|
222
|
+
min-inline-size: 0;
|
|
223
|
+
hyphens: auto;
|
|
224
|
+
overflow-wrap: break-word;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.zx-app-sidebar__item-badge {
|
|
228
|
+
grid-column: 3;
|
|
229
|
+
margin-inline-start: var(--zx-space-2);
|
|
230
|
+
color: var(--zx-color-text-muted);
|
|
231
|
+
font-size: var(--zx-text-xs);
|
|
232
|
+
font-variant-numeric: tabular-nums;
|
|
233
|
+
line-height: 1;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.zx-app-sidebar__item-disclosure {
|
|
237
|
+
grid-column: 4;
|
|
238
|
+
display: inline-flex;
|
|
239
|
+
margin-inline-start: var(--zx-space-1);
|
|
240
|
+
color: var(--zx-color-text-muted);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.zx-app-sidebar__item:hover { background: var(--zx-app-sidebar-hover); }
|
|
244
|
+
.zx-app-sidebar__item:hover .zx-app-sidebar__item-icon,
|
|
245
|
+
.zx-app-sidebar__item:hover .zx-app-sidebar__item-disclosure { color: var(--zx-color-text); }
|
|
246
|
+
.zx-app-sidebar__item:active { background: var(--zx-app-sidebar-press); }
|
|
247
|
+
|
|
248
|
+
/* A branch that is closed over the active route carries the heavier label, so the way back to
|
|
249
|
+
the current page is visible without opening it; a branch that is open says so with its chevron
|
|
250
|
+
and shows the active row itself. */
|
|
251
|
+
.zx-app-sidebar__item[data-active-descendant="true"][aria-expanded="false"] {
|
|
252
|
+
font-weight: var(--zx-weight-semibold);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.zx-app-sidebar__item[data-active-descendant="true"][aria-expanded="false"] .zx-app-sidebar__item-icon {
|
|
256
|
+
color: var(--zx-color-text);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* The current page: a neutral fill, the full text colour, a heavier label. Focus is a ring on top
|
|
260
|
+
of it, never a replacement for it, so a keyboard user arrowing past the active route can still
|
|
261
|
+
tell which row is current — the shared `--zx-focus-ring` reads on a neutral fill as it does on
|
|
262
|
+
none. */
|
|
263
|
+
.zx-app-sidebar__item[aria-current="page"] {
|
|
264
|
+
background: var(--zx-app-sidebar-current);
|
|
265
|
+
color: var(--zx-color-text);
|
|
266
|
+
font-weight: var(--zx-weight-semibold);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.zx-app-sidebar__item[aria-current="page"]:active {
|
|
270
|
+
background: var(--zx-app-sidebar-press);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.zx-app-sidebar__item[aria-current="page"] .zx-app-sidebar__item-icon,
|
|
274
|
+
.zx-app-sidebar__item[aria-current="page"] .zx-app-sidebar__item-disclosure {
|
|
275
|
+
color: var(--zx-color-text);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* Unavailable, and still readable. A blanket `opacity` on the row took the label under 3:1, which
|
|
279
|
+
is the one thing a destination a user cannot reach still has to do: say where it would go. The
|
|
280
|
+
icon carries the dimming instead, and the label keeps the muted text role. */
|
|
281
|
+
.zx-app-sidebar__item[aria-disabled="true"] {
|
|
282
|
+
color: var(--zx-color-text-muted);
|
|
283
|
+
cursor: not-allowed;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.zx-app-sidebar__item[aria-disabled="true"] .zx-app-sidebar__item-icon { opacity: .55; }
|
|
287
|
+
|
|
288
|
+
.zx-app-sidebar__item[aria-disabled="true"]:hover,
|
|
289
|
+
.zx-app-sidebar__item[aria-disabled="true"]:active { background: transparent; }
|
|
75
290
|
|
|
76
291
|
.zx-app-sidebar__footer {
|
|
77
292
|
border-block-start: 1px solid var(--zx-color-border);
|
|
78
|
-
padding: var(--zx-space-2);
|
|
293
|
+
padding: var(--zx-space-2) var(--zx-space-3);
|
|
79
294
|
}
|
|
80
295
|
.zx-app-sidebar__footer[hidden] { display: none; }
|
|
296
|
+
|
|
81
297
|
.zx-app-sidebar__rail-header { display: grid; gap: var(--zx-space-1); }
|
|
82
298
|
|
|
299
|
+
/*
|
|
300
|
+
* The minimized rail. Only the presentation changes; the same vocabulary applies — the neutral
|
|
301
|
+
* current fill, the full text colour for a branch over the active route, a badge that survives
|
|
302
|
+
* the loss of the label.
|
|
303
|
+
*/
|
|
304
|
+
.zx-app-sidebar__rail .zx-app-rail__item {
|
|
305
|
+
color: var(--zx-color-text-muted);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.zx-app-sidebar__rail .zx-app-rail__item:not([aria-current="page"]):hover,
|
|
309
|
+
.zx-app-sidebar__rail .zx-app-rail__item:not([aria-current="page"])[aria-expanded="true"] {
|
|
310
|
+
background: var(--zx-app-sidebar-hover);
|
|
311
|
+
color: var(--zx-color-text);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.zx-app-sidebar__rail .zx-app-rail__item:active {
|
|
315
|
+
background: var(--zx-app-sidebar-press);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"] {
|
|
319
|
+
background: var(--zx-app-sidebar-current);
|
|
320
|
+
color: var(--zx-color-text);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* A rail item whose flyout holds the active route says so the way a closed branch does in the
|
|
324
|
+
tree: in the full text colour, without a fill of its own. */
|
|
325
|
+
.zx-app-sidebar__rail .zx-app-rail__item[data-active-descendant="true"]:not([aria-current="page"]) {
|
|
326
|
+
color: var(--zx-color-text);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/*
|
|
330
|
+
* The count a minimized rail would otherwise lose. `data-zx-badge` is set by the component from
|
|
331
|
+
* the item's own badge: a number that fits stays a number, and one that does not becomes a dot,
|
|
332
|
+
* because a three-digit pill on a 30px target stops being a badge and starts being a label. The
|
|
333
|
+
* figure itself moves into the control's accessible name either way, so nothing is dropped —
|
|
334
|
+
* only redrawn.
|
|
335
|
+
*/
|
|
336
|
+
.zx-app-sidebar__rail .zx-app-rail__item-badge {
|
|
337
|
+
inset-block-start: 0;
|
|
338
|
+
inset-inline-end: 0;
|
|
339
|
+
min-inline-size: var(--zx-space-4);
|
|
340
|
+
block-size: var(--zx-space-4);
|
|
341
|
+
border-radius: var(--zx-radius-full);
|
|
342
|
+
background: var(--zx-color-accent);
|
|
343
|
+
box-shadow: 0 0 0 2px var(--zx-color-bg-surface);
|
|
344
|
+
padding-inline: var(--zx-space-1);
|
|
345
|
+
color: var(--zx-color-on-accent);
|
|
346
|
+
font-size: var(--zx-text-xs);
|
|
347
|
+
font-variant-numeric: tabular-nums;
|
|
348
|
+
font-weight: var(--zx-weight-semibold);
|
|
349
|
+
line-height: var(--zx-space-4);
|
|
350
|
+
text-align: center;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.zx-app-sidebar__rail .zx-app-rail__item-badge[data-zx-badge="dot"] {
|
|
354
|
+
min-inline-size: 0;
|
|
355
|
+
inline-size: var(--zx-space-2);
|
|
356
|
+
block-size: var(--zx-space-2);
|
|
357
|
+
padding: 0;
|
|
358
|
+
font-size: 0;
|
|
359
|
+
line-height: 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/*
|
|
363
|
+
* Motion. Everything here is decoration on a state change that is already expressed statically,
|
|
364
|
+
* so all of it is opt-in.
|
|
365
|
+
*
|
|
366
|
+
* The expanded tree is re-rendered wholesale on every branch toggle, which is why the disclosure
|
|
367
|
+
* animation is driven by `data-branch-motion` — an attribute the component sets on exactly the
|
|
368
|
+
* sublist it just opened, so the branches that were already open do not replay it.
|
|
369
|
+
*/
|
|
370
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
371
|
+
.zx-app-sidebar__item,
|
|
372
|
+
.zx-app-sidebar__rail .zx-app-rail__item {
|
|
373
|
+
transition: background-color var(--zx-dur-fast) var(--zx-ease),
|
|
374
|
+
color var(--zx-dur-fast) var(--zx-ease),
|
|
375
|
+
box-shadow var(--zx-dur-fast) var(--zx-ease);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.zx-app-sidebar__item:active,
|
|
379
|
+
.zx-app-sidebar__rail .zx-app-rail__item:active { transform: scale(.985); }
|
|
380
|
+
|
|
381
|
+
.zx-app-sidebar__expanded,
|
|
382
|
+
.zx-app-sidebar__rail { animation: zx-app-sidebar-mode var(--zx-dur-base) var(--zx-ease); }
|
|
383
|
+
|
|
384
|
+
.zx-app-sidebar__sublist[data-branch-motion="expand"] { animation: zx-app-sidebar-branch var(--zx-dur-base) var(--zx-ease); }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@keyframes zx-app-sidebar-mode {
|
|
388
|
+
from { opacity: 0; }
|
|
389
|
+
to { opacity: 1; }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
@keyframes zx-app-sidebar-branch {
|
|
393
|
+
from { opacity: 0; transform: translateY(-4px); }
|
|
394
|
+
to { opacity: 1; transform: translateY(0); }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Forced colours drop author backgrounds, so the current fill would disappear. The system's own
|
|
398
|
+
selected-item pair carries the state there instead. */
|
|
399
|
+
@media (forced-colors: active) {
|
|
400
|
+
.zx-app-sidebar__item[aria-current="page"],
|
|
401
|
+
.zx-app-sidebar__rail .zx-app-rail__item[aria-current="page"],
|
|
402
|
+
.zx-app-sidebar__rail .zx-app-rail__item-badge {
|
|
403
|
+
background: Highlight;
|
|
404
|
+
color: HighlightText;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
83
408
|
@media (prefers-reduced-transparency: reduce) {
|
|
84
409
|
.zx-app-sidebar {
|
|
85
410
|
border-color: var(--zx-color-border);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Component } from '../../core/component.js';
|
|
2
2
|
import { h } from '../../core/dom.js';
|
|
3
|
+
import { printf } from '../../core/i18n.js';
|
|
3
4
|
import { icon } from '../../core/icons.js';
|
|
4
5
|
import { uid } from '../../core/util.js';
|
|
5
6
|
import {
|
|
@@ -77,7 +78,9 @@ export class AppSidebar extends Component {
|
|
|
77
78
|
/** @returns {HTMLElement} */
|
|
78
79
|
render() {
|
|
79
80
|
this._createdRoot = this.el === null;
|
|
80
|
-
|
|
81
|
+
// A plain box: the <nav> inside is the landmark. An <aside> here would add an unnamed
|
|
82
|
+
// complementary landmark around every navigation column.
|
|
83
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
81
84
|
this.el = root;
|
|
82
85
|
this._original = this._createdRoot ? null : {
|
|
83
86
|
attributes: Array.from(root.attributes, (attribute) => [attribute.name, attribute.value]),
|
|
@@ -99,17 +102,20 @@ export class AppSidebar extends Component {
|
|
|
99
102
|
setItems(items) {
|
|
100
103
|
this._items = normalizeAppItems(items);
|
|
101
104
|
this._reconcileExpanded();
|
|
102
|
-
if (this._rail)
|
|
103
|
-
|
|
105
|
+
if (this._rail) {
|
|
106
|
+
this._rail.setItems(this._items);
|
|
107
|
+
this._decorateRail();
|
|
108
|
+
} else this._renderTree();
|
|
104
109
|
return this;
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Returns a copy of the normalized items. A leaf has no `children` property, as it was written,
|
|
114
|
+
* rather than the empty list normalization gives it.
|
|
115
|
+
* @returns {AppNavItem[]}
|
|
116
|
+
*/
|
|
108
117
|
getItems() {
|
|
109
|
-
return this._items
|
|
110
|
-
...item,
|
|
111
|
-
children: item.children?.map((child) => ({ ...child }))
|
|
112
|
-
}));
|
|
118
|
+
return copyItems(this._items);
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
/** Sets the active id without routing. @param {string|number|null} id @returns {this} */
|
|
@@ -151,7 +157,10 @@ export class AppSidebar extends Component {
|
|
|
151
157
|
const expanded = !this._expanded.has(key);
|
|
152
158
|
if (expanded) this._expanded.add(key);
|
|
153
159
|
else this._expanded.delete(key);
|
|
154
|
-
if (!this._rail)
|
|
160
|
+
if (!this._rail) {
|
|
161
|
+
this._renderTree();
|
|
162
|
+
if (expanded) this._markBranchMotion(key);
|
|
163
|
+
}
|
|
155
164
|
if (!silent) this.emit('branchchange', {
|
|
156
165
|
item,
|
|
157
166
|
id: item.id,
|
|
@@ -298,7 +307,7 @@ export class AppSidebar extends Component {
|
|
|
298
307
|
const collapseButton = h('button', {
|
|
299
308
|
class: 'zx-app-sidebar__collapse zx-icon-btn',
|
|
300
309
|
type: 'button',
|
|
301
|
-
ariaLabel: 'Minimize application sidebar'
|
|
310
|
+
ariaLabel: this._message('appSidebar.minimize', 'Minimize application sidebar')
|
|
302
311
|
}, icon(this._side === 'right' ? 'chevron-right' : 'chevron-left'));
|
|
303
312
|
collapseButton.hidden = !this.options.collapsible;
|
|
304
313
|
const footer = h('footer', { class: 'zx-app-sidebar__footer' });
|
|
@@ -330,7 +339,7 @@ export class AppSidebar extends Component {
|
|
|
330
339
|
const expandButton = h('button', {
|
|
331
340
|
class: 'zx-app-sidebar__rail-toggle zx-icon-btn',
|
|
332
341
|
type: 'button',
|
|
333
|
-
ariaLabel: 'Expand application sidebar'
|
|
342
|
+
ariaLabel: this._message('appSidebar.expand', 'Expand application sidebar')
|
|
334
343
|
}, icon(this._side === 'right' ? 'chevron-left' : 'chevron-right'));
|
|
335
344
|
railHeader.append(expandButton);
|
|
336
345
|
this._railExpandButton = expandButton;
|
|
@@ -356,6 +365,31 @@ export class AppSidebar extends Component {
|
|
|
356
365
|
});
|
|
357
366
|
this._listenMode(railHost, 'zx-select', (event) => event.stopPropagation());
|
|
358
367
|
this._listenMode(railHost, 'zx-flyoutchange', (event) => event.stopPropagation());
|
|
368
|
+
this._decorateRail();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Keeps a minimized rail's counts readable and audible.
|
|
373
|
+
*
|
|
374
|
+
* A rail destination is icon-only, so its accessible name is the label alone and the badge the
|
|
375
|
+
* expanded tree showed as text is announced by nothing. Here the figure is folded back into the
|
|
376
|
+
* name, and `data-zx-badge` tells the stylesheet whether the count still fits the target or has
|
|
377
|
+
* to be drawn as a dot. Presentation only: no item, option, event, or listener is touched.
|
|
378
|
+
* @returns {void}
|
|
379
|
+
*/
|
|
380
|
+
_decorateRail() {
|
|
381
|
+
const rail = this.refs.rail;
|
|
382
|
+
if (!rail) return;
|
|
383
|
+
for (const control of rail.querySelectorAll('.zx-app-rail__item[data-app-nav-value-id]')) {
|
|
384
|
+
const badge = control.querySelector(':scope > .zx-app-rail__item-badge');
|
|
385
|
+
if (!badge) continue;
|
|
386
|
+
const item = findItem(this._items, control.getAttribute('data-app-nav-value-id'));
|
|
387
|
+
if (!item || item.badge == null) continue;
|
|
388
|
+
const count = String(item.badge);
|
|
389
|
+
badge.dataset.zxBadge = count.length > 2 ? 'dot' : 'count';
|
|
390
|
+
control.setAttribute('aria-label',
|
|
391
|
+
this._message('appSidebar.itemBadge', '%1 (%2)', item.label, count));
|
|
392
|
+
}
|
|
359
393
|
}
|
|
360
394
|
|
|
361
395
|
/** @param {EventTarget} target @param {string} type @param {EventListener} listener @returns {void} */
|
|
@@ -494,6 +528,35 @@ export class AppSidebar extends Component {
|
|
|
494
528
|
}
|
|
495
529
|
}
|
|
496
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Flags the sublist a branch toggle just opened so the stylesheet can animate that one branch.
|
|
533
|
+
*
|
|
534
|
+
* `_renderTree()` rebuilds every row on every toggle, so a transition declared on the sublist
|
|
535
|
+
* alone would replay on branches that never moved. The attribute is written after the rebuild
|
|
536
|
+
* and disappears with the element on the next one, which is why it needs no teardown.
|
|
537
|
+
* @param {string} key Branch id as a string.
|
|
538
|
+
* @returns {void}
|
|
539
|
+
*/
|
|
540
|
+
_markBranchMotion(key) {
|
|
541
|
+
if (!this.refs.list) return;
|
|
542
|
+
const control = [...this.refs.list.querySelectorAll('[data-app-nav-id]')]
|
|
543
|
+
.find((candidate) => candidate.getAttribute('data-app-nav-id') === key) ?? null;
|
|
544
|
+
const sublist = control?.parentElement?.querySelector(':scope > .zx-app-sidebar__sublist');
|
|
545
|
+
if (sublist instanceof HTMLElement) sublist.dataset.branchMotion = 'expand';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Resolves a message through the host translator, falling back to the built-in English text.
|
|
550
|
+
* @param {string} key Message key.
|
|
551
|
+
* @param {string} fallback Built-in text, with `%1`-style placeholders.
|
|
552
|
+
* @param {...unknown} args Interpolation values.
|
|
553
|
+
* @returns {string}
|
|
554
|
+
*/
|
|
555
|
+
_message(key, fallback, ...args) {
|
|
556
|
+
const message = this.msg(key, ...args);
|
|
557
|
+
return message === key ? printf(fallback, args) : message;
|
|
558
|
+
}
|
|
559
|
+
|
|
497
560
|
/** @returns {void} */
|
|
498
561
|
_reconcileExpanded() {
|
|
499
562
|
const branchIds = new Set();
|
|
@@ -584,5 +647,17 @@ function normalizeSide(orientation, side) {
|
|
|
584
647
|
|
|
585
648
|
/** Fired when a destination/action is activated. @event AppSidebar#select @type {CustomEvent<AppNavSelectDetail>} */
|
|
586
649
|
/** Fired when an inline branch changes. @event AppSidebar#branchchange @type {CustomEvent<{item: AppNavItem|null, id: string|number|null, expanded: boolean|null, ids: Array<string|number>}>} */
|
|
650
|
+
/**
|
|
651
|
+
* Copies a navigation tree, leaving out the empty `children` lists normalization adds to leaves.
|
|
652
|
+
* @param {AppNavItem[]} items Normalized items.
|
|
653
|
+
* @returns {AppNavItem[]}
|
|
654
|
+
*/
|
|
655
|
+
function copyItems(items) {
|
|
656
|
+
return items.map((item) => {
|
|
657
|
+
const { children, ...rest } = item;
|
|
658
|
+
return children?.length ? { ...rest, children: copyItems(children) } : rest;
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
587
662
|
/** Fired when expanded/minimized presentation changes. @event AppSidebar#collapsechange @type {CustomEvent<{collapsed: boolean}>} */
|
|
588
663
|
/** Fired when a minimized flyout changes. @event AppSidebar#flyoutchange @type {CustomEvent<{item: AppNavItem, id: string|number, open: boolean}>} */
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
.zx-aurora {
|
|
2
|
+
/* The brand accent, then three chart series: decoration takes categorical colour, never the
|
|
3
|
+
status set, so the light behind a page never reads as a success or a warning. */
|
|
2
4
|
--zx-aurora-color-1: var(--zx-color-accent);
|
|
3
|
-
--zx-aurora-color-2: var(--zx-color-
|
|
4
|
-
--zx-aurora-color-3: var(--zx-color-
|
|
5
|
-
--zx-aurora-color-4: var(--zx-color-
|
|
5
|
+
--zx-aurora-color-2: var(--zx-color-chart-1);
|
|
6
|
+
--zx-aurora-color-3: var(--zx-color-chart-3);
|
|
7
|
+
--zx-aurora-color-4: var(--zx-color-chart-4);
|
|
6
8
|
--zx-aurora-core: 7%;
|
|
7
9
|
--zx-aurora-wash: 4%;
|
|
8
10
|
--zx-aurora-faint: 2%;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The fallback is the avatar most people see, so it carries an identity of its own: initials on a
|
|
3
|
+
* tint from the categorical tone palette, picked by a stable hash of the name and written as
|
|
4
|
+
* `data-tone` by the component. No ring — the tint is the edge. The three presets are steps of the
|
|
5
|
+
* spacing scale: 24 / 32 / 40px at the default unit.
|
|
6
|
+
*/
|
|
1
7
|
.zx-avatar {
|
|
2
|
-
--zx-avatar-size: var(--zx-
|
|
8
|
+
--zx-avatar-size: var(--zx-space-7);
|
|
3
9
|
position: relative;
|
|
4
10
|
display: inline-grid;
|
|
5
11
|
flex: none;
|
|
@@ -11,12 +17,22 @@
|
|
|
11
17
|
vertical-align: middle;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
|
-
.zx-avatar[data-size="sm"] { --zx-avatar-size:
|
|
15
|
-
.zx-avatar[data-size="lg"] { --zx-avatar-size:
|
|
20
|
+
.zx-avatar[data-size="sm"] { --zx-avatar-size: var(--zx-space-6); }
|
|
21
|
+
.zx-avatar[data-size="lg"] { --zx-avatar-size: var(--zx-space-8); }
|
|
16
22
|
.zx-avatar[data-shape="circle"] { border-radius: 50%; }
|
|
17
23
|
.zx-avatar[data-shape="rounded"] { border-radius: var(--zx-radius-md); }
|
|
18
24
|
.zx-avatar[data-shape="square"] { border-radius: 0; }
|
|
19
25
|
|
|
26
|
+
.zx-avatar[data-tone="blue"] { --zx-avatar-bg: var(--zx-color-tone-blue-bg); --zx-avatar-fg: var(--zx-color-tone-blue-fg); }
|
|
27
|
+
.zx-avatar[data-tone="teal"] { --zx-avatar-bg: var(--zx-color-tone-teal-bg); --zx-avatar-fg: var(--zx-color-tone-teal-fg); }
|
|
28
|
+
.zx-avatar[data-tone="green"] { --zx-avatar-bg: var(--zx-color-tone-green-bg); --zx-avatar-fg: var(--zx-color-tone-green-fg); }
|
|
29
|
+
.zx-avatar[data-tone="amber"] { --zx-avatar-bg: var(--zx-color-tone-amber-bg); --zx-avatar-fg: var(--zx-color-tone-amber-fg); }
|
|
30
|
+
.zx-avatar[data-tone="orange"] { --zx-avatar-bg: var(--zx-color-tone-orange-bg); --zx-avatar-fg: var(--zx-color-tone-orange-fg); }
|
|
31
|
+
.zx-avatar[data-tone="red"] { --zx-avatar-bg: var(--zx-color-tone-red-bg); --zx-avatar-fg: var(--zx-color-tone-red-fg); }
|
|
32
|
+
.zx-avatar[data-tone="pink"] { --zx-avatar-bg: var(--zx-color-tone-pink-bg); --zx-avatar-fg: var(--zx-color-tone-pink-fg); }
|
|
33
|
+
.zx-avatar[data-tone="violet"] { --zx-avatar-bg: var(--zx-color-tone-violet-bg); --zx-avatar-fg: var(--zx-color-tone-violet-fg); }
|
|
34
|
+
.zx-avatar[data-tone="slate"] { --zx-avatar-bg: var(--zx-color-tone-slate-bg); --zx-avatar-fg: var(--zx-color-tone-slate-fg); }
|
|
35
|
+
|
|
20
36
|
.zx-avatar__image,
|
|
21
37
|
.zx-avatar__fallback {
|
|
22
38
|
inline-size: 100%;
|
|
@@ -35,14 +51,18 @@
|
|
|
35
51
|
display: none;
|
|
36
52
|
}
|
|
37
53
|
|
|
54
|
+
/* Initials at 0.4 of the size, never under 10px; one of them below 24px (the component decides). */
|
|
38
55
|
.zx-avatar__fallback {
|
|
39
56
|
display: grid;
|
|
40
57
|
place-items: center;
|
|
41
|
-
|
|
42
|
-
background: var(--zx-color-bg-muted);
|
|
43
|
-
|
|
44
|
-
font-
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
background: var(--zx-avatar-bg, var(--zx-color-bg-muted));
|
|
60
|
+
color: var(--zx-avatar-fg, var(--zx-color-text-muted));
|
|
61
|
+
font-size: max(10px, calc(var(--zx-avatar-size) * .4));
|
|
62
|
+
font-weight: var(--zx-weight-semibold);
|
|
63
|
+
letter-spacing: 0;
|
|
45
64
|
line-height: 1;
|
|
65
|
+
white-space: nowrap;
|
|
46
66
|
}
|
|
47
67
|
|
|
48
68
|
.zx-avatar__status {
|
|
@@ -59,3 +79,8 @@
|
|
|
59
79
|
.zx-avatar__status[data-status="online"] { background: var(--zx-color-success); }
|
|
60
80
|
.zx-avatar__status[data-status="away"] { background: var(--zx-color-warning); }
|
|
61
81
|
.zx-avatar__status[data-status="busy"] { background: var(--zx-color-danger); }
|
|
82
|
+
|
|
83
|
+
/* Forced colours drop the tint; a system-coloured edge keeps the fallback a shape. */
|
|
84
|
+
@media (forced-colors: active) {
|
|
85
|
+
.zx-avatar__fallback { border: 1px solid CanvasText; }
|
|
86
|
+
}
|