@7365admin1/layer-common 3.2.2-staging.79 → 3.2.2-staging.81
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/assets/css/shell.css +258 -0
- package/assets/css/tokens.css +220 -0
- package/components/Layout/Header.vue +19 -8
- package/components/Layout/NavigationDrawer.vue +46 -1
- package/components/PassInformation.vue +104 -45
- package/components/ScanVisitorQRCode.vue +35 -7
- package/composables/useThemePreference.ts +63 -0
- package/composables/useVisitor.ts +6 -1
- package/nuxt.config.ts +31 -0
- package/package.json +3 -2
- package/plugins/vuetify.ts +30 -1
- package/utils/status.test.ts +41 -0
- package/utils/status.ts +54 -0
- package/utils/theme.test.ts +325 -7
- package/utils/theme.ts +268 -137
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE RESPONSIVE SHELL PRIMITIVES.
|
|
3
|
+
*
|
|
4
|
+
* The prototype is not responsive. Rendered at 360 and 768 its sidebar stays a
|
|
5
|
+
* fixed 252px, the content column is clipped, and the hamburger only hides the
|
|
6
|
+
* rail instead of overlaying it - so there is no reference to copy and this
|
|
7
|
+
* behaviour is designed rather than transcribed. It is built from the same
|
|
8
|
+
* tokens, so nothing here introduces a colour, a size or a radius of its own.
|
|
9
|
+
*
|
|
10
|
+
* Breakpoints, and why these:
|
|
11
|
+
* >= 1024 the layout the design draws: 252px rail beside the content.
|
|
12
|
+
* < 1024 the rail becomes an overlay drawer behind the hamburger. 252px
|
|
13
|
+
* out of 1024 is a quarter of the screen; below that the content
|
|
14
|
+
* column stops being usable before the rail stops fitting.
|
|
15
|
+
* < 768 one column. Filter rows stack, the breadcrumb truncates.
|
|
16
|
+
* < 600 modals go near-full-screen with a 16px inset.
|
|
17
|
+
*
|
|
18
|
+
* TWO KINDS OF RULE LIVE HERE, and the distinction is deliberate:
|
|
19
|
+
*
|
|
20
|
+
* 1. Token adjustments at a breakpoint. These change what `--content-pad-x`
|
|
21
|
+
* and friends MEAN on a small screen. They reach every surface that reads
|
|
22
|
+
* the tokens and touch nothing that does not, which is why the padding is
|
|
23
|
+
* done this way instead of as a rule on someone else's markup.
|
|
24
|
+
*
|
|
25
|
+
* 2. Named primitives (`.app-shell`, `.app-topbar`, `.filter-row`,
|
|
26
|
+
* `.table-scroll`, `.modal-body`). These are what Phase 2 onwards builds
|
|
27
|
+
* the shell out of. NOTHING in Phase 1 applies them to a module screen -
|
|
28
|
+
* the primitives exist and are reviewable; rolling them across the
|
|
29
|
+
* product is the next phase's work.
|
|
30
|
+
*
|
|
31
|
+
* The one thing here that DOES reach the existing product is the touch-target
|
|
32
|
+
* rule at the bottom, and that is on purpose - see the note above it.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/* ------------------------------------------------------------------ */
|
|
36
|
+
/* 1. What the tokens mean on a smaller screen. */
|
|
37
|
+
/* ------------------------------------------------------------------ */
|
|
38
|
+
|
|
39
|
+
@media (max-width: 1023.98px) {
|
|
40
|
+
:root {
|
|
41
|
+
--content-pad-x: 20px;
|
|
42
|
+
--content-pad-y: 18px;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@media (max-width: 767.98px) {
|
|
47
|
+
:root {
|
|
48
|
+
--content-pad-x: 16px;
|
|
49
|
+
--content-pad-y: 16px;
|
|
50
|
+
--content-pad-bottom: 28px;
|
|
51
|
+
/* The row keeps its vertical rhythm and gives back side padding, which is
|
|
52
|
+
what buys a table its columns before it has to scroll. */
|
|
53
|
+
--cellpad-x: 14px;
|
|
54
|
+
--grid-gap: 10px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@media (max-width: 599.98px) {
|
|
59
|
+
:root {
|
|
60
|
+
--content-pad-x: 14px;
|
|
61
|
+
/* 22px on a 360px screen is a headline that wraps to three lines. */
|
|
62
|
+
--fs-h1: 19px;
|
|
63
|
+
--fs-kpi: 26px;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ------------------------------------------------------------------ */
|
|
68
|
+
/* 2. The primitives. */
|
|
69
|
+
/* ------------------------------------------------------------------ */
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The shell. A flex row at desktop; at < 1024 the rail is out of flow (it is
|
|
73
|
+
* an overlay) and the content column takes the full width.
|
|
74
|
+
*
|
|
75
|
+
* `min-width: 0` on the content column is not decoration. A flex child's
|
|
76
|
+
* default `min-width: auto` refuses to shrink below its content, so ONE wide
|
|
77
|
+
* table pushes the whole column past the viewport and the page scrolls
|
|
78
|
+
* sideways with the sidebar attached. This is the line that stops that.
|
|
79
|
+
*/
|
|
80
|
+
.app-shell {
|
|
81
|
+
display: flex;
|
|
82
|
+
min-height: 100vh;
|
|
83
|
+
background: var(--bg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.app-shell__rail {
|
|
87
|
+
flex: 0 0 var(--sidebar-w);
|
|
88
|
+
width: var(--sidebar-w);
|
|
89
|
+
background: var(--sidebar);
|
|
90
|
+
border-right: 1px solid var(--border);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.app-shell__content {
|
|
94
|
+
flex: 1 1 auto;
|
|
95
|
+
min-width: 0;
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.app-content {
|
|
101
|
+
flex: 1 1 auto;
|
|
102
|
+
min-width: 0;
|
|
103
|
+
padding: var(--content-pad-y) var(--content-pad-x) var(--content-pad-bottom);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@media (max-width: 1023.98px) {
|
|
107
|
+
.app-shell__rail {
|
|
108
|
+
display: none;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The 58px top bar. Its two ends are fixed; the middle is what gives way. */
|
|
113
|
+
.app-topbar {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 12px;
|
|
117
|
+
height: var(--topbar-h);
|
|
118
|
+
min-height: var(--topbar-h);
|
|
119
|
+
padding: 0 var(--content-pad-x);
|
|
120
|
+
background: var(--card);
|
|
121
|
+
border-bottom: 1px solid var(--border);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.app-topbar__crumb {
|
|
125
|
+
flex: 1 1 auto;
|
|
126
|
+
min-width: 0;
|
|
127
|
+
font-size: var(--fs-breadcrumb);
|
|
128
|
+
color: var(--muted);
|
|
129
|
+
/* Truncates rather than wrapping: a two-line breadcrumb would break the
|
|
130
|
+
58px bar, and the site name is the half worth losing first. */
|
|
131
|
+
overflow: hidden;
|
|
132
|
+
white-space: nowrap;
|
|
133
|
+
text-overflow: ellipsis;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.app-topbar__actions {
|
|
137
|
+
flex: 0 0 auto;
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
gap: 8px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The hamburger only exists where the rail does not. */
|
|
144
|
+
.app-topbar__toggle {
|
|
145
|
+
display: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@media (max-width: 1023.98px) {
|
|
149
|
+
.app-topbar__toggle {
|
|
150
|
+
display: inline-flex;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* A filter row wraps before it stacks. Wrapping keeps two short selects side
|
|
156
|
+
* by side on a 768px tablet, where forcing them full-width would waste half
|
|
157
|
+
* the row; below 600 there is no room for two, so they stack.
|
|
158
|
+
*/
|
|
159
|
+
.filter-row {
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-wrap: wrap;
|
|
162
|
+
gap: var(--grid-gap);
|
|
163
|
+
align-items: center;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.filter-row > * {
|
|
167
|
+
flex: 1 1 200px;
|
|
168
|
+
min-width: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@media (max-width: 599.98px) {
|
|
172
|
+
.filter-row {
|
|
173
|
+
flex-direction: column;
|
|
174
|
+
align-items: stretch;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.filter-row > * {
|
|
178
|
+
flex: 1 1 auto;
|
|
179
|
+
width: 100%;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* A table scrolls sideways inside its own card rather than reflowing into
|
|
185
|
+
* stacked key/value blocks. The design is a table card and its columns carry
|
|
186
|
+
* meaning by position; restacking them at 360 would be a different design, not
|
|
187
|
+
* a smaller one - and it is the kind of change rule 2 rules out. So the card
|
|
188
|
+
* edge stays put and the table moves within it.
|
|
189
|
+
*/
|
|
190
|
+
.table-scroll {
|
|
191
|
+
overflow-x: auto;
|
|
192
|
+
-webkit-overflow-scrolling: touch;
|
|
193
|
+
overscroll-behavior-x: contain;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.table-scroll > table {
|
|
197
|
+
min-width: max-content;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Modal container. 520px as designed, but never wider or taller than the
|
|
202
|
+
* viewport it has to fit inside, and the BODY is what scrolls - a modal whose
|
|
203
|
+
* whole card scrolls takes its Cancel/Submit footer off screen with it.
|
|
204
|
+
*/
|
|
205
|
+
.modal-card {
|
|
206
|
+
width: min(var(--modal-w), calc(100vw - 32px));
|
|
207
|
+
max-height: calc(100vh - 32px);
|
|
208
|
+
display: flex;
|
|
209
|
+
flex-direction: column;
|
|
210
|
+
border-radius: var(--r-modal);
|
|
211
|
+
background: var(--card);
|
|
212
|
+
box-shadow: var(--shadow-modal);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.modal-body {
|
|
216
|
+
flex: 1 1 auto;
|
|
217
|
+
min-height: 0;
|
|
218
|
+
overflow-y: auto;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@media (max-width: 599.98px) {
|
|
222
|
+
.modal-card {
|
|
223
|
+
width: calc(100vw - 32px);
|
|
224
|
+
max-height: calc(100dvh - 32px);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* ------------------------------------------------------------------ */
|
|
229
|
+
/* 3. Touch targets. This one DOES reach the existing product. */
|
|
230
|
+
/* ------------------------------------------------------------------ */
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* A 36px icon button is comfortable with a mouse and a miss with a thumb.
|
|
234
|
+
* WCAG 2.2 asks for 44x44 on a touch pointer, and this is the one rule in
|
|
235
|
+
* Phase 1 that changes existing screens rather than waiting for a phase to
|
|
236
|
+
* adopt it - because it only applies on a COARSE pointer, so no desktop
|
|
237
|
+
* rendering moves by a pixel, and because leaving it to seven more phases
|
|
238
|
+
* means shipping the mobile-hostile version for all of them.
|
|
239
|
+
*
|
|
240
|
+
* `min-height`/`min-width` only, so nothing shrinks and no layout is forced.
|
|
241
|
+
*/
|
|
242
|
+
@media (pointer: coarse) {
|
|
243
|
+
.v-btn--icon,
|
|
244
|
+
.v-btn--size-small,
|
|
245
|
+
.v-btn--size-x-small {
|
|
246
|
+
min-height: 44px;
|
|
247
|
+
min-width: 44px;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.v-list-item {
|
|
251
|
+
min-height: 44px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.v-selection-control__wrapper {
|
|
255
|
+
min-height: 44px;
|
|
256
|
+
min-width: 44px;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLATFORM REDESIGN - THE DESIGN TOKENS.
|
|
3
|
+
*
|
|
4
|
+
* Not one colour is written down in this file. Every colour token is an ALIAS
|
|
5
|
+
* onto the Vuetify theme variable that already holds it, and the values
|
|
6
|
+
* themselves live in `utils/theme.ts` (`PALETTE`), which `utils/theme.test.ts`
|
|
7
|
+
* measures. Two systems, one source of truth, flowing one way:
|
|
8
|
+
*
|
|
9
|
+
* PALETTE -> LIGHT_THEME/DARK_THEME -> --v-theme-* -> --bg, --card, --muted
|
|
10
|
+
*
|
|
11
|
+
* The aliases are declared on `[class*="v-theme--"]` rather than on `:root`
|
|
12
|
+
* ON PURPOSE. Vuetify puts that class on the application root AND on every
|
|
13
|
+
* `<v-theme-provider>` subtree, so `--card` read inside the `plain-dark`
|
|
14
|
+
* layout, or inside a dialog that forces a theme, resolves to THAT subtree's
|
|
15
|
+
* theme. Declaring the aliases at `:root` - or flipping a class on `<body>`,
|
|
16
|
+
* which is how the prototype does it - would have painted those subtrees with
|
|
17
|
+
* the page's theme instead.
|
|
18
|
+
*
|
|
19
|
+
* Values below that are NOT colours (opacity overlays, shadows, the type
|
|
20
|
+
* scale, the spacing scale) have nothing on the Vuetify side to alias, so they
|
|
21
|
+
* are stated here. The theme-dependent ones are stated twice, once per theme.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/* ------------------------------------------------------------------ */
|
|
25
|
+
/* Colour tokens - aliases only. */
|
|
26
|
+
/* ------------------------------------------------------------------ */
|
|
27
|
+
|
|
28
|
+
[class*="v-theme--"] {
|
|
29
|
+
/* Surfaces */
|
|
30
|
+
--bg: rgb(var(--v-theme-background));
|
|
31
|
+
--sidebar: rgb(var(--v-theme-sidebar));
|
|
32
|
+
--card: rgb(var(--v-theme-surface));
|
|
33
|
+
--border: rgb(var(--v-theme-border));
|
|
34
|
+
|
|
35
|
+
/* Text */
|
|
36
|
+
--text: rgb(var(--v-theme-text-primary));
|
|
37
|
+
--text2: rgb(var(--v-theme-text2));
|
|
38
|
+
--muted: rgb(var(--v-theme-muted));
|
|
39
|
+
|
|
40
|
+
/* Semantic status */
|
|
41
|
+
--ok: rgb(var(--v-theme-success));
|
|
42
|
+
--warn: rgb(var(--v-theme-warning));
|
|
43
|
+
--err: rgb(var(--v-theme-error));
|
|
44
|
+
--info: rgb(var(--v-theme-info));
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* The brand accent, exactly as the design specifies it. `--accent-strong` is
|
|
48
|
+
* the same colour 18% darker and is the ONLY thing a white label may sit on:
|
|
49
|
+
* white on `--accent` measures 3.23:1 in both themes. Everything else - the
|
|
50
|
+
* links, the active nav item, the tab underline, the toggle's on state, the
|
|
51
|
+
* chart lines, `--accent-soft` - uses `--accent` unchanged.
|
|
52
|
+
*/
|
|
53
|
+
--accent: rgb(var(--v-theme-accent));
|
|
54
|
+
--accent-strong: rgb(var(--v-theme-accent-strong));
|
|
55
|
+
--accent-text: rgb(var(--v-theme-accent-text));
|
|
56
|
+
--accent-soft: rgba(var(--v-theme-accent), 0.12);
|
|
57
|
+
--on-accent-strong: #ffffff;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/*
|
|
61
|
+
* Overlays and shadows. Not colours in the Vuetify sense - an overlay is a
|
|
62
|
+
* colour AND an alpha, and a shadow is a whole box-shadow - so these are the
|
|
63
|
+
* only values restated per theme. Equal specificity to the block above, so
|
|
64
|
+
* source order decides: light first, dark second.
|
|
65
|
+
*/
|
|
66
|
+
[class*="v-theme--"] {
|
|
67
|
+
--hover: rgba(20, 22, 26, 0.05);
|
|
68
|
+
--thead: rgba(20, 22, 26, 0.02);
|
|
69
|
+
--shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
|
|
70
|
+
--shadow-modal: 0 24px 64px rgba(10, 12, 16, 0.3);
|
|
71
|
+
--shadow-menu: 0 12px 32px rgba(16, 24, 40, 0.14);
|
|
72
|
+
--scrim: rgba(12, 14, 18, 0.5);
|
|
73
|
+
|
|
74
|
+
/* Soft status backgrounds. Stated by the design literally rather than
|
|
75
|
+
derived from the foreground, so darkening a label for AA leaves its chip
|
|
76
|
+
the colour it was drawn. */
|
|
77
|
+
--ok-bg: rgba(15, 138, 98, 0.1);
|
|
78
|
+
--warn-bg: rgba(190, 140, 25, 0.13);
|
|
79
|
+
--err-bg: rgba(207, 75, 75, 0.1);
|
|
80
|
+
--info-bg: rgba(59, 111, 212, 0.1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.v-theme--dark {
|
|
84
|
+
--hover: rgba(255, 255, 255, 0.06);
|
|
85
|
+
--thead: rgba(255, 255, 255, 0.02);
|
|
86
|
+
--shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
87
|
+
|
|
88
|
+
--ok-bg: rgba(65, 199, 149, 0.12);
|
|
89
|
+
--warn-bg: rgba(220, 175, 78, 0.13);
|
|
90
|
+
--err-bg: rgba(229, 115, 115, 0.12);
|
|
91
|
+
--info-bg: rgba(125, 166, 236, 0.12);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ------------------------------------------------------------------ */
|
|
95
|
+
/* Type, shape and spacing - the same in both themes. */
|
|
96
|
+
/* ------------------------------------------------------------------ */
|
|
97
|
+
|
|
98
|
+
:root {
|
|
99
|
+
/*
|
|
100
|
+
* Manrope is loaded in `nuxt.config.ts` and this token names it, but NOTHING
|
|
101
|
+
* in Phase 1 sets `font-family` on the page. Switching eleven applications to
|
|
102
|
+
* a new typeface before a single screen has been rebuilt would leave the
|
|
103
|
+
* product looking half-migrated; each phase adopts the token as it rebuilds
|
|
104
|
+
* its surfaces.
|
|
105
|
+
*/
|
|
106
|
+
--font-sans: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
107
|
+
"Helvetica Neue", Arial, sans-serif;
|
|
108
|
+
|
|
109
|
+
/* Type scale - size / weight / letter-spacing, straight from the contract. */
|
|
110
|
+
--fs-h1: 22px;
|
|
111
|
+
--fw-h1: 800;
|
|
112
|
+
--ls-h1: -0.01em;
|
|
113
|
+
|
|
114
|
+
--fs-card-title: 15.5px;
|
|
115
|
+
--fw-card-title: 800;
|
|
116
|
+
|
|
117
|
+
--fs-kpi: 30px;
|
|
118
|
+
--fw-kpi: 800;
|
|
119
|
+
--ls-kpi: -0.02em;
|
|
120
|
+
|
|
121
|
+
--fs-table-head: 11px;
|
|
122
|
+
--fw-table-head: 800;
|
|
123
|
+
--ls-table-head: 0.07em;
|
|
124
|
+
|
|
125
|
+
--fs-cell: 13.5px;
|
|
126
|
+
--fw-cell: 600;
|
|
127
|
+
--fs-cell-dense: 13px;
|
|
128
|
+
|
|
129
|
+
--fs-nav: 13px;
|
|
130
|
+
|
|
131
|
+
--fs-section-label: 10.5px;
|
|
132
|
+
--fw-section-label: 800;
|
|
133
|
+
--ls-section-label: 0.09em;
|
|
134
|
+
|
|
135
|
+
--fs-btn: 13.5px;
|
|
136
|
+
--fw-btn: 700;
|
|
137
|
+
--fw-btn-strong: 800;
|
|
138
|
+
|
|
139
|
+
--fs-chip: 11.5px;
|
|
140
|
+
--fs-chip-lg: 12px;
|
|
141
|
+
--fw-chip: 700;
|
|
142
|
+
|
|
143
|
+
--fs-breadcrumb: 12.5px;
|
|
144
|
+
|
|
145
|
+
/* Shape */
|
|
146
|
+
--r-card: 14px;
|
|
147
|
+
--r-inner: 10px;
|
|
148
|
+
--r-inner-sm: 9px;
|
|
149
|
+
--r-inner-lg: 12px;
|
|
150
|
+
--r-tag: 7px;
|
|
151
|
+
--r-pill: 999px;
|
|
152
|
+
--r-modal: 16px;
|
|
153
|
+
--r-menu: 14px;
|
|
154
|
+
|
|
155
|
+
/* Shell metrics */
|
|
156
|
+
--sidebar-w: 252px;
|
|
157
|
+
--topbar-h: 58px;
|
|
158
|
+
--content-pad-y: 22px;
|
|
159
|
+
--content-pad-x: 26px;
|
|
160
|
+
--content-pad-bottom: 40px;
|
|
161
|
+
|
|
162
|
+
/* Controls */
|
|
163
|
+
--input-h: 40px;
|
|
164
|
+
--input-h-compact: 34px;
|
|
165
|
+
--btn-h: 40px;
|
|
166
|
+
--icon-btn: 36px;
|
|
167
|
+
--avatar: 36px;
|
|
168
|
+
--toggle-w: 38px;
|
|
169
|
+
--toggle-h: 22px;
|
|
170
|
+
--grid-gap: 12px;
|
|
171
|
+
--modal-w: 520px;
|
|
172
|
+
--menu-w: 240px;
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
* Table density - the design's own knob. `--cellpad` is the comfortable row
|
|
176
|
+
* padding; put `data-density="compact"` on a table card for the dense one.
|
|
177
|
+
* The accent is the other knob the handoff asks for, and it is a value in
|
|
178
|
+
* `PALETTE` (utils/theme.ts) rather than a runtime switch, because nothing
|
|
179
|
+
* changes it at runtime yet.
|
|
180
|
+
*/
|
|
181
|
+
--cellpad: 15px;
|
|
182
|
+
--cellpad-x: 20px;
|
|
183
|
+
|
|
184
|
+
--motion: 150ms;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
[data-density="compact"] {
|
|
188
|
+
--cellpad: 9px;
|
|
189
|
+
--fs-cell: var(--fs-cell-dense);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* ------------------------------------------------------------------ */
|
|
193
|
+
/* Semantic status tones. */
|
|
194
|
+
/* ------------------------------------------------------------------ */
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* The mapping from a status word to a tone lives in `utils/status.ts`, so that
|
|
198
|
+
* one list serves the chips, the dots and anything else. These classes are
|
|
199
|
+
* what that tone paints with. A chip is `.tone-*` plus its own shape.
|
|
200
|
+
*/
|
|
201
|
+
.tone-ok {
|
|
202
|
+
color: var(--ok);
|
|
203
|
+
--tone-bg: var(--ok-bg);
|
|
204
|
+
}
|
|
205
|
+
.tone-warn {
|
|
206
|
+
color: var(--warn);
|
|
207
|
+
--tone-bg: var(--warn-bg);
|
|
208
|
+
}
|
|
209
|
+
.tone-err {
|
|
210
|
+
color: var(--err);
|
|
211
|
+
--tone-bg: var(--err-bg);
|
|
212
|
+
}
|
|
213
|
+
.tone-info {
|
|
214
|
+
color: var(--info);
|
|
215
|
+
--tone-bg: var(--info-bg);
|
|
216
|
+
}
|
|
217
|
+
.tone-neutral {
|
|
218
|
+
color: var(--text2);
|
|
219
|
+
--tone-bg: var(--hover);
|
|
220
|
+
}
|
|
@@ -3,7 +3,18 @@
|
|
|
3
3
|
<v-app-bar-nav-icon v-if="!props.hideNavIcon" @click="drawer = !drawer"></v-app-bar-nav-icon>
|
|
4
4
|
|
|
5
5
|
<template #append>
|
|
6
|
-
|
|
6
|
+
<!--
|
|
7
|
+
The icon now says which theme you would GET, and the choice is
|
|
8
|
+
remembered - see `composables/useThemePreference.ts`. Same button, same
|
|
9
|
+
place, same one click.
|
|
10
|
+
-->
|
|
11
|
+
<v-btn
|
|
12
|
+
:icon="current === 'dark' ? 'mdi-weather-sunny' : 'mdi-weather-night'"
|
|
13
|
+
:aria-label="current === 'dark' ? 'Switch to light theme' : 'Switch to dark theme'"
|
|
14
|
+
variant="text"
|
|
15
|
+
class="mx-2"
|
|
16
|
+
@click="toggleTheme"
|
|
17
|
+
/>
|
|
7
18
|
|
|
8
19
|
<v-menu offset="10px" :close-on-content-click="false">
|
|
9
20
|
<template #activator="{ props }">
|
|
@@ -45,8 +56,6 @@
|
|
|
45
56
|
</template>
|
|
46
57
|
|
|
47
58
|
<script setup lang="ts">
|
|
48
|
-
import { useTheme } from "vuetify";
|
|
49
|
-
|
|
50
59
|
const props = defineProps({
|
|
51
60
|
hideNavIcon: {
|
|
52
61
|
type: Boolean,
|
|
@@ -64,11 +73,13 @@ const { redirect } = useUtils();
|
|
|
64
73
|
|
|
65
74
|
const { APP_ACCOUNT, APP_MAIN, APP_NAME } = useRuntimeConfig().public;
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
/**
|
|
77
|
+
* This used to be `theme.global.name.value = ...` and nothing else, so the
|
|
78
|
+
* choice was gone on the next reload - and, because these are eleven separate
|
|
79
|
+
* applications, gone again on every hop between them. The composable writes the
|
|
80
|
+
* layer's shared `theme` cookie, and `plugins/vuetify.ts` boots on it.
|
|
81
|
+
*/
|
|
82
|
+
const { current, toggleTheme } = useThemePreference();
|
|
72
83
|
|
|
73
84
|
const { currentUser } = useLocalAuth();
|
|
74
85
|
|
|
@@ -28,9 +28,26 @@
|
|
|
28
28
|
primary-tinted fill (see `NavigationItem.vue`); 0.12 left the fill at
|
|
29
29
|
1.15:1, 0.18 is 1.44:1 light, 1.33:1 dark.
|
|
30
30
|
-->
|
|
31
|
+
<!--
|
|
32
|
+
RESPONSIVE: THE RAIL BECOMES AN OVERLAY BELOW 1024px.
|
|
33
|
+
|
|
34
|
+
`permanent` was hardcoded, which is why the prototype's own behaviour - and
|
|
35
|
+
ours - was a 252px rail still sitting there on a 360px phone with the
|
|
36
|
+
content squeezed into what was left. Dropping `permanent` and naming the
|
|
37
|
+
breakpoint hands the whole behaviour to Vuetify: at >= 1024 the drawer is
|
|
38
|
+
part of the layout exactly as it is today, and below it the same drawer
|
|
39
|
+
becomes temporary, floats OVER the content with a scrim, and closes on the
|
|
40
|
+
scrim, on Escape and on a navigation.
|
|
41
|
+
|
|
42
|
+
1024 rather than Vuetify's default 1280: at 1024 the rail still leaves a
|
|
43
|
+
772px content column, which is a usable table. Below that it does not.
|
|
44
|
+
|
|
45
|
+
Nothing about the menu changes - same items, same order, same permissions,
|
|
46
|
+
same routes. Only where the panel is drawn.
|
|
47
|
+
-->
|
|
31
48
|
<v-navigation-drawer
|
|
32
49
|
v-model="drawer"
|
|
33
|
-
|
|
50
|
+
:mobile-breakpoint="1024"
|
|
34
51
|
class="bg-brand-surface brand-drawer"
|
|
35
52
|
>
|
|
36
53
|
<v-list>
|
|
@@ -75,6 +92,8 @@
|
|
|
75
92
|
</template>
|
|
76
93
|
|
|
77
94
|
<script setup lang="ts">
|
|
95
|
+
import { useDisplay } from "vuetify";
|
|
96
|
+
|
|
78
97
|
const props = defineProps({
|
|
79
98
|
navigationItems: { type: Array, required: true },
|
|
80
99
|
title: { type: String, default: "" },
|
|
@@ -85,6 +104,32 @@ const emit = defineEmits(["navigate"]);
|
|
|
85
104
|
const { drawer } = useLocal();
|
|
86
105
|
const { APP_NAME } = useRuntimeConfig().public;
|
|
87
106
|
const route = useRoute();
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* `drawer` is shared application state and it defaults to OPEN, which is right
|
|
110
|
+
* for a rail and wrong for an overlay - a phone would load with the menu
|
|
111
|
+
* covering the page behind a scrim. So the default follows the viewport, and a
|
|
112
|
+
* user who resizes past the breakpoint gets the state that suits where they
|
|
113
|
+
* landed.
|
|
114
|
+
*
|
|
115
|
+
* Only the DEFAULT is managed here. Once the hamburger has been used, the
|
|
116
|
+
* user's choice stands until the layout itself changes underneath them.
|
|
117
|
+
*/
|
|
118
|
+
const { mobile } = useDisplay({ mobileBreakpoint: 1024 });
|
|
119
|
+
|
|
120
|
+
watch(mobile, (isOverlay) => (drawer.value = !isOverlay), { immediate: true });
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* An overlay drawer that survives a navigation leaves the scrim over the page
|
|
124
|
+
* the user just asked for. A rail must NOT close - that would be a step added
|
|
125
|
+
* to every desktop navigation.
|
|
126
|
+
*/
|
|
127
|
+
watch(
|
|
128
|
+
() => route.fullPath,
|
|
129
|
+
() => {
|
|
130
|
+
if (mobile.value) drawer.value = false;
|
|
131
|
+
}
|
|
132
|
+
);
|
|
88
133
|
const orgId = computed(() => String(route.params.org ?? ""));
|
|
89
134
|
const siteId = computed(() => String(route.params.site ?? ""));
|
|
90
135
|
const { useHidFaceReaderMenu } = useHidNavigation();
|