@7365admin1/layer-common 3.2.1 → 3.2.2-staging.100
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/.changeset/camera-wall-gated-endpoint.md +47 -0
- package/.changeset/camera-wall-plain-english.md +60 -0
- package/.changeset/camera-wall-selection-and-guidance.md +65 -0
- package/.changeset/dark-primary-contrast.md +39 -0
- package/.changeset/dashboard-dark-theme-contrast.md +15 -0
- package/.changeset/notification-preferences.md +17 -0
- package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +27 -0
- package/.changeset/service-provider-invitation-actions.md +28 -0
- package/assets/css/primitives.css +680 -0
- package/assets/css/screens.css +541 -0
- package/assets/css/shell.css +258 -0
- package/assets/css/tokens.css +220 -0
- package/components/AccessCardQrTagging.vue +1 -1
- package/components/AccessManagement.vue +53 -72
- package/components/AppButton.vue +52 -0
- package/components/AppCard.vue +20 -0
- package/components/AppDateField.vue +35 -0
- package/components/AppField.vue +46 -0
- package/components/AppKpiTile.vue +65 -0
- package/components/AppSegmented.vue +38 -0
- package/components/AppSelect.vue +203 -0
- package/components/AreaChecklistHistoryMain.vue +6 -2
- package/components/AreaMain.vue +9 -17
- package/components/AttendanceMain.vue +4 -3
- package/components/Avatar/Main.vue +54 -2
- package/components/BillingMain.vue +26 -30
- package/components/BuildingManagement/buildings.vue +27 -38
- package/components/BuildingManagement/units.vue +26 -42
- package/components/BulletinBoardManagement.vue +23 -10
- package/components/CameraWall.vue +1034 -0
- package/components/CameraWallTile.vue +818 -0
- package/components/CardToolbar.vue +37 -0
- package/components/CleaningScheduleMain.vue +29 -46
- package/components/ClientMain.vue +90 -78
- package/components/DashboardEmptyState.vue +11 -1
- package/components/DashboardMain.vue +349 -275
- package/components/DashboardPanel.vue +1 -1
- package/components/DocumentManagement.vue +129 -93
- package/components/EntryPassInformation.vue +4 -4
- package/components/EntryPassMain.vue +49 -27
- package/components/EquipmentItemMain.vue +4 -6
- package/components/EquipmentManagementMain.vue +9 -7
- package/components/FeedbackMain.vue +42 -56
- package/components/FormDialog.vue +60 -21
- package/components/HidAccessLogDashboard.vue +119 -44
- package/components/HidCameraCaptureDialog.vue +199 -0
- package/components/HidIntercomManagement.vue +825 -202
- package/components/HidQrCodeConfiguration.vue +927 -93
- package/components/HidServiceSettingsPanel.vue +20 -1
- package/components/HidUserEnrollment.vue +454 -106
- package/components/InvitationMain.vue +46 -61
- package/components/Layout/Header.vue +289 -37
- package/components/Layout/NavigationDrawer.vue +340 -30
- package/components/ManageChecklistMain.vue +17 -17
- package/components/MemberMain.vue +79 -60
- package/components/MyAttendanceMain.vue +3 -5
- package/components/NavigationItem.vue +9 -0
- package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
- package/components/NotificationSettings.vue +361 -0
- package/components/OnlineFormConfigurationForm.vue +620 -224
- package/components/OnlineFormFill.vue +36 -8
- package/components/OnlineFormsConfiguration.vue +7 -3
- package/components/PageHeader.vue +43 -0
- package/components/PassInformation.vue +104 -45
- package/components/QrTemplate/CreditCardLandscape.vue +19 -12
- package/components/QrTemplate/PrintDialog.vue +209 -196
- package/components/RolePermissionMain.vue +184 -91
- package/components/ScanVisitorQRCode.vue +35 -7
- package/components/ScheduleAreaMain.vue +15 -49
- package/components/ScheduleTaskMain.vue +23 -26
- package/components/ScheduleTastTicketMain.vue +18 -43
- package/components/ServiceProviderInvitationPrompt.vue +150 -0
- package/components/ServiceProviderMain.vue +346 -157
- package/components/SiteSettings.vue +25 -10
- package/components/SpecificAttr.vue +10 -2
- package/components/StatusChip.vue +93 -0
- package/components/SwitchContext.vue +33 -1
- package/components/TableHygiene.vue +29 -46
- package/components/TableList.vue +1 -1
- package/components/TableListSecondary.vue +1 -1
- package/components/TableMain.vue +161 -77
- package/components/TableV3.vue +61 -66
- package/components/UnitMain.vue +9 -17
- package/components/VehicleManagement.vue +20 -9
- package/components/VisitorForm.vue +294 -78
- package/components/VisitorManagement.vue +221 -71
- package/components/VisitorSocketPopUp.vue +56 -2
- package/components/WorkOrder/Main.vue +42 -51
- package/composables/useComment.ts +3 -3
- package/composables/useHidAmico.ts +151 -0
- package/composables/useHidNavigation.ts +0 -7
- package/composables/useLocalAuth.ts +9 -36
- package/composables/useNotificationPreference.ts +87 -0
- package/composables/useOnlineFormPages.ts +2 -0
- package/composables/useOrg.ts +1 -1
- package/composables/useRole.ts +22 -0
- package/composables/useServiceProviderInvitation.ts +145 -0
- package/composables/useSipWebPhone.ts +336 -0
- package/composables/useSiteSettings.ts +50 -0
- package/composables/useThemePreference.ts +63 -0
- package/composables/useVisitor.ts +6 -1
- package/composables/useVisitorSocket.ts +26 -0
- package/composables/useWebUsb.ts +127 -37
- package/nuxt.config.ts +31 -0
- package/package.json +4 -1
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/pages/notification-settings.vue +19 -0
- package/plugins/secure-member.client.ts +21 -56
- package/plugins/vuetify.ts +68 -9
- package/test/visitor-socket.test.mjs +36 -0
- package/types/org.d.ts +2 -0
- package/types/site.d.ts +71 -0
- package/utils/camera-wall.test.ts +571 -0
- package/utils/camera-wall.ts +926 -0
- package/utils/nav-sections.test.ts +190 -0
- package/utils/nav-sections.ts +151 -0
- package/utils/status.test.ts +77 -0
- package/utils/status.ts +90 -0
- package/utils/theme.test.ts +534 -0
- package/utils/theme.ts +294 -0
- package/components/HidIdentityMapping.vue +0 -975
- package/middleware/member.ts +0 -4
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLATFORM REDESIGN - THE PAGE PRIMITIVES.
|
|
3
|
+
*
|
|
4
|
+
* Phases 1-5 tokenised what Vuetify draws. This file is the other half: the
|
|
5
|
+
* pieces the design draws that Vuetify has no component for, taken as literal
|
|
6
|
+
* CSS out of the handoff prototype
|
|
7
|
+
* (`design_handoff_platform_redesign/iService365 Redesign.dc.html`) rather
|
|
8
|
+
* than re-derived by eye. Every declaration below has a line in that file.
|
|
9
|
+
*
|
|
10
|
+
* The rule for a screen adopting these: MARKUP, not a theme override. A
|
|
11
|
+
* `.v-btn` restyled into a pill is still a `.v-btn` with Vuetify's ripple,
|
|
12
|
+
* overlay, min-width and uppercase tracking fighting it at a higher
|
|
13
|
+
* specificity; a `<button class="app-btn">` simply is the button in the
|
|
14
|
+
* design. Where Vuetify owns the behaviour (the data table, the menu, the
|
|
15
|
+
* dialog) it stays - only the chrome around it is replaced.
|
|
16
|
+
*
|
|
17
|
+
* Numbers, for review against the handoff:
|
|
18
|
+
* page title 22px/800/-0.01em · card radius 14px, 1px border, --shadow
|
|
19
|
+
* in-card toolbar 0 20px, tabs 14px 2px 12px, 2px accent underline
|
|
20
|
+
* fields 40px (34px compact), 10px radius, 13.5px/500
|
|
21
|
+
* primary button 40px, 10px radius, 13.5px/800 · ghost 38px, 13px/700
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/* ------------------------------------------------------------------ */
|
|
25
|
+
/* The typeface. */
|
|
26
|
+
/* ------------------------------------------------------------------ */
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Phase 1 loaded Manrope and deliberately did not apply it, so that the
|
|
30
|
+
* product would not sit half-migrated between two typefaces. The page
|
|
31
|
+
* scaffold is the migration, so the font goes on now - on Vuetify's own
|
|
32
|
+
* application root, which is every rendered page in all eleven apps.
|
|
33
|
+
*
|
|
34
|
+
* `.v-application` and not `body`: Vuetify sets `font-family` on that class
|
|
35
|
+
* itself, so a rule on `body` loses to it and the product would keep Roboto
|
|
36
|
+
* everywhere while the new primitives alone changed - the exact
|
|
37
|
+
* half-migrated look this was avoiding.
|
|
38
|
+
*/
|
|
39
|
+
.v-application,
|
|
40
|
+
.v-overlay-container {
|
|
41
|
+
font-family: var(--font-sans);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* ------------------------------------------------------------------ */
|
|
45
|
+
/* PageHeader - the row every screen opens with. */
|
|
46
|
+
/* ------------------------------------------------------------------ */
|
|
47
|
+
|
|
48
|
+
.app-page-head {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
gap: 16px;
|
|
53
|
+
margin-bottom: 18px;
|
|
54
|
+
flex-wrap: wrap;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.app-page-head__title {
|
|
58
|
+
margin: 0;
|
|
59
|
+
font-size: var(--fs-h1);
|
|
60
|
+
font-weight: var(--fw-h1);
|
|
61
|
+
letter-spacing: var(--ls-h1);
|
|
62
|
+
line-height: 1.25;
|
|
63
|
+
color: var(--text);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.app-page-head__sub {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
gap: 5px;
|
|
70
|
+
margin-top: 4px;
|
|
71
|
+
font-size: 12.5px;
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
color: var(--muted);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.app-page-head__actions {
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: 10px;
|
|
80
|
+
flex-wrap: wrap;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ------------------------------------------------------------------ */
|
|
84
|
+
/* AppCard - the surface everything else sits on. */
|
|
85
|
+
/* ------------------------------------------------------------------ */
|
|
86
|
+
|
|
87
|
+
.app-card {
|
|
88
|
+
background: var(--card);
|
|
89
|
+
border: 1px solid var(--border);
|
|
90
|
+
border-radius: var(--r-card);
|
|
91
|
+
box-shadow: var(--shadow);
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.app-card--flush {
|
|
96
|
+
overflow: visible;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ------------------------------------------------------------------ */
|
|
100
|
+
/* CardToolbar - the in-card top row. */
|
|
101
|
+
/* ------------------------------------------------------------------ */
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Two shapes in the handoff, one component. With tabs the row is `0 20px` and
|
|
105
|
+
* the tabs supply their own vertical padding, so the underline meets the
|
|
106
|
+
* card's rule; without tabs (a refresh button on the left) it is `10px 14px`.
|
|
107
|
+
*/
|
|
108
|
+
.app-toolbar {
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
/* One row at desktop widths, as drawn. On a phone the tabs and the
|
|
112
|
+
search/pager group drop onto their own lines rather than overflow. */
|
|
113
|
+
flex-wrap: wrap;
|
|
114
|
+
gap: 22px;
|
|
115
|
+
padding: 0 var(--cellpad-x);
|
|
116
|
+
border-bottom: 1px solid var(--border);
|
|
117
|
+
min-height: 48px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.app-toolbar--plain {
|
|
121
|
+
gap: 8px;
|
|
122
|
+
padding: 10px 14px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.app-toolbar__spacer {
|
|
126
|
+
flex: 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.app-toolbar__right {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
gap: 8px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* The list toolbar's own search box. `--search` alone stretches (it is written
|
|
136
|
+
for the filter row), which would push the pager off the end of the row. */
|
|
137
|
+
.app-toolbar__search.app-field--search {
|
|
138
|
+
flex: 0 0 auto;
|
|
139
|
+
width: 200px;
|
|
140
|
+
min-width: 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.app-toolbar__count {
|
|
144
|
+
font-size: 12.5px;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
color: var(--muted);
|
|
147
|
+
font-variant-numeric: tabular-nums;
|
|
148
|
+
white-space: nowrap;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.app-tabs {
|
|
152
|
+
display: flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
gap: 22px;
|
|
155
|
+
min-width: 0;
|
|
156
|
+
overflow-x: auto;
|
|
157
|
+
scrollbar-width: none;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.app-tabs::-webkit-scrollbar {
|
|
161
|
+
display: none;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.app-tab {
|
|
165
|
+
padding: 14px 2px 12px 2px;
|
|
166
|
+
border: none;
|
|
167
|
+
background: transparent;
|
|
168
|
+
border-bottom: 2px solid transparent;
|
|
169
|
+
font-family: inherit;
|
|
170
|
+
font-size: var(--fs-nav);
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
color: var(--muted);
|
|
173
|
+
white-space: nowrap;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
/* A tab is a <button> on the screens that switch a list in place and a link
|
|
176
|
+
on the ones that route (Invitations), so the underline has to be ours. */
|
|
177
|
+
text-decoration: none;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.app-tab:hover {
|
|
181
|
+
color: var(--text2);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.app-tab--active {
|
|
185
|
+
font-weight: 800;
|
|
186
|
+
color: var(--accent-text);
|
|
187
|
+
border-bottom-color: var(--accent);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ------------------------------------------------------------------ */
|
|
191
|
+
/* The table, drawn as a grid. */
|
|
192
|
+
/* ------------------------------------------------------------------ */
|
|
193
|
+
|
|
194
|
+
.app-thead {
|
|
195
|
+
display: grid;
|
|
196
|
+
gap: 12px;
|
|
197
|
+
padding: 12px var(--cellpad-x);
|
|
198
|
+
font-size: var(--fs-table-head);
|
|
199
|
+
font-weight: var(--fw-table-head);
|
|
200
|
+
letter-spacing: var(--ls-table-head);
|
|
201
|
+
text-transform: uppercase;
|
|
202
|
+
color: var(--muted);
|
|
203
|
+
background: var(--thead);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.app-trow {
|
|
207
|
+
display: grid;
|
|
208
|
+
gap: 12px;
|
|
209
|
+
align-items: center;
|
|
210
|
+
padding: var(--cellpad) var(--cellpad-x);
|
|
211
|
+
border-top: 1px solid var(--border);
|
|
212
|
+
font-size: 13px;
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
color: var(--text);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.app-trow:hover {
|
|
218
|
+
background: var(--hover);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.app-cell--strong {
|
|
222
|
+
font-weight: 700;
|
|
223
|
+
overflow: hidden;
|
|
224
|
+
text-overflow: ellipsis;
|
|
225
|
+
white-space: nowrap;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.app-cell--muted {
|
|
229
|
+
color: var(--text2);
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
text-overflow: ellipsis;
|
|
232
|
+
white-space: nowrap;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.app-cell--num {
|
|
236
|
+
color: var(--text2);
|
|
237
|
+
font-variant-numeric: tabular-nums;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.app-empty {
|
|
241
|
+
display: flex;
|
|
242
|
+
flex-direction: column;
|
|
243
|
+
align-items: center;
|
|
244
|
+
gap: 8px;
|
|
245
|
+
padding: 52px var(--cellpad-x);
|
|
246
|
+
border-top: 1px solid var(--border);
|
|
247
|
+
font-size: 13px;
|
|
248
|
+
font-weight: 600;
|
|
249
|
+
color: var(--muted);
|
|
250
|
+
text-align: center;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* ------------------------------------------------------------------ */
|
|
254
|
+
/* AppButton. */
|
|
255
|
+
/* ------------------------------------------------------------------ */
|
|
256
|
+
|
|
257
|
+
.app-btn {
|
|
258
|
+
display: inline-flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: center;
|
|
261
|
+
gap: 6px;
|
|
262
|
+
height: var(--btn-h);
|
|
263
|
+
padding: 0 16px;
|
|
264
|
+
border: none;
|
|
265
|
+
border-radius: var(--r-inner);
|
|
266
|
+
/* `--accent` at 3.23:1 cannot carry a white label - see Phase 1. The fill,
|
|
267
|
+
and only the fill, uses the 18%-darker `--accent-strong`; the underline,
|
|
268
|
+
the links and every soft background stay on `--accent`. */
|
|
269
|
+
background: var(--accent-strong);
|
|
270
|
+
color: var(--on-accent-strong);
|
|
271
|
+
font-family: inherit;
|
|
272
|
+
font-size: var(--fs-btn);
|
|
273
|
+
font-weight: var(--fw-btn-strong);
|
|
274
|
+
line-height: 1;
|
|
275
|
+
white-space: nowrap;
|
|
276
|
+
cursor: pointer;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.app-btn:hover {
|
|
280
|
+
filter: brightness(1.06);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.app-btn:disabled {
|
|
284
|
+
opacity: 0.5;
|
|
285
|
+
cursor: default;
|
|
286
|
+
filter: none;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.app-btn--ghost {
|
|
290
|
+
height: 38px;
|
|
291
|
+
padding: 0 14px;
|
|
292
|
+
border: 1px solid var(--border);
|
|
293
|
+
background: var(--card);
|
|
294
|
+
color: var(--text2);
|
|
295
|
+
font-size: var(--fs-nav);
|
|
296
|
+
font-weight: var(--fw-btn);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.app-btn--ghost:hover {
|
|
300
|
+
background: var(--hover);
|
|
301
|
+
filter: none;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.app-btn--icon {
|
|
305
|
+
width: 32px;
|
|
306
|
+
height: 32px;
|
|
307
|
+
padding: 0;
|
|
308
|
+
border: none;
|
|
309
|
+
border-radius: 8px;
|
|
310
|
+
background: transparent;
|
|
311
|
+
color: var(--text2);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.app-btn--icon:hover {
|
|
315
|
+
background: var(--hover);
|
|
316
|
+
filter: none;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* The 30px bordered row action - the `more_vert` at the end of a member row. */
|
|
320
|
+
.app-btn--row {
|
|
321
|
+
width: 30px;
|
|
322
|
+
height: 30px;
|
|
323
|
+
padding: 0;
|
|
324
|
+
border: 1px solid var(--border);
|
|
325
|
+
border-radius: 8px;
|
|
326
|
+
background: transparent;
|
|
327
|
+
color: var(--muted);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.app-btn--row:hover {
|
|
331
|
+
background: var(--hover);
|
|
332
|
+
filter: none;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* ------------------------------------------------------------------ */
|
|
336
|
+
/* AppField / AppSelect / AppDateField. */
|
|
337
|
+
/* ------------------------------------------------------------------ */
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Flat and compact, with the label OUTSIDE the box as a small muted word -
|
|
341
|
+
* not Vuetify's outlined field with a label that floats through the border.
|
|
342
|
+
* A native `<input>`/`<select>`, so the browser keeps the keyboard, the date
|
|
343
|
+
* picker and the accessibility for free.
|
|
344
|
+
*/
|
|
345
|
+
.app-field {
|
|
346
|
+
display: flex;
|
|
347
|
+
align-items: center;
|
|
348
|
+
gap: 8px;
|
|
349
|
+
height: var(--input-h);
|
|
350
|
+
padding: 0 12px;
|
|
351
|
+
border: 1px solid var(--border);
|
|
352
|
+
border-radius: var(--r-inner);
|
|
353
|
+
background: var(--card);
|
|
354
|
+
font-size: var(--fs-nav);
|
|
355
|
+
font-weight: 600;
|
|
356
|
+
color: var(--text);
|
|
357
|
+
min-width: 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.app-field--compact {
|
|
361
|
+
height: var(--input-h-compact);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.app-field:focus-within {
|
|
365
|
+
border-color: var(--accent);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.app-field__label {
|
|
369
|
+
font-size: 12px;
|
|
370
|
+
font-weight: 600;
|
|
371
|
+
color: var(--muted);
|
|
372
|
+
white-space: nowrap;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.app-field__icon {
|
|
376
|
+
color: var(--muted);
|
|
377
|
+
flex: 0 0 auto;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.app-field__input {
|
|
381
|
+
flex: 1;
|
|
382
|
+
min-width: 0;
|
|
383
|
+
height: 100%;
|
|
384
|
+
border: none;
|
|
385
|
+
outline: none;
|
|
386
|
+
background: transparent;
|
|
387
|
+
font-family: inherit;
|
|
388
|
+
font-size: var(--fs-cell);
|
|
389
|
+
font-weight: 500;
|
|
390
|
+
color: var(--text);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.app-field__input::placeholder {
|
|
394
|
+
color: var(--muted);
|
|
395
|
+
opacity: 1;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/* A `<select>` keeps the native menu; only the chrome is ours. */
|
|
399
|
+
select.app-field__input {
|
|
400
|
+
font-weight: 600;
|
|
401
|
+
font-size: var(--fs-nav);
|
|
402
|
+
appearance: none;
|
|
403
|
+
cursor: pointer;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/*
|
|
407
|
+
* The MULTI select. Native has no dropdown for `multiple` - only an
|
|
408
|
+
* always-open list box - so multi wears the same field chrome as a button and
|
|
409
|
+
* opens a popover. The chosen values are `StatusChip pill tone-neutral`, the
|
|
410
|
+
* soft pill the design already uses and the one whose contrast is measured.
|
|
411
|
+
*/
|
|
412
|
+
.app-select {
|
|
413
|
+
cursor: pointer;
|
|
414
|
+
overflow: hidden;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.app-select--disabled {
|
|
418
|
+
cursor: default;
|
|
419
|
+
opacity: 0.55;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.app-select__values {
|
|
423
|
+
display: flex;
|
|
424
|
+
align-items: center;
|
|
425
|
+
gap: 6px;
|
|
426
|
+
flex: 1;
|
|
427
|
+
min-width: 0;
|
|
428
|
+
overflow: hidden;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.app-select__placeholder {
|
|
432
|
+
font-size: var(--fs-cell);
|
|
433
|
+
font-weight: 500;
|
|
434
|
+
color: var(--muted);
|
|
435
|
+
white-space: nowrap;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* The x that removes one pill, and the one that empties the field. */
|
|
439
|
+
.app-select__x,
|
|
440
|
+
.app-select__clear {
|
|
441
|
+
display: inline-flex;
|
|
442
|
+
align-items: center;
|
|
443
|
+
color: inherit;
|
|
444
|
+
opacity: 0.7;
|
|
445
|
+
background: none;
|
|
446
|
+
border: none;
|
|
447
|
+
padding: 0;
|
|
448
|
+
cursor: pointer;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.app-select__x:hover,
|
|
452
|
+
.app-select__clear:hover {
|
|
453
|
+
opacity: 1;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.app-select__clear {
|
|
457
|
+
color: var(--muted);
|
|
458
|
+
flex: 0 0 auto;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.app-select__menu {
|
|
462
|
+
min-width: 200px;
|
|
463
|
+
max-height: 280px;
|
|
464
|
+
overflow-y: auto;
|
|
465
|
+
padding: 6px;
|
|
466
|
+
border: 1px solid var(--border);
|
|
467
|
+
border-radius: var(--r-inner-lg);
|
|
468
|
+
background: var(--card);
|
|
469
|
+
box-shadow: var(--shadow-menu);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.app-select__option {
|
|
473
|
+
display: flex;
|
|
474
|
+
align-items: center;
|
|
475
|
+
gap: 8px;
|
|
476
|
+
width: 100%;
|
|
477
|
+
padding: 7px 8px;
|
|
478
|
+
border: none;
|
|
479
|
+
border-radius: var(--r-inner-sm);
|
|
480
|
+
background: none;
|
|
481
|
+
font-family: var(--font-sans);
|
|
482
|
+
font-size: var(--fs-cell);
|
|
483
|
+
font-weight: 500;
|
|
484
|
+
color: var(--text);
|
|
485
|
+
text-align: left;
|
|
486
|
+
cursor: pointer;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.app-select__option:hover {
|
|
490
|
+
background: var(--hover);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.app-select__box {
|
|
494
|
+
color: var(--muted);
|
|
495
|
+
flex: 0 0 auto;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.app-select__box--on {
|
|
499
|
+
color: var(--accent);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.app-select__option-text {
|
|
503
|
+
min-width: 0;
|
|
504
|
+
overflow: hidden;
|
|
505
|
+
text-overflow: ellipsis;
|
|
506
|
+
white-space: nowrap;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.app-field--search {
|
|
510
|
+
flex: 1;
|
|
511
|
+
min-width: 220px;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.app-filter-row {
|
|
515
|
+
display: flex;
|
|
516
|
+
gap: var(--grid-gap);
|
|
517
|
+
margin-bottom: 14px;
|
|
518
|
+
flex-wrap: wrap;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
@media (max-width: 599.98px) {
|
|
522
|
+
.app-filter-row > * {
|
|
523
|
+
flex: 1 1 100%;
|
|
524
|
+
min-width: 0;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/* ------------------------------------------------------------------ */
|
|
529
|
+
/* SegmentedControl - Today | Week | Month. */
|
|
530
|
+
/* ------------------------------------------------------------------ */
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* The handoff draws this as a 3px-padded bordered tray holding 8px-radius
|
|
534
|
+
* segments; the SELECTED one is an ACCENT FILL, not a grey one. Vuetify's
|
|
535
|
+
* `v-btn-toggle` cannot be talked into that shape (its own height scale, its
|
|
536
|
+
* dividers, its overlay layer), so the tray is written as markup.
|
|
537
|
+
*
|
|
538
|
+
* The fill is `--accent-strong`, not `--accent`: white on `--accent` measures
|
|
539
|
+
* 3.23:1, the same Phase-1 rule every other filled label follows.
|
|
540
|
+
*/
|
|
541
|
+
.app-seg {
|
|
542
|
+
display: flex;
|
|
543
|
+
gap: 2px;
|
|
544
|
+
padding: 3px;
|
|
545
|
+
border: 1px solid var(--border);
|
|
546
|
+
border-radius: var(--r-inner);
|
|
547
|
+
background: var(--card);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.app-seg__item {
|
|
551
|
+
padding: 6px 12px;
|
|
552
|
+
border: none;
|
|
553
|
+
border-radius: 8px;
|
|
554
|
+
background: transparent;
|
|
555
|
+
color: var(--muted);
|
|
556
|
+
font-family: inherit;
|
|
557
|
+
font-size: 12.5px;
|
|
558
|
+
font-weight: 700;
|
|
559
|
+
line-height: 1.2;
|
|
560
|
+
white-space: nowrap;
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.app-seg__item:hover {
|
|
565
|
+
background: var(--hover);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.app-seg__item--on,
|
|
569
|
+
.app-seg__item--on:hover {
|
|
570
|
+
background: var(--accent-strong);
|
|
571
|
+
color: var(--on-accent-strong);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/* ------------------------------------------------------------------ */
|
|
575
|
+
/* KpiTile - the dashboard's headline figure. */
|
|
576
|
+
/* ------------------------------------------------------------------ */
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Three rows, in this order, straight from the handoff:
|
|
580
|
+
* 1. a 34px soft icon square and the uppercase label, side by side
|
|
581
|
+
* 2. the 30px tabular number with its percentage pill BESIDE it
|
|
582
|
+
* 3. the sub-line
|
|
583
|
+
* The product had the icon alone on row 1 and the pill stranded at the
|
|
584
|
+
* bottom, which is why the tile read as a different component.
|
|
585
|
+
*/
|
|
586
|
+
.app-kpi {
|
|
587
|
+
padding: 18px;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.app-kpi__head {
|
|
591
|
+
display: flex;
|
|
592
|
+
align-items: center;
|
|
593
|
+
gap: 10px;
|
|
594
|
+
margin-bottom: 14px;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.app-kpi__icon {
|
|
598
|
+
display: flex;
|
|
599
|
+
align-items: center;
|
|
600
|
+
justify-content: center;
|
|
601
|
+
width: 34px;
|
|
602
|
+
height: 34px;
|
|
603
|
+
flex: 0 0 34px;
|
|
604
|
+
border-radius: var(--r-inner);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.app-kpi__label {
|
|
608
|
+
font-size: var(--fs-table-head);
|
|
609
|
+
font-weight: var(--fw-table-head);
|
|
610
|
+
letter-spacing: var(--ls-table-head);
|
|
611
|
+
text-transform: uppercase;
|
|
612
|
+
color: var(--muted);
|
|
613
|
+
min-width: 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.app-kpi__action {
|
|
617
|
+
margin-left: auto;
|
|
618
|
+
flex: 0 0 auto;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.app-kpi__figure {
|
|
622
|
+
display: flex;
|
|
623
|
+
align-items: center;
|
|
624
|
+
gap: 10px;
|
|
625
|
+
flex-wrap: wrap;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.app-kpi__value {
|
|
629
|
+
font-size: var(--fs-kpi);
|
|
630
|
+
font-weight: var(--fw-kpi);
|
|
631
|
+
letter-spacing: var(--ls-kpi);
|
|
632
|
+
line-height: 1.1;
|
|
633
|
+
color: var(--text);
|
|
634
|
+
font-variant-numeric: tabular-nums;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.app-kpi__chip {
|
|
638
|
+
display: inline-flex;
|
|
639
|
+
align-items: center;
|
|
640
|
+
padding: 3px 8px;
|
|
641
|
+
border-radius: 999px;
|
|
642
|
+
font-size: 11.5px;
|
|
643
|
+
font-weight: 800;
|
|
644
|
+
line-height: 1.2;
|
|
645
|
+
background: var(--ok-bg);
|
|
646
|
+
color: var(--ok);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.app-kpi__chip--down {
|
|
650
|
+
background: var(--err-bg);
|
|
651
|
+
color: var(--err);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.app-kpi__sub {
|
|
655
|
+
margin-top: 6px;
|
|
656
|
+
font-size: 12.5px;
|
|
657
|
+
font-weight: 600;
|
|
658
|
+
color: var(--muted);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/* ------------------------------------------------------------------ */
|
|
662
|
+
/* Responsive - the prototype has no reference, this follows Phase 1. */
|
|
663
|
+
/* ------------------------------------------------------------------ */
|
|
664
|
+
|
|
665
|
+
@media (max-width: 767.98px) {
|
|
666
|
+
.app-page-head {
|
|
667
|
+
align-items: flex-start;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* The grid table keeps its columns and scrolls sideways inside the card,
|
|
671
|
+
deliberately not restacked into key/value blocks: the columns carry
|
|
672
|
+
meaning by position. Same decision as Phase 1's tables. */
|
|
673
|
+
.app-table-scroll {
|
|
674
|
+
overflow-x: auto;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.app-table-scroll > * {
|
|
678
|
+
min-width: 720px;
|
|
679
|
+
}
|
|
680
|
+
}
|