@7365admin1/layer-common 3.2.2-staging.82 → 3.2.2-staging.83

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.
@@ -1,31 +1,35 @@
1
+ <!--
2
+ PHASE 3 - THE MODAL.
3
+
4
+ 520px, 16px radius and the design's overlay and shadow, with the BODY as the
5
+ only thing that scrolls so the footer never leaves the screen. Same dialog,
6
+ same slots, same close behaviour, same `persistent`.
7
+
8
+ The two `theme.name.value === ...` branches are gone, and that is a fix, not
9
+ a preference: they hard-coded `bg-white` and Vuetify's `grey-darken-4`, so
10
+ the card was painted by a literal colour instead of by the theme it is in.
11
+ `--card` is that surface in each theme.
12
+ -->
1
13
  <template>
2
- <v-dialog v-model="model" max-width="600" persistent>
3
- <v-card
4
- class="px-3 d-flex flex-column"
5
- :class="`${theme.name.value === 'light' ? 'bg-white' : ''} ${
6
- mdAndUp ? 'rounded-xl' : ''
7
- }`"
8
- :style="{ border: 'none', boxShadow: 'none' }"
9
- >
14
+ <v-dialog v-model="model" :max-width="520" persistent class="form-dialog">
15
+ <v-card class="d-flex flex-column form-dialog__card" :style="{ border: 'none' }">
10
16
  <v-toolbar
11
- :color="theme.name.value === 'dark' ? 'grey-darken-4' : 'white'"
12
- class="flex-shrink-0 mb-3"
17
+ color="transparent"
18
+ class="flex-shrink-0 form-dialog__head"
13
19
  style="position: sticky; top: 0; z-index: 10"
14
20
  >
15
21
  <slot name="title">
16
- <span class="text-h6 font-weight-medium pt-1 text-capitalize">
17
- Dialog
18
- </span>
22
+ <span class="form-dialog__title"> Dialog </span>
19
23
  </slot>
20
24
  <v-spacer />
21
- <v-btn icon="mdi-close" @click="closeDialog" />
25
+ <v-btn icon="mdi-close" variant="text" @click="closeDialog" />
22
26
  </v-toolbar>
23
27
 
24
- <v-sheet class="flex-grow-1 overflow-y-auto px-3">
28
+ <v-sheet class="flex-grow-1 overflow-y-auto px-4 form-dialog__body">
25
29
  <slot />
26
30
  </v-sheet>
27
31
 
28
- <v-sheet class="flex-shrink-0 px-3 py-2">
32
+ <v-sheet class="flex-shrink-0 px-4 py-3 form-dialog__footer">
29
33
  <slot name="footer" />
30
34
  </v-sheet>
31
35
  </v-card>
@@ -33,11 +37,6 @@
33
37
  </template>
34
38
 
35
39
  <script setup lang="ts">
36
- import { useTheme, useDisplay } from "vuetify";
37
-
38
- const theme = useTheme().global;
39
- const { mdAndUp } = useDisplay();
40
-
41
40
  const props = defineProps<{
42
41
  modelValue: boolean;
43
42
  }>();
@@ -62,4 +61,44 @@ function closeDialog() {
62
61
  :deep(.v-dialog .v-overlay__content) {
63
62
  margin: auto;
64
63
  }
64
+
65
+ .form-dialog__card {
66
+ font-family: var(--font-sans);
67
+ background: var(--card);
68
+ /* `!important` because Vuetify's own `.v-overlay__content > .v-card` rule is
69
+ more specific than a class on the card, and it is what keeps the dialog at
70
+ its factory 4px. */
71
+ border-radius: var(--r-modal) !important;
72
+ box-shadow: var(--shadow-modal);
73
+ /* Never taller than the screen it has to fit inside; the body is what
74
+ scrolls, so the Cancel/Submit footer cannot be pushed out of view. */
75
+ max-height: calc(100vh - 32px);
76
+ overflow: hidden;
77
+ }
78
+
79
+ .form-dialog__head {
80
+ border-bottom: 1px solid var(--border);
81
+ }
82
+
83
+ .form-dialog__title {
84
+ font-size: var(--fs-card-title);
85
+ font-weight: var(--fw-card-title);
86
+ color: var(--text);
87
+ text-transform: capitalize;
88
+ }
89
+
90
+ .form-dialog__body,
91
+ .form-dialog__footer {
92
+ background: var(--card);
93
+ }
94
+
95
+ .form-dialog__footer {
96
+ border-top: 1px solid var(--border);
97
+ }
98
+
99
+ @media (max-width: 599.98px) {
100
+ .form-dialog__card {
101
+ max-height: calc(100dvh - 32px);
102
+ }
103
+ }
65
104
  </style>
@@ -0,0 +1,64 @@
1
+ <!--
2
+ THE STATUS CHIP.
3
+
4
+ A pill in the tone the design assigns to the status WORD, with the 6px
5
+ leading dot. The word -> tone mapping is `utils/status.ts` and is shared, so
6
+ "Open" is the same green on a work order and on a Daily Occurrence Book, and
7
+ a status the design never named comes out neutral rather than guessed.
8
+
9
+ Colour only. The chip prints the label it is given; it does not decide, map,
10
+ translate or shorten a status.
11
+ -->
12
+ <template>
13
+ <span class="status-chip" :class="[toneClass, { 'status-chip--plain': !dot }]">
14
+ <span v-if="dot" class="status-chip__dot" />
15
+ {{ label ?? status }}
16
+ </span>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { statusToneClass } from "../utils/status";
21
+
22
+ const props = defineProps({
23
+ /** The status word - what decides the colour. */
24
+ status: { type: String, default: "" },
25
+ /** What to PRINT, when that differs from the word being matched on. */
26
+ label: { type: String, default: undefined },
27
+ /** The design's tag chips (pass IDs, role names) carry no leading dot. */
28
+ dot: { type: Boolean, default: true },
29
+ });
30
+
31
+ const toneClass = computed(() => statusToneClass(props.status));
32
+ </script>
33
+
34
+ <style scoped>
35
+ .status-chip {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: 6px;
39
+ padding: 4px 10px;
40
+ border-radius: var(--r-pill);
41
+ font-family: var(--font-sans);
42
+ font-size: var(--fs-chip-lg);
43
+ font-weight: var(--fw-chip);
44
+ line-height: 1.4;
45
+ white-space: nowrap;
46
+ /* `--tone-bg` is set alongside the colour by the `.tone-*` class - see
47
+ assets/css/tokens.css - so one class carries both halves of the pair the
48
+ contrast was measured on. */
49
+ background: var(--tone-bg);
50
+ }
51
+
52
+ .status-chip--plain {
53
+ border-radius: var(--r-tag);
54
+ font-size: var(--fs-chip);
55
+ }
56
+
57
+ .status-chip__dot {
58
+ width: 6px;
59
+ height: 6px;
60
+ border-radius: var(--r-pill);
61
+ background: currentColor;
62
+ flex: 0 0 6px;
63
+ }
64
+ </style>
@@ -1,3 +1,18 @@
1
+ <!--
2
+ PHASE 3 - THE STANDARD TABLE CARD.
3
+
4
+ The redesign is, more than anything else, a table card: toolbar row (refresh,
5
+ optional in-card search, pagination) -> uppercase header row -> rows. This is
6
+ the shared one, so this file is where that pattern is drawn once.
7
+
8
+ VISUAL ONLY, and that matters more here than anywhere. Every column, filter,
9
+ sort, action, slot, prop, emit and page still behaves exactly as it did:
10
+ the headers are the caller's, the items are the caller's, every slot is still
11
+ forwarded to `v-data-table` untouched, and no prop's default has changed. The
12
+ empty state is the same wording `v-data-table` already showed, with the icon
13
+ the design draws above it - and it is only supplied when the caller has NOT
14
+ passed its own `no-data` slot.
15
+ -->
1
16
  <template>
2
17
  <v-row no-gutters>
3
18
  <!-- Top Actions -->
@@ -5,13 +20,17 @@
5
20
  <v-row no-gutters>
6
21
  <slot name="actions">
7
22
  <v-col cols="12">
8
- <v-row no-gutters class="ga-2">
23
+ <v-row no-gutters class="ga-2 align-center table-card__actions">
24
+ <!--
25
+ The design's primary button: the accent fill that may carry a
26
+ white label (`--accent-strong`; white on `--accent` is 3.23:1),
27
+ 40px, 10px radius, leading icon. Same click, same label.
28
+ -->
9
29
  <v-btn
10
30
  v-if="canCreate"
11
- class="text-none"
12
- rounded="pill"
13
- variant="tonal"
14
- size="large"
31
+ class="text-none table-card__primary"
32
+ variant="flat"
33
+ prepend-icon="mdi-plus"
15
34
  @click="emits('create')"
16
35
  >
17
36
  {{ createLabel }}
@@ -25,14 +44,14 @@
25
44
  max-width="300"
26
45
  append-inner-icon="mdi-magnify"
27
46
  hide-details
47
+ class="table-card__search"
28
48
  />
49
+ <!-- Ghost/secondary, as the design specifies for Scan QR Code. -->
29
50
  <v-btn
30
51
  v-if="canScanVisitorQRCode"
31
52
  text="Scan QR Code"
32
- class="text-none ml-2"
33
- rounded="pill"
34
- variant="tonal"
35
- size="large"
53
+ class="text-none ml-2 table-card__ghost"
54
+ variant="outlined"
36
55
  @click="emits('scan')"
37
56
  />
38
57
  </v-row>
@@ -45,19 +64,19 @@
45
64
  <v-col cols="12">
46
65
  <v-card
47
66
  width="100%"
48
- variant="outlined"
49
- border="thin"
50
- rounded="lg"
67
+ variant="flat"
68
+ class="table-card"
51
69
  :loading="loading"
52
70
  >
53
71
  <!-- Toolbar -->
54
72
  <v-toolbar
55
73
  density="compact"
56
- color="grey-lighten-4"
74
+ color="transparent"
75
+ class="table-card__toolbar"
57
76
  :extension-height="extensionHeight"
58
77
  >
59
78
  <template #prepend>
60
- <v-btn fab icon density="comfortable" @click="emits('refresh')">
79
+ <v-btn fab icon density="comfortable" variant="text" @click="emits('refresh')">
61
80
  <v-icon>mdi-refresh</v-icon>
62
81
  </v-btn>
63
82
  <slot name="prepend-additional" />
@@ -65,7 +84,7 @@
65
84
 
66
85
  <template #append>
67
86
  <v-row no-gutters justify="end" align="center">
68
- <span class="mr-2 text-caption text-fontgray">
87
+ <span class="mr-2 table-card__range">
69
88
  {{ pageRange }}
70
89
  </span>
71
90
  <local-pagination
@@ -96,6 +115,18 @@
96
115
  <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
97
116
  <slot :name="slotName" v-bind="slotProps" />
98
117
  </template>
118
+
119
+ <!--
120
+ The design's empty state. Same sentence the table already showed;
121
+ only supplied when the caller has not passed its own, so nobody's
122
+ existing empty state is replaced.
123
+ -->
124
+ <template v-if="!$slots['no-data']" #no-data>
125
+ <div class="table-card__empty">
126
+ <v-icon icon="mdi-magnify-close" size="32" />
127
+ <span>No data available</span>
128
+ </div>
129
+ </template>
99
130
  </v-data-table>
100
131
  <slot name="footer" />
101
132
  </v-card>
@@ -187,7 +218,134 @@ watch(
187
218
  </script>
188
219
 
189
220
  <style scoped>
190
- .v-data-table :deep(thead th) {
191
- font-weight: bold !important;
221
+ /* ------------------------------------------------------------------ */
222
+ /* The card. */
223
+ /* ------------------------------------------------------------------ */
224
+
225
+ .table-card {
226
+ font-family: var(--font-sans);
227
+ background: var(--card);
228
+ border: 1px solid var(--border);
229
+ border-radius: var(--r-card);
230
+ box-shadow: var(--shadow);
231
+ overflow: hidden;
232
+ }
233
+
234
+ /**
235
+ * The toolbar was `color="grey-lighten-4"` - a literal Vuetify grey, which is
236
+ * a near-white bar sitting on a #1c1e24 card in dark mode. It is the card's
237
+ * own surface now, with the design's rule under it.
238
+ */
239
+ .table-card__toolbar {
240
+ border-bottom: 1px solid var(--border);
241
+ }
242
+
243
+ .table-card__range {
244
+ font-size: 12px;
245
+ font-weight: 600;
246
+ color: var(--muted);
247
+ font-variant-numeric: tabular-nums;
248
+ }
249
+
250
+ /* ------------------------------------------------------------------ */
251
+ /* The header row and the rows under it. */
252
+ /* ------------------------------------------------------------------ */
253
+
254
+ .table-card :deep(thead th) {
255
+ background: var(--thead) !important;
256
+ color: var(--muted) !important;
257
+ font-size: var(--fs-table-head) !important;
258
+ font-weight: var(--fw-table-head) !important;
259
+ letter-spacing: var(--ls-table-head);
260
+ text-transform: uppercase;
261
+ padding: 10px var(--cellpad-x) !important;
262
+ height: auto !important;
263
+ border-bottom: 1px solid var(--border) !important;
264
+ white-space: nowrap;
265
+ }
266
+
267
+ .table-card :deep(tbody td) {
268
+ font-size: var(--fs-cell);
269
+ font-weight: var(--fw-cell);
270
+ color: var(--text);
271
+ padding: var(--cellpad) var(--cellpad-x) !important;
272
+ height: auto !important;
273
+ font-variant-numeric: tabular-nums;
274
+ }
275
+
276
+ .table-card :deep(tbody tr:hover > td) {
277
+ background: var(--hover);
278
+ }
279
+
280
+ /* ------------------------------------------------------------------ */
281
+ /* Empty state, in-card search, buttons and in-card tabs. */
282
+ /* ------------------------------------------------------------------ */
283
+
284
+ .table-card__empty {
285
+ display: flex;
286
+ flex-direction: column;
287
+ align-items: center;
288
+ justify-content: center;
289
+ gap: 8px;
290
+ padding: 52px 16px;
291
+ font-size: var(--fs-cell);
292
+ color: var(--muted);
293
+ }
294
+
295
+ .table-card__empty :deep(.v-icon) {
296
+ color: var(--muted);
297
+ }
298
+
299
+ /* The in-card variant: 34px, 10px radius, as drawn. */
300
+ .table-card__search :deep(.v-field) {
301
+ border-radius: var(--r-inner);
302
+ font-size: var(--fs-cell);
303
+ }
304
+
305
+ .table-card__primary {
306
+ height: var(--btn-h);
307
+ border-radius: var(--r-inner);
308
+ padding: 0 16px;
309
+ font-size: var(--fs-btn);
310
+ font-weight: var(--fw-btn-strong);
311
+ letter-spacing: 0;
312
+ background: var(--accent-strong);
313
+ color: var(--on-accent-strong);
314
+ }
315
+
316
+ .table-card__ghost {
317
+ height: var(--btn-h);
318
+ border-radius: var(--r-inner);
319
+ padding: 0 16px;
320
+ font-size: var(--fs-btn);
321
+ font-weight: var(--fw-btn);
322
+ letter-spacing: 0;
323
+ color: var(--text2);
324
+ border-color: var(--border);
325
+ background: var(--card);
326
+ }
327
+
328
+ /**
329
+ * Tabs that a screen puts in this card's extension slot: 13px, the active one
330
+ * at 800 in `--accent-text` over a 2px accent underline. Tabs elsewhere in the
331
+ * product are their own screens' and are restyled with them.
332
+ */
333
+ .table-card :deep(.v-tab) {
334
+ font-size: var(--fs-nav);
335
+ font-weight: var(--fw-cell);
336
+ letter-spacing: 0;
337
+ text-transform: none;
338
+ color: var(--muted);
339
+ min-width: 0;
340
+ }
341
+
342
+ .table-card :deep(.v-tab.v-tab--selected) {
343
+ font-weight: var(--fw-table-head);
344
+ color: var(--accent-text);
345
+ }
346
+
347
+ .table-card :deep(.v-tab .v-tab__slider) {
348
+ height: 2px;
349
+ background: var(--accent);
192
350
  }
193
351
  </style>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.2-staging.82",
5
+ "version": "3.2.2-staging.83",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {