@7365admin1/layer-common 3.2.2-staging.85 → 3.2.2-staging.87

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.
@@ -0,0 +1,486 @@
1
+ /**
2
+ * PHASE 4 - THE MODULE-SCREEN PATTERNS.
3
+ *
4
+ * Phase 1 wrote the tokens, Phase 2 the shell, Phase 3 the table card. What is
5
+ * left on a module screen is everything AROUND that table: the filter bar over
6
+ * it, the tabs that switch it, the dialogs it opens, the plain cards and the
7
+ * settings accordion. Those patterns are drawn once, here, and the screens opt
8
+ * in by class name - so a screen's diff is class names and chips, not a block
9
+ * of CSS repeated twenty times.
10
+ *
11
+ * OPT-IN, NOT GLOBAL. Every rule below is under a class this layer's own
12
+ * components add to their own markup. Nothing here selects a bare Vuetify class
13
+ * on its own, so no application page is repainted by a stylesheet it never
14
+ * asked for - the eleven apps get these surfaces through the components they
15
+ * already mount, and their own markup is Phases 6 and 7.
16
+ *
17
+ * VISUAL ONLY. No rule here hides, moves, reorders, disables or reveals a
18
+ * control: everything is colour, type, radius, border, shadow and spacing.
19
+ */
20
+
21
+ /* ------------------------------------------------------------------ */
22
+ /* 1. The filter bar. */
23
+ /* ------------------------------------------------------------------ */
24
+
25
+ /**
26
+ * The row of filters that sits over a table. On these screens it is a
27
+ * `v-card flat` with a fixed 60px height and `ga-5`, which on a phone puts
28
+ * three 300px fields on one 60px line and clips them. This is the same fields
29
+ * in the same order, on the design's surface, wrapping instead of clipping.
30
+ */
31
+ .filter-bar {
32
+ display: flex;
33
+ flex-wrap: wrap;
34
+ align-items: center;
35
+ gap: var(--grid-gap);
36
+ width: 100%;
37
+ padding: 10px var(--cellpad-x);
38
+ background: transparent;
39
+ font-family: var(--font-sans);
40
+ }
41
+
42
+ /* 40px, 10px radius, 13.5px - the design's input, whatever Vuetify's density
43
+ would otherwise make of it. `.filter-field` is the same input on its own,
44
+ for the screens whose one search box sits in a toolbar rather than a row. */
45
+ .filter-bar .v-field,
46
+ .filter-field .v-field {
47
+ border-radius: var(--r-inner);
48
+ font-size: var(--fs-cell);
49
+ font-family: var(--font-sans);
50
+ }
51
+
52
+ .filter-bar .v-field__input,
53
+ .filter-field .v-field__input {
54
+ min-height: var(--input-h);
55
+ font-size: var(--fs-cell);
56
+ }
57
+
58
+ .filter-bar .v-label,
59
+ .filter-field .v-label,
60
+ .filter-bar .v-field__input input::placeholder,
61
+ .filter-field .v-field__input input::placeholder {
62
+ font-size: var(--fs-cell);
63
+ font-family: var(--font-sans);
64
+ }
65
+
66
+ /* A field is at least readable-wide and then shares what is left. Below 600
67
+ there is no room for two, so they stack - the same rule `.filter-row` uses. */
68
+ .filter-bar > * {
69
+ flex: 1 1 200px;
70
+ min-width: 0;
71
+ max-width: 100%;
72
+ }
73
+
74
+ .filter-bar > .filter-bar__fit {
75
+ flex: 0 0 auto;
76
+ }
77
+
78
+ @media (max-width: 599.98px) {
79
+ .filter-bar {
80
+ flex-direction: column;
81
+ align-items: stretch;
82
+ }
83
+
84
+ .filter-bar > * {
85
+ flex: 1 1 auto;
86
+ width: 100%;
87
+ }
88
+ }
89
+
90
+ /* ------------------------------------------------------------------ */
91
+ /* 2. Tabs. */
92
+ /* ------------------------------------------------------------------ */
93
+
94
+ /**
95
+ * 13px, the active one at 800 in `--accent-text` over a 2px accent underline.
96
+ * Vuetify's own tab label is uppercase 14px/500 with wide tracking and it puts
97
+ * `text-uppercase` on it as a utility, so these need the same weight of hand
98
+ * the nav items needed in Phase 2.
99
+ */
100
+ .screen-tabs .v-tab {
101
+ font-family: var(--font-sans);
102
+ font-size: var(--fs-nav) !important;
103
+ font-weight: var(--fw-cell) !important;
104
+ letter-spacing: 0 !important;
105
+ text-transform: none !important;
106
+ color: var(--muted);
107
+ min-width: 0;
108
+ }
109
+
110
+ .screen-tabs .v-tab.v-tab--selected {
111
+ font-weight: var(--fw-table-head) !important;
112
+ color: var(--accent-text);
113
+ }
114
+
115
+ .screen-tabs .v-tab .v-tab__slider {
116
+ height: 2px;
117
+ background: var(--accent);
118
+ }
119
+
120
+ /* ------------------------------------------------------------------ */
121
+ /* 3. Cards, section headings and empty states. */
122
+ /* ------------------------------------------------------------------ */
123
+
124
+ /** The card contract, for the surfaces that are not the table card. */
125
+ .screen-card {
126
+ font-family: var(--font-sans);
127
+ background: var(--card);
128
+ border: 1px solid var(--border);
129
+ border-radius: var(--r-card);
130
+ box-shadow: var(--shadow);
131
+ }
132
+
133
+ .screen-title {
134
+ font-family: var(--font-sans);
135
+ font-size: var(--fs-h1);
136
+ font-weight: var(--fw-h1);
137
+ letter-spacing: var(--ls-h1);
138
+ color: var(--text);
139
+ }
140
+
141
+ .screen-card-title {
142
+ font-family: var(--font-sans);
143
+ font-size: var(--fs-card-title) !important;
144
+ font-weight: var(--fw-card-title) !important;
145
+ letter-spacing: 0 !important;
146
+ color: var(--text);
147
+ }
148
+
149
+ .screen-empty {
150
+ display: flex;
151
+ flex-direction: column;
152
+ align-items: center;
153
+ justify-content: center;
154
+ gap: 8px;
155
+ padding: 52px 16px;
156
+ font-family: var(--font-sans);
157
+ font-size: var(--fs-cell);
158
+ color: var(--muted);
159
+ text-align: center;
160
+ }
161
+
162
+ .screen-empty .v-icon {
163
+ color: var(--muted);
164
+ }
165
+
166
+ /**
167
+ * The one-line explanation under a setting's name. It was
168
+ * `text-grey-darken-1` - a fixed Vuetify grey, i.e. the same dark grey on the
169
+ * dark page - so the sentence that explains a switch was the hardest thing on
170
+ * the screen to read. `--muted` is the design's own, and it is a token, so it
171
+ * follows the theme.
172
+ */
173
+ .screen-hint {
174
+ font-family: var(--font-sans);
175
+ color: var(--muted);
176
+ }
177
+
178
+ /* ------------------------------------------------------------------ */
179
+ /* 4. Dialogs. */
180
+ /* ------------------------------------------------------------------ */
181
+
182
+ /**
183
+ * THE ONE RULE HERE THAT IS NOT OPT-IN, AND WHY.
184
+ *
185
+ * The product opens its dialogs from more than sixty different form and dialog
186
+ * components, not from the twenty-three module screens. Giving the design's
187
+ * 16px corner to a modal by editing sixty files would be sixty chances to
188
+ * break a dialog for a corner radius. So the SHAPE is stated once, for any
189
+ * card that Vuetify has put inside an overlay - shape and shadow only. It
190
+ * repaints no colour, moves no control and changes no type, and it is the same
191
+ * `.v-overlay__content > .v-card` selector Phase 3 already had to out-specify.
192
+ */
193
+ .v-overlay__content > .v-card,
194
+ .v-overlay__content > form > .v-card {
195
+ border-radius: var(--r-modal) !important;
196
+ box-shadow: var(--shadow-modal) !important;
197
+ }
198
+
199
+ /**
200
+ * `.v-overlay__content > .v-card` is MORE SPECIFIC than a class on the card
201
+ * (Phase 3 measured a dialog stuck at its factory 4px radius because of it),
202
+ * so the radius has to be `!important` to be reached at all.
203
+ *
204
+ * The body is what scrolls, not the whole card: a modal whose card scrolls
205
+ * takes its Cancel/Submit footer off screen with it. `.screen-modal__body` is
206
+ * only applied where a screen's dialog already has a distinct body element.
207
+ */
208
+ .screen-modal {
209
+ font-family: var(--font-sans);
210
+ border-radius: var(--r-modal) !important;
211
+ box-shadow: var(--shadow-modal) !important;
212
+ background: var(--card) !important;
213
+ max-height: calc(100vh - 32px);
214
+ }
215
+
216
+ .screen-modal .v-card-title {
217
+ font-family: var(--font-sans);
218
+ font-size: var(--fs-card-title) !important;
219
+ font-weight: var(--fw-card-title) !important;
220
+ letter-spacing: 0 !important;
221
+ color: var(--text);
222
+ }
223
+
224
+ .screen-modal__body {
225
+ overflow-y: auto;
226
+ font-size: var(--fs-cell);
227
+ }
228
+
229
+ /* The design's two buttons, for the dialogs and toolbars that draw their own. */
230
+ .screen-btn-primary {
231
+ height: var(--btn-h);
232
+ border-radius: var(--r-inner);
233
+ padding: 0 16px;
234
+ font-family: var(--font-sans);
235
+ font-size: var(--fs-btn);
236
+ font-weight: var(--fw-btn-strong);
237
+ letter-spacing: 0;
238
+ text-transform: none;
239
+ background: var(--accent-strong);
240
+ color: var(--on-accent-strong);
241
+ }
242
+
243
+ /* Vuetify paints `variant="tonal"` with an overlay layer ABOVE the button's own
244
+ background, so without this the accent fill shows through a wash. */
245
+ .screen-btn-primary .v-btn__overlay,
246
+ .screen-btn-ghost .v-btn__overlay {
247
+ opacity: 0;
248
+ }
249
+
250
+ /**
251
+ * The icon-only actions next to a primary button (download template, import,
252
+ * export). The design draws an icon button as a bordered square the height of
253
+ * the input next to it - the same button Phase 2 gave the top bar - not a
254
+ * tinted pill.
255
+ */
256
+ .screen-icon-btn {
257
+ height: var(--btn-h);
258
+ width: var(--btn-h);
259
+ min-width: var(--btn-h);
260
+ border-radius: var(--r-inner);
261
+ border: 1px solid var(--border);
262
+ background: var(--card);
263
+ color: var(--text2);
264
+ }
265
+
266
+ .screen-icon-btn .v-btn__overlay {
267
+ opacity: 0;
268
+ }
269
+
270
+ .screen-btn-ghost {
271
+ height: var(--btn-h);
272
+ border-radius: var(--r-inner);
273
+ padding: 0 16px;
274
+ font-family: var(--font-sans);
275
+ font-size: var(--fs-btn);
276
+ font-weight: var(--fw-btn);
277
+ letter-spacing: 0;
278
+ text-transform: none;
279
+ color: var(--text2);
280
+ border-color: var(--border);
281
+ background: var(--card);
282
+ }
283
+
284
+ /* ------------------------------------------------------------------ */
285
+ /* 5. The settings accordion. */
286
+ /* ------------------------------------------------------------------ */
287
+
288
+ /**
289
+ * The design draws settings as a stack of bordered sections with one open at a
290
+ * time. The "one at a time" half is BEHAVIOUR and is left exactly as the panel
291
+ * has it (`multiple`, on the site settings screen); this is the drawn half:
292
+ * the border, the radius, the title type and the open section's surface.
293
+ */
294
+ .screen-accordion .v-expansion-panel {
295
+ font-family: var(--font-sans);
296
+ background: var(--card);
297
+ border: 1px solid var(--border);
298
+ border-radius: var(--r-card) !important;
299
+ box-shadow: none !important;
300
+ margin-bottom: 10px;
301
+ }
302
+
303
+ .screen-accordion .v-expansion-panel::after {
304
+ display: none;
305
+ }
306
+
307
+ .screen-accordion .v-expansion-panel-title {
308
+ font-family: var(--font-sans);
309
+ font-size: var(--fs-card-title);
310
+ font-weight: var(--fw-card-title);
311
+ color: var(--text);
312
+ min-height: 56px;
313
+ }
314
+
315
+ .screen-accordion .v-expansion-panel-title--active {
316
+ color: var(--text);
317
+ border-bottom: 1px solid var(--border);
318
+ }
319
+
320
+ .screen-accordion .v-expansion-panel-text {
321
+ font-size: var(--fs-cell);
322
+ color: var(--text2);
323
+ }
324
+
325
+ .screen-accordion .v-expansion-panel-title__overlay {
326
+ background: transparent;
327
+ }
328
+
329
+ /* ------------------------------------------------------------------ */
330
+ /* 6. The table card (moved here from TableMain, unchanged). */
331
+ /* ------------------------------------------------------------------ */
332
+
333
+ /**
334
+ * Phase 3 drew this inside `TableMain`'s own scoped block. It is moved here,
335
+ * rule for rule, because it is not TableMain's alone any more: `BillingMain`
336
+ * draws the same card from its own copy of that markup, and a second copy of
337
+ * the CSS is how the two drift apart. `TableMain` still puts the same classes
338
+ * on the same elements - only the stylesheet moved, so nothing it renders
339
+ * changes.
340
+ */
341
+
342
+ /* ------------------------------------------------------------------ */
343
+ /* The card. */
344
+ /* ------------------------------------------------------------------ */
345
+
346
+ .table-card {
347
+ font-family: var(--font-sans);
348
+ background: var(--card);
349
+ border: 1px solid var(--border);
350
+ border-radius: var(--r-card);
351
+ box-shadow: var(--shadow);
352
+ overflow: hidden;
353
+ }
354
+
355
+ /**
356
+ * The toolbar was `color="grey-lighten-4"` - a literal Vuetify grey, which is
357
+ * a near-white bar sitting on a #1c1e24 card in dark mode. It is the card's
358
+ * own surface now, with the design's rule under it.
359
+ */
360
+ .table-card__toolbar {
361
+ border-bottom: 1px solid var(--border);
362
+ }
363
+
364
+ .table-card__range {
365
+ font-size: 12px;
366
+ font-weight: 600;
367
+ color: var(--muted);
368
+ font-variant-numeric: tabular-nums;
369
+ }
370
+
371
+ /* ------------------------------------------------------------------ */
372
+ /* The header row and the rows under it. */
373
+ /* ------------------------------------------------------------------ */
374
+
375
+ .table-card thead th {
376
+ background: var(--thead) !important;
377
+ color: var(--muted) !important;
378
+ font-size: var(--fs-table-head) !important;
379
+ font-weight: var(--fw-table-head) !important;
380
+ letter-spacing: var(--ls-table-head);
381
+ text-transform: uppercase;
382
+ padding: 10px var(--cellpad-x) !important;
383
+ height: auto !important;
384
+ border-bottom: 1px solid var(--border) !important;
385
+ white-space: nowrap;
386
+ }
387
+
388
+ .table-card tbody td {
389
+ font-size: var(--fs-cell);
390
+ font-weight: var(--fw-cell);
391
+ color: var(--text);
392
+ padding: var(--cellpad) var(--cellpad-x) !important;
393
+ height: auto !important;
394
+ font-variant-numeric: tabular-nums;
395
+ }
396
+
397
+ .table-card tbody tr:hover > td {
398
+ background: var(--hover);
399
+ }
400
+
401
+ /**
402
+ * The grouped list (`TableHygiene`) draws its own rows instead of a <table>,
403
+ * so it cannot inherit the two rules above. Its group header takes the header
404
+ * row's surface and its rows the card's - replacing the literal
405
+ * `grey-lighten-4` and `white` that painted the whole list near-white on a
406
+ * dark card.
407
+ */
408
+ .table-card__group {
409
+ background: var(--thead);
410
+ }
411
+
412
+ .table-card__row {
413
+ background: var(--card);
414
+ }
415
+
416
+ /* ------------------------------------------------------------------ */
417
+ /* Empty state, in-card search, buttons and in-card tabs. */
418
+ /* ------------------------------------------------------------------ */
419
+
420
+ .table-card__empty {
421
+ display: flex;
422
+ flex-direction: column;
423
+ align-items: center;
424
+ justify-content: center;
425
+ gap: 8px;
426
+ padding: 52px 16px;
427
+ font-size: var(--fs-cell);
428
+ color: var(--muted);
429
+ }
430
+
431
+ .table-card__empty .v-icon {
432
+ color: var(--muted);
433
+ }
434
+
435
+ /* The in-card variant: 34px, 10px radius, as drawn. */
436
+ .table-card__search .v-field {
437
+ border-radius: var(--r-inner);
438
+ font-size: var(--fs-cell);
439
+ }
440
+
441
+ .table-card__primary {
442
+ height: var(--btn-h);
443
+ border-radius: var(--r-inner);
444
+ padding: 0 16px;
445
+ font-size: var(--fs-btn);
446
+ font-weight: var(--fw-btn-strong);
447
+ letter-spacing: 0;
448
+ background: var(--accent-strong);
449
+ color: var(--on-accent-strong);
450
+ }
451
+
452
+ .table-card__ghost {
453
+ height: var(--btn-h);
454
+ border-radius: var(--r-inner);
455
+ padding: 0 16px;
456
+ font-size: var(--fs-btn);
457
+ font-weight: var(--fw-btn);
458
+ letter-spacing: 0;
459
+ color: var(--text2);
460
+ border-color: var(--border);
461
+ background: var(--card);
462
+ }
463
+
464
+ /**
465
+ * Tabs that a screen puts in this card's extension slot: 13px, the active one
466
+ * at 800 in `--accent-text` over a 2px accent underline. Tabs elsewhere in the
467
+ * product are their own screens' and are restyled with them.
468
+ */
469
+ .table-card .v-tab {
470
+ font-size: var(--fs-nav);
471
+ font-weight: var(--fw-cell);
472
+ letter-spacing: 0;
473
+ text-transform: none;
474
+ color: var(--muted);
475
+ min-width: 0;
476
+ }
477
+
478
+ .table-card .v-tab.v-tab--selected {
479
+ font-weight: var(--fw-table-head);
480
+ color: var(--accent-text);
481
+ }
482
+
483
+ .table-card .v-tab .v-tab__slider {
484
+ height: 2px;
485
+ background: var(--accent);
486
+ }
@@ -33,7 +33,7 @@
33
33
  <template #extension>
