@visns-studio/visns-components 6.24.4 → 6.26.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/package.json +4 -2
- package/src/components/Autocomplete.jsx +189 -119
- package/src/components/DataGrid.jsx +472 -31
- package/src/components/Navigation.jsx +475 -51
- package/src/components/auth/ClientAuthFrame.jsx +5 -0
- package/src/components/auth/ClientAuthScreen.jsx +29 -0
- package/src/components/callQueue/CallQueueDiagnostics.jsx +1043 -0
- package/src/components/callQueue/CallQueuePop.jsx +713 -90
- package/src/components/callQueue/CallQueueSettings.jsx +308 -146
- package/src/components/callQueue/callPopStatus.js +236 -0
- package/src/components/callQueue/callQueueHelpers.js +284 -2
- package/src/components/columns/ColumnRenderers.jsx +3 -46
- package/src/components/columns/StackedRow.jsx +186 -0
- package/src/components/controls/DataGridSearch.jsx +110 -2
- package/src/components/controls/DataGridSortSheet.jsx +155 -0
- package/src/components/generic/GenericAuth.jsx +50 -18
- package/src/components/generic/GenericDashboard.jsx +20 -1
- package/src/components/generic/GenericDetail.jsx +446 -259
- package/src/components/mapboxSearchBox.js +640 -0
- package/src/components/navBadges.js +63 -1
- package/src/components/navDrawer.js +147 -0
- package/src/components/sms/SmsThreadPanel.jsx +34 -6
- package/src/components/sms/smsHelpers.js +15 -0
- package/src/components/styles/CallQueueDiagnostics.module.scss +398 -0
- package/src/components/styles/CallQueuePop.module.scss +29 -0
- package/src/components/styles/CallQueueSettings.module.scss +93 -0
- package/src/components/styles/ClientAuth.module.scss +39 -0
- package/src/components/styles/DataGrid.module.scss +158 -5
- package/src/components/styles/Field.module.scss +52 -1
- package/src/components/styles/Form.module.scss +82 -0
- package/src/components/styles/GenericClientPortal.module.scss +72 -20
- package/src/components/styles/GenericDashboard.module.scss +50 -0
- package/src/components/styles/GenericDetail.module.scss +63 -1
- package/src/components/styles/GenericDynamic.module.scss +23 -0
- package/src/components/styles/GenericFormBuilder.module.scss +11 -0
- package/src/components/styles/GenericIndex.module.scss +6 -1
- package/src/components/styles/Navigation.module.scss +460 -7
- package/src/components/styles/Sms.module.scss +92 -0
- package/src/components/styles/StackedRow.module.scss +182 -0
- package/src/components/styles/TicketConversation.module.scss +76 -0
- package/src/components/styles/Vault.module.scss +192 -0
- package/src/components/styles/density.css +10 -0
- package/src/components/styles/global-datagrid.css +163 -0
- package/src/components/styles/global.css +20 -0
- package/src/components/tickets/TicketConversation.jsx +13 -8
- package/src/components/utils/ConfirmDialog.js +22 -3
- package/src/components/utils/cardLayout.js +666 -0
- package/src/components/utils/contactChannels.js +130 -0
- package/src/components/utils/editPlacement.js +95 -0
- package/src/components/utils/useDensity.js +303 -7
- package/src/index.js +42 -0
|
@@ -177,10 +177,22 @@
|
|
|
177
177
|
z-index: 1000;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/* The wide dialog: 90% of the glass, never past 1400px, and never narrower
|
|
181
|
+
than 600px — except that a 600px floor on a 390px phone is not a floor, it
|
|
182
|
+
is a 210px overflow with the dialog's right-hand edge and its close button
|
|
183
|
+
off the screen. `min-width` outranks `width` and `max-width` both, so the
|
|
184
|
+
flat 600 won every time it was wrong.
|
|
185
|
+
*
|
|
186
|
+
* `min(600px, 90vw)` keeps the floor doing its job on anything wide enough to
|
|
187
|
+
* honour it and hands the decision back to `width` on anything narrower — and
|
|
188
|
+
* 90vw is not a new number, it is the width this rule already asks for, so the
|
|
189
|
+
* floor can never outrank the line above it again. Restated verbatim in
|
|
190
|
+
* GenericIndex.module.scss and GenericDetail.module.scss; the three are one
|
|
191
|
+
* dialog wearing three hashed class names. */
|
|
180
192
|
.modalWide {
|
|
181
193
|
width: 90vw;
|
|
182
194
|
max-width: 1400px;
|
|
183
|
-
min-width: 600px;
|
|
195
|
+
min-width: min(600px, 90vw);
|
|
184
196
|
}
|
|
185
197
|
|
|
186
198
|
.modal {
|
|
@@ -1581,10 +1593,26 @@
|
|
|
1581
1593
|
}
|
|
1582
1594
|
|
|
1583
1595
|
/* Tablet mode is a bench at arm's length worked with gloves on, so the
|
|
1584
|
-
controls step up to the 44px tap-target floor.
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1596
|
+
controls step up to the 44px tap-target floor. The grid's tablet rows are
|
|
1597
|
+
52px, so 44px still clears the row edges.
|
|
1598
|
+
|
|
1599
|
+
Written once as a mixin and emitted twice, because there are now TWO signals
|
|
1600
|
+
that mean "a finger is driving this" and they must not be allowed to drift:
|
|
1601
|
+
|
|
1602
|
+
body.tablet-mode the explicit per-view opt-in, the signal the package
|
|
1603
|
+
already styles against in global-datagrid.css. It now
|
|
1604
|
+
means "treat this as touch even on a mouse".
|
|
1605
|
+
(pointer: coarse) the primary pointing device is a finger. Nobody has to
|
|
1606
|
+
set it and no view config can forget it, which is the
|
|
1607
|
+
whole point — the opt-in only ever reached the handful
|
|
1608
|
+
of views whose JSON carried it, and every phone opening
|
|
1609
|
+
every other grid got 32px targets.
|
|
1610
|
+
|
|
1611
|
+
`pointer` reports the PRIMARY device: a touchscreen laptop answers `fine`
|
|
1612
|
+
and is unaffected. `utils/useDensity.js` ORs the same two signals for the
|
|
1613
|
+
geometry the grid has to compute in JS; these two blocks and that hook are
|
|
1614
|
+
one decision expressed in the only two places it can be. */
|
|
1615
|
+
@mixin touch-tree-controls {
|
|
1588
1616
|
.treeControlBtn {
|
|
1589
1617
|
width: var(--grid-tree-control-size-tablet, 44px);
|
|
1590
1618
|
height: var(--grid-tree-control-size-tablet, 44px);
|
|
@@ -1613,6 +1641,14 @@
|
|
|
1613
1641
|
}
|
|
1614
1642
|
}
|
|
1615
1643
|
|
|
1644
|
+
:global(body.tablet-mode) {
|
|
1645
|
+
@include touch-tree-controls;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
@media (pointer: coarse) {
|
|
1649
|
+
@include touch-tree-controls;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1616
1652
|
/* =========================================================================
|
|
1617
1653
|
treeGroupBy nesting: accent bar and member indent
|
|
1618
1654
|
|
|
@@ -2141,6 +2177,32 @@
|
|
|
2141
2177
|
cursor: default;
|
|
2142
2178
|
}
|
|
2143
2179
|
|
|
2180
|
+
/* The pager, under a finger.
|
|
2181
|
+
*
|
|
2182
|
+
* 28px is a mouse target. The first and last page arrows sit side by side with
|
|
2183
|
+
* about 4px between them, and on a phone the thumb covers both — which is why
|
|
2184
|
+
* paging a grid on a phone lands on the wrong page roughly as often as the
|
|
2185
|
+
* right one. 44px is the floor, and it is affordable here because the bar
|
|
2186
|
+
* already wraps (`flex-wrap: wrap`, above): the buttons take a second line
|
|
2187
|
+
* rather than pushing the record count off the side.
|
|
2188
|
+
*
|
|
2189
|
+
* The row is the only part of the grid's own chrome that needed this. The
|
|
2190
|
+
* COLUMN FILTER ROW could not be given the same treatment from here — Inovua
|
|
2191
|
+
* measures its header height in JS from the theme's 32px and a 44px input
|
|
2192
|
+
* inside it is clipped, not accommodated — so the filters step up only as far
|
|
2193
|
+
* as the cell the theme already reserves for them (global-datagrid.css). */
|
|
2194
|
+
@media (pointer: coarse) {
|
|
2195
|
+
.dataGridBottomContainer {
|
|
2196
|
+
gap: 0.5rem;
|
|
2197
|
+
padding: 0.5rem;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
.dataGridBottomContainer button {
|
|
2201
|
+
min-width: 44px;
|
|
2202
|
+
height: 44px;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2144
2206
|
// -- empty state -------------------------------------------------------------
|
|
2145
2207
|
|
|
2146
2208
|
.noDataMessage {
|
|
@@ -2152,3 +2214,94 @@
|
|
|
2152
2214
|
color: var(--dg-muted);
|
|
2153
2215
|
font-size: 0.875rem;
|
|
2154
2216
|
}
|
|
2217
|
+
|
|
2218
|
+
/* ==========================================================================
|
|
2219
|
+
CARD MODE — the two controls that only exist below 640px
|
|
2220
|
+
--------------------------------------------------------------------------
|
|
2221
|
+
Sort, and the Cards/Table toggle. Icon squares rather than labelled buttons
|
|
2222
|
+
because they share a 340px row with Add New, where three labelled buttons
|
|
2223
|
+
wrap onto two lines.
|
|
2224
|
+
|
|
2225
|
+
`--control-height` is the same token the search field and Add New beside
|
|
2226
|
+
them resolve, so the row keeps one height; it is 2.75rem under a coarse
|
|
2227
|
+
pointer, which is also the 44px minimum a finger needs. There is no separate
|
|
2228
|
+
touch rule here because there is nothing to correct.
|
|
2229
|
+
========================================================================== */
|
|
2230
|
+
.cardControl {
|
|
2231
|
+
display: inline-flex;
|
|
2232
|
+
align-items: center;
|
|
2233
|
+
justify-content: center;
|
|
2234
|
+
width: var(--control-height, 34px);
|
|
2235
|
+
height: var(--control-height, 34px);
|
|
2236
|
+
padding: 0;
|
|
2237
|
+
color: var(--paragraph-color, #443c2b);
|
|
2238
|
+
background: var(--surface-color, #fff);
|
|
2239
|
+
border: 1px solid var(--dg-line, rgba(43, 43, 43, 0.14));
|
|
2240
|
+
border-radius: var(--btn-br, var(--radius-sm, 4px));
|
|
2241
|
+
cursor: pointer;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/* The sort sheet's list. One row per sortable field, both directions drawn —
|
|
2245
|
+
a single button that toggles makes the reader press it to find out which
|
|
2246
|
+
way it is about to go. */
|
|
2247
|
+
.sortSheet {
|
|
2248
|
+
display: flex;
|
|
2249
|
+
flex-direction: column;
|
|
2250
|
+
padding: var(--spacing-sm, 0.5rem) 0;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
.sortSheetEmpty {
|
|
2254
|
+
margin: 0;
|
|
2255
|
+
padding: var(--spacing-md, 1rem);
|
|
2256
|
+
color: var(--muted-color, #6b7280);
|
|
2257
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
.sortSheetRow {
|
|
2261
|
+
display: flex;
|
|
2262
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
2263
|
+
align-items: center;
|
|
2264
|
+
justify-content: space-between;
|
|
2265
|
+
min-height: 2.75rem;
|
|
2266
|
+
padding: 0 var(--spacing-md, 1rem);
|
|
2267
|
+
border-bottom: 1px solid var(--border-color, #e1e1e1);
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
.sortSheetLabel {
|
|
2271
|
+
display: inline-flex;
|
|
2272
|
+
gap: 0.375rem;
|
|
2273
|
+
align-items: center;
|
|
2274
|
+
overflow: hidden;
|
|
2275
|
+
min-width: 0;
|
|
2276
|
+
color: var(--paragraph-color, #443c2b);
|
|
2277
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
2278
|
+
white-space: nowrap;
|
|
2279
|
+
text-overflow: ellipsis;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
.sortSheetDirections {
|
|
2283
|
+
display: inline-flex;
|
|
2284
|
+
flex: 0 0 auto;
|
|
2285
|
+
gap: 0.25rem;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
.sortSheetDirection {
|
|
2289
|
+
display: inline-flex;
|
|
2290
|
+
align-items: center;
|
|
2291
|
+
justify-content: center;
|
|
2292
|
+
/* 2.75rem, not the control height: this is a bare icon in a list on a
|
|
2293
|
+
touch screen, and it has nothing beside it to borrow a hit area from. */
|
|
2294
|
+
width: 2.75rem;
|
|
2295
|
+
height: 2.75rem;
|
|
2296
|
+
padding: 0;
|
|
2297
|
+
color: var(--muted-color, #6b7280);
|
|
2298
|
+
background: none;
|
|
2299
|
+
border: 0;
|
|
2300
|
+
border-radius: var(--radius-sm, 4px);
|
|
2301
|
+
cursor: pointer;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
.sortSheetDirectionOn {
|
|
2305
|
+
color: var(--primary-color, #1b3933);
|
|
2306
|
+
background: var(--primary-color-lighter, rgba(27, 57, 51, 0.08));
|
|
2307
|
+
}
|
|
@@ -124,8 +124,14 @@
|
|
|
124
124
|
min-height: var(--field-height, 50px);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/* The same four-track grid Form.module.scss draws, and the same container
|
|
128
|
+
declaration, so a form assembled out of THIS module's classes collapses the
|
|
129
|
+
way one assembled out of Form's does. See the long note over
|
|
130
|
+
Form.module.scss's `.formcontainer` for why the query asks the panel rather
|
|
131
|
+
than the window, and for the one consequence of `container-type`. */
|
|
127
132
|
.formcontainer {
|
|
128
133
|
width: 100%;
|
|
134
|
+
container-type: inline-size;
|
|
129
135
|
|
|
130
136
|
form {
|
|
131
137
|
width: 100%;
|
|
@@ -309,6 +315,43 @@
|
|
|
309
315
|
width: 100%;
|
|
310
316
|
}
|
|
311
317
|
|
|
318
|
+
/* ----------------------------------------------------------------------------
|
|
319
|
+
* The field sizes, when the panel runs out of room.
|
|
320
|
+
*
|
|
321
|
+
* These are the OTHER HALF of the collapse declared over `.formcontainer` in
|
|
322
|
+
* Form.module.scss: that file narrows the track list, this one re-states what
|
|
323
|
+
* a "half" and a "quarter" mean against the narrower list. They have to be two
|
|
324
|
+
* files because CSS modules hash class names per module and Field.jsx writes
|
|
325
|
+
* these two classes while Form.jsx owns the container. Keep the breakpoints in
|
|
326
|
+
* step; there is no way to share them.
|
|
327
|
+
*
|
|
328
|
+
* The queries are unnamed, so each resolves against the nearest ancestor
|
|
329
|
+
* container — `.formcontainer`, whichever module declared it (Form's,
|
|
330
|
+
* GenericDynamic's, or this file's). A form with no container ancestor at all
|
|
331
|
+
* simply keeps the four tracks, which is the pre-collapse behaviour and a safe
|
|
332
|
+
* floor rather than a broken one.
|
|
333
|
+
*
|
|
334
|
+
* `grid-column: 1 / -1` and NOT `span 2` at the one-track step: `span 2` on a
|
|
335
|
+
* single-column grid does not clamp, it CREATES a second implicit column — the
|
|
336
|
+
* row would silently go back to two-up at exactly the width where two-up stops
|
|
337
|
+
* fitting.
|
|
338
|
+
* --------------------------------------------------------------------------*/
|
|
339
|
+
|
|
340
|
+
/* Two tracks: a half is the whole row, a quarter is half of it. */
|
|
341
|
+
@container (max-width: 768px) {
|
|
342
|
+
.halfItem {
|
|
343
|
+
grid-column: 1 / -1;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* One track: everything is the row. */
|
|
348
|
+
@container (max-width: 640px) {
|
|
349
|
+
.halfItem,
|
|
350
|
+
.qtrItem {
|
|
351
|
+
grid-column: 1 / -1;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
312
355
|
input:not(:placeholder-shown) + .fi__span,
|
|
313
356
|
textarea:not(:placeholder-shown) + .fi__span,
|
|
314
357
|
select:not(:placeholder-shown) + .fi__span {
|
|
@@ -1333,7 +1376,15 @@ input[type='file']:hover {
|
|
|
1333
1376
|
grid-template-columns: auto 1fr auto; /* Consistent grid for images */
|
|
1334
1377
|
}
|
|
1335
1378
|
|
|
1336
|
-
/* Responsive Design
|
|
1379
|
+
/* Responsive Design — the file-upload field's own previews.
|
|
1380
|
+
*
|
|
1381
|
+
* Deliberately still VIEWPORT queries while the grid collapse above is a
|
|
1382
|
+
* container query, and the two are not in conflict: the grid asks how wide its
|
|
1383
|
+
* panel is because that is what decides how many fields fit beside each other,
|
|
1384
|
+
* and these ask how big the SCREEN is because a thumbnail is sized against the
|
|
1385
|
+
* reader's eye and their thumb, not against the panel it happens to sit in.
|
|
1386
|
+
* The 768 here and the 768 above therefore agree by coincidence rather than by
|
|
1387
|
+
* contract; changing one does not oblige the other. */
|
|
1337
1388
|
@media (max-width: 768px) {
|
|
1338
1389
|
.image-preview {
|
|
1339
1390
|
max-width: 150px;
|
|
@@ -7,8 +7,46 @@
|
|
|
7
7
|
display: block;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/* ============================================================================
|
|
11
|
+
* THE FORM GRID — four tracks, and the two it gives up when there is no room
|
|
12
|
+
* ----------------------------------------------------------------------------
|
|
13
|
+
* The grid was a flat `1fr 1fr 1fr 1fr` with nothing underneath it, so a
|
|
14
|
+
* `size: "quarter"` field held one of four tracks at EVERY width: about 80px
|
|
15
|
+
* on a 390px phone, which is a label that cannot be read over an input that
|
|
16
|
+
* cannot be typed in. Two tracks at 768 and one at 640 is the collapse.
|
|
17
|
+
*
|
|
18
|
+
* WHY A CONTAINER QUERY AND NOT A MEDIA QUERY. The window's width is not the
|
|
19
|
+
* question. The same `<Form>` renders in a full-page panel, in a 560px sheet,
|
|
20
|
+
* in a modal, and in a narrow side rail beside a grid — and a form in a 420px
|
|
21
|
+
* rail on a 2560px monitor has exactly the problem a form on a phone has. A
|
|
22
|
+
* media query answers "how big is the glass"; this layout needs "how much room
|
|
23
|
+
* do I actually have", and only the container knows that. The library already
|
|
24
|
+
* reaches for this where the same argument holds (Integrations' `.fields`,
|
|
25
|
+
* global-datagrid's native date filter), so it is the house pattern rather
|
|
26
|
+
* than a new dependency. Nothing here pins a browser floor that would forbid
|
|
27
|
+
* it; the same three engines that ship `color-mix()` — used freely across this
|
|
28
|
+
* library — ship container queries.
|
|
29
|
+
*
|
|
30
|
+
* ONE CONSEQUENCE WORTH KNOWING, because it is not obvious from the one line
|
|
31
|
+
* that causes it: `container-type: inline-size` also applies LAYOUT
|
|
32
|
+
* containment, which makes this element the containing block for any
|
|
33
|
+
* `position: fixed` descendant. Inside a form that is the colour picker's
|
|
34
|
+
* click-away scrim (Field.jsx's `cover`), which now covers the form rather
|
|
35
|
+
* than the whole window — clicking another field still dismisses the picker,
|
|
36
|
+
* clicking the page header no longer does. The scrim is the only fixed thing
|
|
37
|
+
* that renders in here; `.polActions` in GenericDynamic is a SIBLING of the
|
|
38
|
+
* container, not a child, so it is untouched.
|
|
39
|
+
*
|
|
40
|
+
* The item classes that answer these queries live in Field.module.scss
|
|
41
|
+
* (`.halfItem` / `.qtrItem`, written by Field.jsx from `settings.size`) —
|
|
42
|
+
* a different module, but the query is unnamed, so it resolves against the
|
|
43
|
+
* nearest ancestor container whichever file declared it. Keep the two ends in
|
|
44
|
+
* step: the breakpoints below and the ones in Field.module.scss are one rule
|
|
45
|
+
* written in two files because the class names are hashed per module.
|
|
46
|
+
* ==========================================================================*/
|
|
10
47
|
.formcontainer {
|
|
11
48
|
width: 100%;
|
|
49
|
+
container-type: inline-size;
|
|
12
50
|
|
|
13
51
|
form {
|
|
14
52
|
width: 100%;
|
|
@@ -24,6 +62,50 @@
|
|
|
24
62
|
}
|
|
25
63
|
}
|
|
26
64
|
|
|
65
|
+
/* Two tracks. A half-width field is then the full row and a quarter is half of
|
|
66
|
+
one, which is the same proportion the four-track layout gave them — the
|
|
67
|
+
shape survives, it just has less of it.
|
|
68
|
+
*
|
|
69
|
+
* WHERE THESE TWO NUMBERS BITE, since they are container widths and not window
|
|
70
|
+
* widths and the two do not land in the same places. StandardModal's sizes are
|
|
71
|
+
* 420 / 560 / 800 and `.modal__content` spends 1rem a side, so:
|
|
72
|
+
*
|
|
73
|
+
* large (768 content) — exactly on this boundary, and `max-width` is
|
|
74
|
+
* inclusive, so a large dialog draws TWO tracks on a
|
|
75
|
+
* desktop where it used to draw four. Quarters go from
|
|
76
|
+
* ~180px to ~370px, which is roomier rather than
|
|
77
|
+
* wrong, but it is a visible change and this is where
|
|
78
|
+
* to look when somebody asks about it.
|
|
79
|
+
* medium (528 content) — one track.
|
|
80
|
+
* small (388 content) — one track.
|
|
81
|
+
* a 22rem side rail — one track.
|
|
82
|
+
*
|
|
83
|
+
* If a form is wanted back at four tracks in a large dialog, the fix is this
|
|
84
|
+
* number and only this number — but it has to move in Field.module.scss and
|
|
85
|
+
* GenericDynamic.module.scss with it. */
|
|
86
|
+
@container (max-width: 768px) {
|
|
87
|
+
.formcontainer form {
|
|
88
|
+
grid-template-columns: 1fr 1fr;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.halfBuilderItem {
|
|
92
|
+
grid-column: 1 / -1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* One track. Below this a two-up row is two ~150px fields, which is where a
|
|
97
|
+
date input starts clipping its own year. */
|
|
98
|
+
@container (max-width: 640px) {
|
|
99
|
+
.formcontainer form {
|
|
100
|
+
grid-template-columns: 1fr;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.qtrBuilderItem,
|
|
104
|
+
.halfBuilderItem {
|
|
105
|
+
grid-column: 1 / -1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
27
109
|
.formItem {
|
|
28
110
|
width: 100%;
|
|
29
111
|
position: relative;
|
|
@@ -25,71 +25,123 @@
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Header chrome — tokenised, NOT restyled.
|
|
30
|
+
// ----------------------------------------------------------------------------
|
|
31
|
+
// Every `--client-portal-*` property below falls back to the value this file
|
|
32
|
+
// carried before, so a host that defines none of them renders byte-identically
|
|
33
|
+
// to the version that shipped. They exist because the header is the one piece
|
|
34
|
+
// of a client portal that has to look like the company whose portal it is, and
|
|
35
|
+
// the markup for it lives in this library rather than in the host app — so
|
|
36
|
+
// without a hook the only ways to brand it are forking the component or an
|
|
37
|
+
// attribute selector against a generated class name, and both of those break
|
|
38
|
+
// the next time this file is touched.
|
|
39
|
+
//
|
|
40
|
+
// Nothing here reads a host token directly. A host brands the header by
|
|
41
|
+
// defining these properties (typically on `:root`), and can undo it by not.
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
28
44
|
.portalHeader {
|
|
29
45
|
display: flex;
|
|
30
46
|
justify-content: space-between;
|
|
31
47
|
align-items: center;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
gap: var(--client-portal-header-gap, 1rem);
|
|
49
|
+
padding: var(--client-portal-header-padding, 0.5rem 1.25rem);
|
|
50
|
+
background: var(--client-portal-header-bg, var(--tertiary-color, white));
|
|
51
|
+
color: var(--client-portal-header-ink, inherit);
|
|
52
|
+
border-bottom: var(--client-portal-header-border, 0 solid transparent);
|
|
53
|
+
box-shadow: var(--client-portal-header-shadow, 0 1px 3px rgba(0, 0, 0, 0.05));
|
|
35
54
|
z-index: 10;
|
|
36
|
-
|
|
55
|
+
// Increased height to accommodate larger logo
|
|
56
|
+
height: var(--client-portal-header-height, 64px);
|
|
57
|
+
flex: none;
|
|
37
58
|
}
|
|
38
59
|
|
|
39
60
|
.logoContainer {
|
|
40
61
|
display: flex;
|
|
41
62
|
align-items: center;
|
|
42
|
-
margin-left: -1.25rem;
|
|
63
|
+
margin-left: var(--client-portal-logo-offset, -1.25rem);
|
|
43
64
|
padding-left: 0.5rem;
|
|
65
|
+
min-width: 0;
|
|
44
66
|
}
|
|
45
67
|
|
|
46
68
|
.logo {
|
|
47
|
-
height: 52px;
|
|
48
|
-
max-width: 250px;
|
|
69
|
+
height: var(--client-portal-logo-height, 52px);
|
|
70
|
+
max-width: var(--client-portal-logo-max-width, 250px);
|
|
49
71
|
object-fit: contain;
|
|
72
|
+
// `none` unless the host asks otherwise. A dark header needs the reverse of
|
|
73
|
+
// a logo drawn for a white one, and the host cannot swap the file from
|
|
74
|
+
// here — `brightness(0) invert(1)` is the usual answer.
|
|
75
|
+
filter: var(--client-portal-logo-filter, none);
|
|
50
76
|
}
|
|
51
77
|
|
|
52
78
|
.userInfo {
|
|
53
79
|
display: flex;
|
|
54
80
|
align-items: center;
|
|
55
|
-
gap: 0.75rem;
|
|
81
|
+
gap: var(--client-portal-user-gap, 0.75rem);
|
|
82
|
+
min-width: 0;
|
|
56
83
|
}
|
|
57
84
|
|
|
58
85
|
.welcomeText {
|
|
59
|
-
font-size: 0.8rem;
|
|
60
|
-
color: var(--paragraph-color, #555);
|
|
86
|
+
font-size: var(--client-portal-welcome-size, 0.8rem);
|
|
87
|
+
color: var(--client-portal-header-ink, var(--paragraph-color, #555));
|
|
88
|
+
white-space: nowrap;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
text-overflow: ellipsis;
|
|
61
91
|
}
|
|
62
92
|
|
|
63
93
|
.logoutButton {
|
|
64
94
|
display: flex;
|
|
65
95
|
align-items: center;
|
|
66
96
|
justify-content: center;
|
|
67
|
-
|
|
68
|
-
|
|
97
|
+
flex: none;
|
|
98
|
+
width: var(--client-portal-logout-size, 28px);
|
|
99
|
+
height: var(--client-portal-logout-size, 28px);
|
|
69
100
|
padding: 0;
|
|
70
101
|
background-color: transparent;
|
|
71
|
-
border: 1px solid
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
border: 1px solid
|
|
103
|
+
var(
|
|
104
|
+
--client-portal-header-line,
|
|
105
|
+
rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.15)
|
|
106
|
+
);
|
|
107
|
+
border-radius: var(--client-portal-logout-radius, var(--radius, 4px));
|
|
108
|
+
color: var(--client-portal-header-ink, var(--paragraph-color, #555));
|
|
74
109
|
cursor: pointer;
|
|
75
110
|
transition: all 0.2s ease;
|
|
76
111
|
|
|
77
112
|
&:hover {
|
|
78
|
-
background-color:
|
|
79
|
-
|
|
80
|
-
|
|
113
|
+
background-color: var(
|
|
114
|
+
--client-portal-logout-hover-bg,
|
|
115
|
+
rgba(var(--primary-rgb, 0, 123, 255), 0.05)
|
|
116
|
+
);
|
|
117
|
+
border-color: var(
|
|
118
|
+
--client-portal-logout-hover-line,
|
|
119
|
+
var(--primary-color, #0f4229)
|
|
120
|
+
);
|
|
121
|
+
color: var(
|
|
122
|
+
--client-portal-logout-hover-ink,
|
|
123
|
+
var(--primary-color, #0f4229)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
&:focus-visible {
|
|
128
|
+
outline: 2px solid
|
|
129
|
+
var(--client-portal-focus-ring, var(--primary-color, #0f4229));
|
|
130
|
+
outline-offset: 2px;
|
|
81
131
|
}
|
|
82
132
|
}
|
|
83
133
|
|
|
84
134
|
.portalContent {
|
|
85
135
|
display: flex;
|
|
86
136
|
flex: 1;
|
|
87
|
-
height
|
|
137
|
+
// Subtract header height — the same property the header is sized from, so
|
|
138
|
+
// a host that changes one cannot leave the two disagreeing.
|
|
139
|
+
height: calc(100vh - var(--client-portal-header-height, 64px));
|
|
88
140
|
}
|
|
89
141
|
|
|
90
142
|
.mainContent {
|
|
91
143
|
flex: 1;
|
|
92
|
-
padding: 1.25rem;
|
|
144
|
+
padding: var(--client-portal-content-padding, 1.25rem);
|
|
93
145
|
overflow-y: auto;
|
|
94
146
|
color: var(--paragraph-color, #333);
|
|
95
147
|
}
|
|
@@ -756,3 +756,53 @@
|
|
|
756
756
|
}
|
|
757
757
|
|
|
758
758
|
.modal__content { padding: 1.25rem; }
|
|
759
|
+
|
|
760
|
+
// ============================================================================
|
|
761
|
+
// The card, on a phone
|
|
762
|
+
// ----------------------------------------------------------------------------
|
|
763
|
+
// LAST IN THE FILE ON PURPOSE. Every declaration below overrides one written
|
|
764
|
+
// earlier at the same specificity — `.widgetNo` is a single class here and a
|
|
765
|
+
// single class there — so the same block sitting up beside the `grid__*` span
|
|
766
|
+
// collapse it belongs with would be beaten by the rules it exists to override
|
|
767
|
+
// and do nothing at all. Media queries carry no weight of their own.
|
|
768
|
+
//
|
|
769
|
+
// What changes and why: below 700px every card is one column wide, and the
|
|
770
|
+
// reserved heights in this sheet exist so that cards SIDE BY SIDE line their
|
|
771
|
+
// labels, values and buttons up with one another. Nothing sits beside a card
|
|
772
|
+
// at this width. A stat tile is a label, a number and a button — three lines
|
|
773
|
+
// — and it was reserving 180px for them: 40 of padding, 24 of gaps, 24 held
|
|
774
|
+
// open under a 17px label and 48 held open under a 32px number. That is one
|
|
775
|
+
// and a half cards to a 402px screen, on the surface whose whole job is to
|
|
776
|
+
// answer three questions at a glance.
|
|
777
|
+
//
|
|
778
|
+
// Nothing here shrinks the number itself. The value is the content; the empty
|
|
779
|
+
// space around it was the problem.
|
|
780
|
+
// ============================================================================
|
|
781
|
+
|
|
782
|
+
@media (max-width: 700px) {
|
|
783
|
+
// The token's own default rather than a flat value, so a project that has
|
|
784
|
+
// set --dash-card-padding keeps its figure at every width; only the
|
|
785
|
+
// shipped default tightens.
|
|
786
|
+
.grid {
|
|
787
|
+
--dash-pad: var(--dash-card-padding, 0.9rem);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.widgetItem {
|
|
791
|
+
gap: 0.5rem;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Both floors go: each element is still exactly as tall as its content,
|
|
795
|
+
// and there is no neighbouring card to hold a line for.
|
|
796
|
+
.widgetTitle {
|
|
797
|
+
min-height: 0;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.widgetNo,
|
|
801
|
+
.widgetNo-alt {
|
|
802
|
+
min-height: 0;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.widgetLink {
|
|
806
|
+
padding-top: 0.25rem;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
@@ -1207,10 +1207,15 @@
|
|
|
1207
1207
|
z-index: 1000;
|
|
1208
1208
|
}
|
|
1209
1209
|
|
|
1210
|
+
/* See DataGrid.module.scss's `.modalWide` for the whole note. Short version:
|
|
1211
|
+
`min-width` outranks both `width` and `max-width`, so a flat 600px floor put
|
|
1212
|
+
210px of the dialog — its right edge and its close button — off the side of
|
|
1213
|
+
a 390px phone. 90vw is the width this rule already asks for, so the floor
|
|
1214
|
+
can never outrank the line above it. */
|
|
1210
1215
|
.modalWide {
|
|
1211
1216
|
width: 90vw;
|
|
1212
1217
|
max-width: var(--detail-max-width, 1400px);
|
|
1213
|
-
min-width: 600px;
|
|
1218
|
+
min-width: min(600px, 90vw);
|
|
1214
1219
|
}
|
|
1215
1220
|
|
|
1216
1221
|
.modal {
|
|
@@ -1384,6 +1389,63 @@
|
|
|
1384
1389
|
0 12px 24px -12px color-mix(in srgb, var(--primary-color, #1b3933) 35%, transparent);
|
|
1385
1390
|
}
|
|
1386
1391
|
|
|
1392
|
+
/* -- the same cluster, in the title row ----------------------------------- */
|
|
1393
|
+
|
|
1394
|
+
/* `editPlacement: "header"` (see utils/editPlacement.js) hangs the SAME
|
|
1395
|
+
cluster off the breadcrumb row instead of pinning it to the viewport. It is
|
|
1396
|
+
a modifier rather than an edit to `.polActions` on purpose: the floating bar
|
|
1397
|
+
is still the default on every other detail page in every other application,
|
|
1398
|
+
and the two `:has(> :nth-child(2))` rules above are the whole of its
|
|
1399
|
+
treatment.
|
|
1400
|
+
|
|
1401
|
+
Which is exactly what has to come off here. A pill with a blurred backdrop
|
|
1402
|
+
is how a control says "I am floating over the page"; sitting in a header bar
|
|
1403
|
+
it reads as a second bar drawn inside the first, and the elevation a lone
|
|
1404
|
+
button carries reads as a button that has come loose. In the title row the
|
|
1405
|
+
ground is the header, so the cluster is nothing but its buttons — `.btn`
|
|
1406
|
+
itself is untouched, so they are the same buttons as everywhere else.
|
|
1407
|
+
|
|
1408
|
+
Both selectors are doubled (`.polActions.polActionsHeader`) to out-specify
|
|
1409
|
+
the two rules above, which `:has()` lifts to two classes' worth. Source
|
|
1410
|
+
order after them is not enough on its own. */
|
|
1411
|
+
.polActions.polActionsHeader {
|
|
1412
|
+
position: static;
|
|
1413
|
+
inset: auto;
|
|
1414
|
+
z-index: auto;
|
|
1415
|
+
padding: 0;
|
|
1416
|
+
border: 0;
|
|
1417
|
+
border-radius: 0;
|
|
1418
|
+
background: none;
|
|
1419
|
+
backdrop-filter: none;
|
|
1420
|
+
box-shadow: none;
|
|
1421
|
+
flex-wrap: wrap;
|
|
1422
|
+
justify-content: flex-end;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
.polActions.polActionsHeader > * {
|
|
1426
|
+
box-shadow: none;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/* The right-hand end of the title row: the total, a host's `actions`, and —
|
|
1430
|
+
under this placement — the cluster. `margin-left: auto` does nothing while
|
|
1431
|
+
the row fits (the bar is already `justify-content: space-between`); it is
|
|
1432
|
+
what keeps the group on the right once it has WRAPPED onto its own line. */
|
|
1433
|
+
.titleActions {
|
|
1434
|
+
margin-left: auto;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/* Wrapping is opt-in, and only for a header that actually received a cluster.
|
|
1438
|
+
A record title is long ("Verkada Camera & Access Control" under a client
|
|
1439
|
+
name) and a row of buttons is not compressible, so without this the title
|
|
1440
|
+
is squeezed to a word per line to hold a shape that stopped fitting. The
|
|
1441
|
+
buttons take their own line instead. Left alone for every page that has not
|
|
1442
|
+
opted in, so no existing header can start wrapping. */
|
|
1443
|
+
.crmtitle:has(.polActionsHeader),
|
|
1444
|
+
.crmtitle--alternate:has(.polActionsHeader) {
|
|
1445
|
+
flex-wrap: wrap;
|
|
1446
|
+
row-gap: var(--spacing-sm, 0.5rem);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1387
1449
|
/* -- states --------------------------------------------------------------- */
|
|
1388
1450
|
|
|
1389
1451
|
.noDataMessage {
|
|
@@ -176,8 +176,17 @@
|
|
|
176
176
|
font-size: 1rem;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
/* A third copy of the four-track form grid — this one wraps Field.jsx directly
|
|
180
|
+
(see GenericDynamic.jsx), so it has to declare the container too, or the
|
|
181
|
+
collapse written in Field.module.scss finds nothing to measure and the
|
|
182
|
+
fields stay four-up on a phone. `container-type` is the whole of what is
|
|
183
|
+
added here; the reasoning is over Form.module.scss's `.formcontainer`.
|
|
184
|
+
`.polActions` in this module is `position: fixed`, but it is a SIBLING of
|
|
185
|
+
this element rather than a descendant, so the layout containment that comes
|
|
186
|
+
with `container-type` does not move it. */
|
|
179
187
|
.formcontainer {
|
|
180
188
|
width: 100%;
|
|
189
|
+
container-type: inline-size;
|
|
181
190
|
|
|
182
191
|
form {
|
|
183
192
|
width: 100%;
|
|
@@ -188,6 +197,20 @@
|
|
|
188
197
|
}
|
|
189
198
|
}
|
|
190
199
|
|
|
200
|
+
/* The track list, narrowed. What a "half" and a "quarter" mean against the
|
|
201
|
+
narrower list is re-stated in Field.module.scss, which owns those classes. */
|
|
202
|
+
@container (max-width: 768px) {
|
|
203
|
+
.formcontainer form {
|
|
204
|
+
grid-template-columns: 1fr 1fr;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@container (max-width: 640px) {
|
|
209
|
+
.formcontainer form {
|
|
210
|
+
grid-template-columns: 1fr;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
191
214
|
.formItem {
|
|
192
215
|
width: 100%;
|
|
193
216
|
position: relative;
|