34
34
  <v-row no-gutters class="w-100 d-flex flex-column">
35
35
  <v-card
36
- class="w-100 px-3 d-flex align-center ga-5 py-2"
36
+ class="w-100 filter-bar d-flex align-center ga-5"
37
37
  flat
38
38
  :height="60"
39
39
  >
@@ -80,7 +80,11 @@
80
80
  </div>
81
81
  </template>
82
82
  <template #item.status="{ value }">
83
- <v-chip class="text-capitalize">{{ value || "No Status" }}</v-chip>
83
+ <StatusChip
84
+ class="text-capitalize"
85
+ :status="value"
86
+ :label="value || 'No Status'"
87
+ />
84
88
  </template>
85
89
  </TableMain></v-col
86
90
  >
@@ -49,10 +49,8 @@
49
49
  <!-- Primary action -->
50
50
  <v-btn
51
51
  v-if="canCreateArea"
52
- class="text-none mr-3"
53
- rounded="pill"
54
- variant="tonal"
55
- size="large"
52
+ class="text-none mr-3 screen-btn-primary"
53
+ variant="flat"
56
54
  @click="onCreateItem"
57
55
  >
58
56
  {{ createLabel }}
@@ -63,10 +61,8 @@
63
61
  <template #activator="{ props: t }">
64
62
  <v-btn
65
63
  v-bind="t"
66
- class="ma-1"
67
- variant="tonal"
68
- size="large"
69
- rounded="pill"
64
+ class="ma-1 screen-icon-btn"
65
+ variant="flat"
70
66
  href="/files/area-sample-data.xlsx"
71
67
  target="_blank"
72
68
  download
@@ -81,10 +77,8 @@
81
77
  <template #activator="{ props: t }">
82
78
  <v-btn
83
79
  v-bind="t"
84
- class="ma-1"
85
- variant="tonal"
86
- size="large"
87
- rounded="pill"
80
+ class="ma-1 screen-icon-btn"
81
+ variant="flat"
88
82
  @click="onImportData"
89
83
  >
90
84
  <v-icon>mdi-upload</v-icon>
@@ -97,10 +91,8 @@
97
91
  <template #activator="{ props: t }">
98
92
  <v-btn
99
93
  v-bind="t"
100
- class="ma-1"
101
- variant="tonal"
102
- size="large"
103
- rounded="pill"
94
+ class="ma-1 screen-icon-btn"
95
+ variant="flat"
104
96
  @click="handleDownloadExcel"
105
97
  :loading="isDownloading"
106
98
  >
@@ -114,7 +106,7 @@
114
106
 
115
107
  <v-spacer />
116
108
 
117
- <v-col cols="auto">
109
+ <v-col cols="auto" class="filter-field">
118
110
  <v-text-field
119
111
  v-model="searchInput"
120
112
  density="compact"
@@ -15,11 +15,12 @@
15
15
  @row-click="handleRowClick"
16
16
  >
17
17
  <template #actions>
18
+ <!-- Was `color="black"`: a black slab in both themes, and the only
19
+ black surface on the page. Same button, the design's ghost. -->
18
20
  <v-btn
19
21
  v-if="canManageAttendanceSettings"
20
- variant="flat"
21
- color="black"
22
- class="text-none"
22
+ variant="outlined"
23
+ class="text-none screen-btn-ghost"
23
24
  @click="dialogShowSettings = true"
24
25
  >
25
26
  <v-icon class="mr-2">mdi-cog</v-icon>
@@ -1,14 +1,12 @@
1
1
  <template>
2
2
  <v-row no-gutters>
3
3
  <v-col cols="12">
4
- <v-card
5
- width="100%"
6
- variant="outlined"
7
- border="thin"
8
- rounded="lg"
9
- :loading="false"
10
- >
11
- <v-toolbar density="compact" color="grey-lighten-4">
4
+ <v-card width="100%" variant="flat" class="table-card" :loading="false">
5
+ <v-toolbar
6
+ density="compact"
7
+ color="transparent"
8
+ class="table-card__toolbar"
9
+ >
12
10
  <template #prepend>
13
11
  <v-btn fab icon density="comfortable">
14
12
  <v-icon>mdi-refresh</v-icon>
@@ -16,7 +14,7 @@
16
14
  </template>
17
15
  <template #append>
18
16
  <v-row no-gutters justify="end" align="center">
19
- <span class="mr-2 text-caption text-fontgray">
17
+ <span class="mr-2 table-card__range">
20
18
  {{ pageRange }}
21
19
  </span>
22
20
  <local-pagination v-model="page" :length="pages" />
@@ -38,7 +36,7 @@
38
36
  <span class="text-caption font-weight-bold text-capitalize">
39
37
  permissions
40
38
  </span>
41
- <v-chip>{{ value.length }}</v-chip>
39
+ <StatusChip :dot="false" tone="neutral" :label="`${value.length}`" />
42
40
  </template>
43
41
 
44
42
  <template #item.nature="{ item }">