@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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StackedRow — one grid row, stacked, for a screen too narrow to lay columns
|
|
3
|
+
* out side by side.
|
|
4
|
+
*
|
|
5
|
+
* ROWS, NOT CARDS. The vocabulary the mapper uses is a card's (title, subtitle,
|
|
6
|
+
* meta, badge) but nothing here is a Material card: no elevation, no inner
|
|
7
|
+
* border, no rounded panel, no margin between rows. The row keeps the grid's
|
|
8
|
+
* own zebra striping and its own divider, because a card per record is four
|
|
9
|
+
* times the height for the same four facts and a phone has the least height of
|
|
10
|
+
* anything we ship to.
|
|
11
|
+
*
|
|
12
|
+
* EVERY SIZE IS rem, AND THAT IS LOAD-BEARING. `styles/density.css` takes the
|
|
13
|
+
* root font-size to 112.5% under `data-density="large"`, so every line, gap and
|
|
14
|
+
* pad below grows by 1.125 with no rule in this file mentioning density. The px
|
|
15
|
+
* table in `utils/cardLayout.js` that fixes the ROW's height is deliberately a
|
|
16
|
+
* shade larger than 1.125× at every entry, so the computed height is always at
|
|
17
|
+
* or above what these rules need — over-allocating leaves a hairline of slack,
|
|
18
|
+
* under-allocating clips the last line, and only one of those is noticed.
|
|
19
|
+
*
|
|
20
|
+
* NO HOVER RULES ANYWHERE IN THIS FILE. It renders on a coarse pointer, where
|
|
21
|
+
* hover is either absent or sticky, and an action that appears on hover is an
|
|
22
|
+
* action a finger cannot find.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
.card {
|
|
26
|
+
display: flex;
|
|
27
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
28
|
+
align-items: center;
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
|
|
31
|
+
/* The cell is sized by the grid's fixed rowHeight; the row fills it and
|
|
32
|
+
clips rather than pushing it. Nothing inside may change the height —
|
|
33
|
+
a blank field collapses its line's CONTENT and leaves the row exactly
|
|
34
|
+
as tall as its neighbours. */
|
|
35
|
+
width: 100%;
|
|
36
|
+
height: 100%;
|
|
37
|
+
padding: 0.625rem 0;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* `min-width: 0` is the whole reason the ellipses work: a flex child's default
|
|
42
|
+
`min-width: auto` is its content's intrinsic width, so a long client name
|
|
43
|
+
would push the rail off the screen instead of being clipped. */
|
|
44
|
+
.main {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex: 1 1 auto;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
gap: 0.125rem;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.titleRow {
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 0.375rem;
|
|
55
|
+
align-items: baseline;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* The record's own number, where it has one: a quote number, a ticket number.
|
|
60
|
+
Mono because it is read digit by digit and compared, not scanned. */
|
|
61
|
+
.reference {
|
|
62
|
+
flex: 0 0 auto;
|
|
63
|
+
padding: 0 0.25rem;
|
|
64
|
+
color: var(--muted-color, #6b7280);
|
|
65
|
+
font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
|
66
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
67
|
+
line-height: 1.375rem;
|
|
68
|
+
background: var(--border-color, #e1e1e1);
|
|
69
|
+
border-radius: var(--radius-sm, 4px);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.title,
|
|
73
|
+
.subtitle,
|
|
74
|
+
.line {
|
|
75
|
+
/* Fixed height, so nothing may take a second line. Repeated on each rather
|
|
76
|
+
than shared through a parent because the renderers put their own
|
|
77
|
+
elements inside these and a rule on the parent does not reach them. */
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
min-width: 0;
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
text-overflow: ellipsis;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.title {
|
|
85
|
+
color: var(--heading-color, #1b3933);
|
|
86
|
+
font-size: 0.9375rem;
|
|
87
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
88
|
+
line-height: 1.375rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.subtitle {
|
|
92
|
+
color: var(--muted-color, #6b7280);
|
|
93
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
94
|
+
line-height: 1.125rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.line {
|
|
98
|
+
display: flex;
|
|
99
|
+
gap: 0.375rem;
|
|
100
|
+
align-items: baseline;
|
|
101
|
+
color: var(--paragraph-color, #443c2b);
|
|
102
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
103
|
+
line-height: 1.125rem;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.lineLabel {
|
|
107
|
+
flex: 0 0 auto;
|
|
108
|
+
color: var(--muted-color, #6b7280);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.lineValue {
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
min-width: 0;
|
|
114
|
+
white-space: nowrap;
|
|
115
|
+
text-overflow: ellipsis;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* The click-to-call and click-to-mail anchors — the reason this treatment
|
|
119
|
+
exists. Accent-coloured because they are the only thing on the row that is
|
|
120
|
+
not the row's own link, and underlined so that reads as a decision rather
|
|
121
|
+
than as a colour scheme. */
|
|
122
|
+
.channel {
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
color: var(--primary-color, #1b3933);
|
|
125
|
+
text-decoration: underline;
|
|
126
|
+
text-underline-offset: 0.125rem;
|
|
127
|
+
white-space: nowrap;
|
|
128
|
+
text-overflow: ellipsis;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Right-hand rail: the state chip over the row's action icons.
|
|
132
|
+
`auto` width, never flexed — the main column is what gives way. */
|
|
133
|
+
.rail {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex: 0 0 auto;
|
|
136
|
+
flex-direction: column;
|
|
137
|
+
gap: 0.25rem;
|
|
138
|
+
align-items: flex-end;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
min-height: 2.75rem;
|
|
141
|
+
max-width: 45%;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.badge {
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
max-width: 100%;
|
|
147
|
+
color: var(--paragraph-color, #443c2b);
|
|
148
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
149
|
+
white-space: nowrap;
|
|
150
|
+
text-overflow: ellipsis;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Always drawn, never on hover. The icons inside are the grid's own
|
|
154
|
+
`renderRowAction` / `renderSetting` output and already stopPropagation, so
|
|
155
|
+
the row's link and the rail's controls cannot both fire. */
|
|
156
|
+
.actions {
|
|
157
|
+
display: flex;
|
|
158
|
+
gap: 0.125rem;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: flex-end;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* THE HIT AREA, not the glyph.
|
|
164
|
+
*
|
|
165
|
+
* `.tdaction` in DataGrid.module.scss is a 28px square, and in the table that
|
|
166
|
+
* is the whole target — the action COLUMN reserves 44px per icon of width, but
|
|
167
|
+
* nothing widens the icon itself. 28px is under every touch guideline there
|
|
168
|
+
* is, and on a card the icons sit at the end of a row whose whole body is a
|
|
169
|
+
* link, so a near miss does not do nothing: it opens the record.
|
|
170
|
+
*
|
|
171
|
+
* So the WRAPPER is grown instead of the icon. Both of the grid's action
|
|
172
|
+
* entry points put their `onClick` on the wrapper — `.rowActionTrigger` for an
|
|
173
|
+
* inline row action, the `setting-*` span for a standard one — so the target
|
|
174
|
+
* grows and nothing about the drawn icon changes. Scoped to this module, so
|
|
175
|
+
* the table's own action column is untouched. */
|
|
176
|
+
.actions > * {
|
|
177
|
+
display: inline-flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
justify-content: center;
|
|
180
|
+
min-width: 2.75rem;
|
|
181
|
+
min-height: 2.75rem;
|
|
182
|
+
}
|
|
@@ -407,6 +407,82 @@
|
|
|
407
407
|
.when { margin-left: 0; width: 100%; }
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
/* -- the phone ------------------------------------------------------------- */
|
|
411
|
+
|
|
412
|
+
/*
|
|
413
|
+
* At 390px the 759 block above is doing the right thing and not enough of it.
|
|
414
|
+
* It fixes the header — one column, the timestamp on its own line — and leaves
|
|
415
|
+
* the two boxes the page is actually FOR at their desktop insets.
|
|
416
|
+
*
|
|
417
|
+
* Count the gutters an entry's text sits behind on a 390px screen: the CRM's
|
|
418
|
+
* page padding, then TicketWorkspace's `.main` card at 1rem a side, then this
|
|
419
|
+
* module's `.entry` at another 1rem. That is roughly 96px of chrome, a quarter
|
|
420
|
+
* of the glass, spent on nested padding around a line of email — and the
|
|
421
|
+
* composer pays it twice over, because its textarea then has its own 0.7rem
|
|
422
|
+
* inside that. Nothing overflows; it is simply the wrong split between frame
|
|
423
|
+
* and content at a width where content is scarce.
|
|
424
|
+
*
|
|
425
|
+
* So the two inner boxes give up half their side padding. The card edges still
|
|
426
|
+
* read as edges — they are drawn with a border, not with space — and the text
|
|
427
|
+
* gets about 28px of its width back per side.
|
|
428
|
+
*
|
|
429
|
+
* READ-AND-TRIAGE IS THE BAR for this pass: the thread has to be readable and
|
|
430
|
+
* the composer has to be usable. There is deliberately NO pinned action bar —
|
|
431
|
+
* a sticky Send over a thread this narrow costs a line of the conversation
|
|
432
|
+
* permanently, and the composer is already at the end of the page where the
|
|
433
|
+
* thumb is.
|
|
434
|
+
*/
|
|
435
|
+
@media (max-width: 480px) {
|
|
436
|
+
.entry,
|
|
437
|
+
.composer {
|
|
438
|
+
padding: 0.7rem;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* The message's own scroll cap. 24rem is 384px — on an 844px-tall phone,
|
|
442
|
+
most of what can be seen at once, so a long forwarded chain fills the
|
|
443
|
+
screen and the reply under it is a scroll away with no sign it is there.
|
|
444
|
+
16rem still shows a real message and keeps the next entry in view. */
|
|
445
|
+
.body,
|
|
446
|
+
.bodyHtml {
|
|
447
|
+
max-height: 16rem;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* Full-bleed Send, which is the phone convention for the one action a
|
|
451
|
+
screen exists to perform — and the only place in this library where a
|
|
452
|
+
full-width primary is right rather than a banner (see the note over
|
|
453
|
+
Form.module.scss's `.buttonContainer` for the desktop argument against
|
|
454
|
+
it). Nothing shares the row with it. */
|
|
455
|
+
.composerActions {
|
|
456
|
+
justify-content: stretch;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.send {
|
|
460
|
+
width: 100%;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* 34px is a mouse target. Under a finger the Send button sits directly under a
|
|
465
|
+
textarea the reader has just been typing in, which is the worst place on the
|
|
466
|
+
page to miss — the miss lands in the textarea and looks like nothing
|
|
467
|
+
happened. 44px is the floor. Applied on the pointer rather than on a width,
|
|
468
|
+
because a tablet with a keyboard case is 1024px wide and still driven by a
|
|
469
|
+
thumb. */
|
|
470
|
+
@media (pointer: coarse) {
|
|
471
|
+
.send {
|
|
472
|
+
min-height: 44px;
|
|
473
|
+
height: auto;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* The image-unblock and quote-collapse chips in `.bodyBar` are 0.68rem
|
|
477
|
+
text in a pill; give them a real box rather than a line of type to aim
|
|
478
|
+
at. They stay small — they are qualifiers on a message, not actions on
|
|
479
|
+
the ticket — but they stop being a 16px-tall target. */
|
|
480
|
+
.bodyAction {
|
|
481
|
+
min-height: 32px;
|
|
482
|
+
padding: 0.25rem 0.6rem;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
410
486
|
/* -- composer -------------------------------------------------------------- */
|
|
411
487
|
|
|
412
488
|
.composer {
|
|
@@ -1106,6 +1106,66 @@ input.checkControl {
|
|
|
1106
1106
|
font-size: 0.75rem; // 12px at a 16px root
|
|
1107
1107
|
}
|
|
1108
1108
|
|
|
1109
|
+
// ---------------------------------------------------------------------------
|
|
1110
|
+
// Mounted in the sidebar rail, and in the mobile drawer
|
|
1111
|
+
// ---------------------------------------------------------------------------
|
|
1112
|
+
// `actionsPlacement: 'sidebar'` — and, below 1024px, the header layout's
|
|
1113
|
+
// drawer — draws the account actions as full-width rows instead of chips. The
|
|
1114
|
+
// block that holds them advertises itself with `data-nav-actions="sidebar"`
|
|
1115
|
+
// (Navigation.jsx / Navigation.module.scss `.rail-actions`), which is all this
|
|
1116
|
+
// file needs to know. Notification.module.scss and Sms.module.scss carry the
|
|
1117
|
+
// same block; a chip and a row are two different things in three places.
|
|
1118
|
+
//
|
|
1119
|
+
// The row's box — full width, the rail's padding, left-aligned, and the LABEL
|
|
1120
|
+
// beside the icon — all come from `.rail-actions`. The trigger publishes no
|
|
1121
|
+
// label of its own, so the row publishes one as `--nav-action-label` and the
|
|
1122
|
+
// rail hangs it off the trigger's `::after`. Only what this file would
|
|
1123
|
+
// otherwise force back is restated here.
|
|
1124
|
+
[data-nav-actions='sidebar'] {
|
|
1125
|
+
// The chip's lift and scale read as a stray button in a list of rows.
|
|
1126
|
+
// The focus ring is left alone — it is the only keyboard affordance the
|
|
1127
|
+
// row has.
|
|
1128
|
+
.trigger {
|
|
1129
|
+
&:hover,
|
|
1130
|
+
&:focus,
|
|
1131
|
+
&:active {
|
|
1132
|
+
transform: none;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
&:hover {
|
|
1136
|
+
background-color: rgba(var(--tertiary-color-rgb, 255, 255, 255), 0.05);
|
|
1137
|
+
color: var(--secondary-color);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// `top: 100%` drops the panel below the row. These rows are pinned to the
|
|
1142
|
+
// FOOT of the rail, so that is below the foot of the viewport. Beside the
|
|
1143
|
+
// rail, bottom-aligned with the row that opened it.
|
|
1144
|
+
.popoverWrapper {
|
|
1145
|
+
top: auto;
|
|
1146
|
+
right: auto;
|
|
1147
|
+
bottom: 0;
|
|
1148
|
+
left: calc(100% + 10px);
|
|
1149
|
+
margin-top: 0;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
@media (max-width: 1024px) {
|
|
1153
|
+
// Inside the drawer there is nothing to the right, so the panel opens
|
|
1154
|
+
// upward from the row.
|
|
1155
|
+
.popoverWrapper {
|
|
1156
|
+
left: 0;
|
|
1157
|
+
bottom: calc(100% + 8px);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Keeps the panel inside a phone's viewport: 348px of rows plus a
|
|
1161
|
+
// head, a search bar and a footer is taller than the room above the
|
|
1162
|
+
// drawer's footer.
|
|
1163
|
+
.rows {
|
|
1164
|
+
max-height: 40vh;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1109
1169
|
// ---------------------------------------------------------------------------
|
|
1110
1170
|
// Generator
|
|
1111
1171
|
// ---------------------------------------------------------------------------
|
|
@@ -2006,6 +2066,138 @@ input.checkControl {
|
|
|
2006
2066
|
}
|
|
2007
2067
|
}
|
|
2008
2068
|
|
|
2069
|
+
// ---------------------------------------------------------------------------
|
|
2070
|
+
// The phone: six columns become two lines
|
|
2071
|
+
// ---------------------------------------------------------------------------
|
|
2072
|
+
//
|
|
2073
|
+
// The table sheds a column at a time down to 900px, which is the right answer
|
|
2074
|
+
// for a tablet and runs out at a phone: what is left below 640 is Title,
|
|
2075
|
+
// Username, Visibility, 2FA and five action buttons across roughly 350px of
|
|
2076
|
+
// glass. Every one of those tracks is then narrower than the word in its own
|
|
2077
|
+
// header, so the table stops being a table — it is six columns of hyphenated
|
|
2078
|
+
// fragments over a horizontal scrollbar.
|
|
2079
|
+
//
|
|
2080
|
+
// So each row becomes a small card of two lines: the title (with the host and
|
|
2081
|
+
// the client already sitting under it from the 1200px step) on the first, and
|
|
2082
|
+
// everything that qualifies it — username, visibility, the live 2FA code — on
|
|
2083
|
+
// the second, followed by the actions. This is SharePointGrid's `620` idea
|
|
2084
|
+
// carried one step past where columns can go: that grid drops a column at each
|
|
2085
|
+
// width until only the name and the date are left, and this is what happens
|
|
2086
|
+
// when even two tracks will not fit.
|
|
2087
|
+
//
|
|
2088
|
+
// THE SORT CONTROLS SURVIVE, which is the part that is easy to get wrong. The
|
|
2089
|
+
// obvious `thead { display: none }` takes the only sort control on the screen
|
|
2090
|
+
// with it — there is no sort menu in the toolbar to fall back on. Instead the
|
|
2091
|
+
// header row collapses to a wrapped strip of just the two sortable headings
|
|
2092
|
+
// (`SORTS` in VaultManager.jsx is Title and Username, and they are the first
|
|
2093
|
+
// two `th`), which reads as the row of chips it now looks like. The four
|
|
2094
|
+
// non-sortable headings are labels for cells that no longer sit under them, so
|
|
2095
|
+
// those do go.
|
|
2096
|
+
@media (max-width: 640px) {
|
|
2097
|
+
// The header strip. `display: block` on the `tr` and `inline-block` on the
|
|
2098
|
+
// `th` rather than flex, because a table row is not a flex container in
|
|
2099
|
+
// every engine without also restating `display` on the `table` and the
|
|
2100
|
+
// `tbody`, and doing that throws away the browser's row model for nothing.
|
|
2101
|
+
.table thead tr {
|
|
2102
|
+
display: block;
|
|
2103
|
+
padding: 0.35rem 0.7rem;
|
|
2104
|
+
// The rule under the header moves from the cells to the row, because
|
|
2105
|
+
// the cells are now a wrapped strip and a hairline under each of them
|
|
2106
|
+
// draws two short underlines instead of one line.
|
|
2107
|
+
border-bottom: 1px solid var(--v-line);
|
|
2108
|
+
background: var(--v-surface);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
.table thead th {
|
|
2112
|
+
display: inline-block;
|
|
2113
|
+
padding: 0.25rem 0.5rem 0.25rem 0;
|
|
2114
|
+
border-bottom: 0;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// Everything after the two sortable columns. `nth-child` and not a class,
|
|
2118
|
+
// because these are the toolkit-free `<th>`s the component writes inline
|
|
2119
|
+
// and giving four of them a class each to hide them all is noise.
|
|
2120
|
+
.table thead th:nth-child(n + 3) {
|
|
2121
|
+
display: none;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// A row is a card. The border that separated rows becomes the card's own
|
|
2125
|
+
// bottom edge, which is the same hairline in the same place.
|
|
2126
|
+
.table tbody tr {
|
|
2127
|
+
display: block;
|
|
2128
|
+
padding: 0.6rem 0.7rem;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
.table tbody td {
|
|
2132
|
+
display: inline;
|
|
2133
|
+
padding: 0;
|
|
2134
|
+
vertical-align: baseline;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// Line one: the title block, which already carries the host and the client
|
|
2138
|
+
// underneath it from the 1200px step.
|
|
2139
|
+
.table tbody td:first-child {
|
|
2140
|
+
display: block;
|
|
2141
|
+
margin-bottom: 0.35rem;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// Line two: username, visibility and the 2FA chip, spaced by a real gap
|
|
2145
|
+
// rather than by the padding a table cell would have had. Wrapped in a
|
|
2146
|
+
// flow of inline boxes so an em dash placeholder costs nothing.
|
|
2147
|
+
.table tbody td:nth-child(2),
|
|
2148
|
+
.table tbody td:nth-child(4),
|
|
2149
|
+
.table tbody td:nth-child(5) {
|
|
2150
|
+
margin-right: 0.6rem;
|
|
2151
|
+
color: var(--v-muted);
|
|
2152
|
+
font-size: 0.8rem;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// The actions get a line of their own: five 29px buttons plus their gaps is
|
|
2156
|
+
// 165px, which is half the width of the glass and would push the facts
|
|
2157
|
+
// beside them down to nothing.
|
|
2158
|
+
.table tbody td.actionsCell {
|
|
2159
|
+
display: block;
|
|
2160
|
+
margin-top: 0.5rem;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// Fitted mode pins every row to a single 58px band so the page size is
|
|
2164
|
+
// arithmetic (see `.tableWrapFitted` above). A two-line card does not fit
|
|
2165
|
+
// in 58px and would be clipped mid-row, so the pin comes off here and the
|
|
2166
|
+
// list scrolls instead. The pager still works; it just no longer gets a
|
|
2167
|
+
// whole number of rows, which is a trade a phone was never going to win.
|
|
2168
|
+
.tableWrapFitted {
|
|
2169
|
+
overflow-y: auto;
|
|
2170
|
+
|
|
2171
|
+
tbody tr,
|
|
2172
|
+
tbody td {
|
|
2173
|
+
height: auto;
|
|
2174
|
+
max-height: none;
|
|
2175
|
+
white-space: normal;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
// The ⌘K quick-search popover's rows, at the same width and for the same
|
|
2181
|
+
// reason. `.rows > .row` and not `.row`, because the geometry above is stated
|
|
2182
|
+
// at 0,2,0 precisely so a host cannot outrank it — and a 0,1,0 override in
|
|
2183
|
+
// here would not outrank it either.
|
|
2184
|
+
@media (max-width: 640px) {
|
|
2185
|
+
.rows > .row {
|
|
2186
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2187
|
+
gap: 0.4rem;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
// Hard left on the second line rather than flush right against an edge the
|
|
2191
|
+
// title no longer reaches: the cluster now starts under the title it
|
|
2192
|
+
// belongs to, which is what keeps the pairing readable once they are two
|
|
2193
|
+
// lines rather than one.
|
|
2194
|
+
.rowActions {
|
|
2195
|
+
justify-self: start;
|
|
2196
|
+
margin-left: 0;
|
|
2197
|
+
flex-wrap: wrap;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2009
2201
|
// ---------------------------------------------------------------------------
|
|
2010
2202
|
// Access log
|
|
2011
2203
|
// ---------------------------------------------------------------------------
|
|
@@ -83,3 +83,13 @@
|
|
|
83
83
|
:root[data-density='large'] body.tablet-mode {
|
|
84
84
|
--grid-checkbox-col-width: 80px;
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
/* And again for a coarse primary pointer, which switches the grid into the
|
|
88
|
+
same tablet geometry with no view config — see utils/useDensity.js, which
|
|
89
|
+
ORs the two signals for the JS half. Restated rather than shared: this is
|
|
90
|
+
plain CSS, so a class and a media query cannot be one selector. */
|
|
91
|
+
@media (pointer: coarse) {
|
|
92
|
+
:root[data-density='large'] body {
|
|
93
|
+
--grid-checkbox-col-width: 80px;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -103,6 +103,41 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
103
103
|
-webkit-line-clamp: 2 !important;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/* The same three rules again for a COARSE PRIMARY POINTER, which now switches
|
|
107
|
+
the grid into its fixed-row-height geometry on its own — see the long note
|
|
108
|
+
in utils/useDensity.js. A phone or a tablet gets the roomy rows and the 44px
|
|
109
|
+
targets with no view config at all; the `body.tablet-mode` class above still
|
|
110
|
+
works and now means "treat this view as touch even on a mouse".
|
|
111
|
+
*
|
|
112
|
+
* Restated rather than folded into a shared selector list, because this is
|
|
113
|
+
* plain CSS with no mixins and a `:is()` across a class and a media query is
|
|
114
|
+
* not a thing that exists. Keep the two halves in step — they are one rule.
|
|
115
|
+
* `pointer` reports the PRIMARY device, so a touchscreen laptop answers `fine`
|
|
116
|
+
* and never reaches any of this. */
|
|
117
|
+
@media (pointer: coarse) {
|
|
118
|
+
.InovuaReactDataGrid__cell.cell-word-wrap,
|
|
119
|
+
.InovuaReactDataGrid__cell.cell-word-wrap > *,
|
|
120
|
+
.InovuaReactDataGrid__cell.cell-word-wrap div,
|
|
121
|
+
.InovuaReactDataGrid__cell.cell-word-wrap span {
|
|
122
|
+
overflow: hidden !important;
|
|
123
|
+
max-height: 100% !important;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.InovuaReactDataGrid__cell.cell-word-wrap:not(
|
|
127
|
+
.InovuaReactDataGrid__cell--no-padding
|
|
128
|
+
) {
|
|
129
|
+
height: 100% !important;
|
|
130
|
+
align-items: center !important;
|
|
131
|
+
padding: var(--grid-cell-padding, 6px 8px) !important;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
135
|
+
display: -webkit-box !important;
|
|
136
|
+
-webkit-box-orient: vertical !important;
|
|
137
|
+
-webkit-line-clamp: 2 !important;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
106
141
|
/* Default cell behavior - no word wrap */
|
|
107
142
|
.InovuaReactDataGrid__cell:not(.cell-word-wrap) {
|
|
108
143
|
white-space: nowrap !important;
|
|
@@ -853,6 +888,34 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
853
888
|
padding-inline-end: 0 !important;
|
|
854
889
|
}
|
|
855
890
|
|
|
891
|
+
/* The filter row under a finger — as far as it can honestly go, which is not
|
|
892
|
+
44px.
|
|
893
|
+
*
|
|
894
|
+
* The datagrid measures its own header height in JS from the theme, which pins
|
|
895
|
+
* the filter cell to `min-height: 32px`. A 44px control inside that cell is
|
|
896
|
+
* CLIPPED, not accommodated: the grid does not re-measure because a stylesheet
|
|
897
|
+
* grew one of its children. So the touch step here is 32px — the full height
|
|
898
|
+
* of the cell the theme already reserves, up from 28 — which at least makes
|
|
899
|
+
* the whole cell the target instead of a 28px box floating inside it.
|
|
900
|
+
*
|
|
901
|
+
* The real 44px targets on a touch device are the pager (DataGrid.module.scss)
|
|
902
|
+
* and the row action buttons. Filtering a grid on a phone is a typing task
|
|
903
|
+
* with a keyboard already covering half the screen; it is not the thing this
|
|
904
|
+
* pass had to rescue. Raising it properly means changing the grid theme's
|
|
905
|
+
* header height, which moves every desktop grid too. */
|
|
906
|
+
@media (pointer: coarse) {
|
|
907
|
+
.InovuaReactDataGrid__column-header__filter input,
|
|
908
|
+
.InovuaReactDataGrid__column-header__filter select {
|
|
909
|
+
height: 32px !important;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.InovuaReactDataGrid__column-header__filter.inovua-react-toolkit-combo-box {
|
|
913
|
+
height: 32px !important;
|
|
914
|
+
min-height: 32px !important;
|
|
915
|
+
max-height: 32px !important;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
856
919
|
/* The keyboard highlight in a dropdown's option list — declared by the
|
|
857
920
|
vendored theme, then painted with nothing.
|
|
858
921
|
|
|
@@ -1073,3 +1136,103 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
1073
1136
|
.visns-native-date-filter__input {
|
|
1074
1137
|
font-size: 0.75rem !important;
|
|
1075
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
/* ==========================================================================
|
|
1141
|
+
NARROW VIEWPORTS — the pager, which used to be sliced off at the edge
|
|
1142
|
+
--------------------------------------------------------------------------
|
|
1143
|
+
Guarded on `(max-width: 640px)` — the same width `utils/useDensity.js`
|
|
1144
|
+
exports as NARROW_VIEWPORT_QUERY and raises the column floor at. Nothing in
|
|
1145
|
+
this block is reachable above it, so every desktop grid is untouched.
|
|
1146
|
+
|
|
1147
|
+
The pager is not the library's own markup: `pagination` on the datagrid
|
|
1148
|
+
renders the TOOLKIT's toolbar (`.inovua-react-pagination-toolbar`, no grid
|
|
1149
|
+
prefix), and that component measures itself in JS and puts its contents in
|
|
1150
|
+
an internal scroll container with little arrows at each end. On a phone its
|
|
1151
|
+
contents are 714px wide inside a 348px window, so what a reader actually
|
|
1152
|
+
gets is "|< < Page [1] of 236 > >| | R" — the "Results per page" label
|
|
1153
|
+
sheared off mid-word at the screen edge, with the rest behind a scroll
|
|
1154
|
+
affordance nobody recognises as one.
|
|
1155
|
+
|
|
1156
|
+
So the toolbar is made to WRAP instead of scroll. Wrapping keeps every
|
|
1157
|
+
control — the page size combo included — where hiding half of them would
|
|
1158
|
+
have been the easier fix and would have taken the record total off the one
|
|
1159
|
+
screen that cannot show the stat strip and the grid at the same time.
|
|
1160
|
+
|
|
1161
|
+
`!important` throughout because the widths and heights being overridden are
|
|
1162
|
+
INLINE, written by the scroll container's own measuring pass. The arrows go
|
|
1163
|
+
with the scrolling: an overflow affordance for content that no longer
|
|
1164
|
+
overflows is a button that does nothing.
|
|
1165
|
+
========================================================================== */
|
|
1166
|
+
@media (max-width: 640px) {
|
|
1167
|
+
.inovua-react-pagination-toolbar {
|
|
1168
|
+
height: auto !important;
|
|
1169
|
+
min-height: 0 !important;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
.inovua-react-pagination-toolbar
|
|
1173
|
+
.inovua-react-toolkit-toolbar__scroll-container,
|
|
1174
|
+
.inovua-react-pagination-toolbar .inovua-react-scroll-container__wrapper,
|
|
1175
|
+
.inovua-react-pagination-toolbar .inovua-react-scroll-container__scroller,
|
|
1176
|
+
.inovua-react-pagination-toolbar .inovua-react-scroll-container__view {
|
|
1177
|
+
width: 100% !important;
|
|
1178
|
+
max-width: 100% !important;
|
|
1179
|
+
height: auto !important;
|
|
1180
|
+
max-height: none !important;
|
|
1181
|
+
overflow: visible !important;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.inovua-react-pagination-toolbar
|
|
1185
|
+
.inovua-react-toolkit-toolbar__inner-wrapper {
|
|
1186
|
+
flex-wrap: wrap !important;
|
|
1187
|
+
width: 100% !important;
|
|
1188
|
+
row-gap: 0.4rem;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/* The two regions and the spacer between them: the spacer is a flex
|
|
1192
|
+
`1 1 auto` that would otherwise push the record count onto a line of its
|
|
1193
|
+
own with a screen's worth of air in front of it. */
|
|
1194
|
+
.inovua-react-pagination-toolbar__region {
|
|
1195
|
+
flex-wrap: wrap !important;
|
|
1196
|
+
width: auto !important;
|
|
1197
|
+
min-width: 0 !important;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
.inovua-react-pagination-toolbar__spacer {
|
|
1201
|
+
display: none !important;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/* The scroll arrows, now that nothing scrolls. */
|
|
1205
|
+
.inovua-react-pagination-toolbar .inovua-react-toolkit-toolbar__arrow {
|
|
1206
|
+
display: none !important;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/* ----------------------------------------------------------------------
|
|
1210
|
+
CARD MODE — the cell the stacked row is drawn in.
|
|
1211
|
+
|
|
1212
|
+
`datagrid-card-mode` is on the grid and `datagrid-card-cell` is on the
|
|
1213
|
+
one synthetic column; both are set in DataGrid.jsx. The cell class is
|
|
1214
|
+
what does the work, because THE CHECKBOX COLUMN IS A CELL TOO and still
|
|
1215
|
+
wants the grid's own padding and its centred alignment — a rule scoped
|
|
1216
|
+
to the grid alone would strip both and drop the tick against the edge.
|
|
1217
|
+
|
|
1218
|
+
Everything here is `!important` for the same reason the rules it
|
|
1219
|
+
overrides are: those are `!important` themselves, a few hundred lines
|
|
1220
|
+
up, and were written before this block existed.
|
|
1221
|
+
---------------------------------------------------------------------- */
|
|
1222
|
+
.datagrid-card-mode .InovuaReactDataGrid__cell.datagrid-card-cell {
|
|
1223
|
+
/* The card owns its own gutters — StackedRow's vertical padding is
|
|
1224
|
+
half the row-height arithmetic in `utils/cardLayout.js`, and two
|
|
1225
|
+
sets of it is how the last line ends up under the row below. */
|
|
1226
|
+
padding: 0 var(--spacing-sm, 0.5rem) !important;
|
|
1227
|
+
/* So `height: 100%` on the row inside resolves against the cell
|
|
1228
|
+
rather than against its own content. */
|
|
1229
|
+
align-items: stretch !important;
|
|
1230
|
+
/* The nowrap/ellipsis clamp on every non-wrapping cell would apply to
|
|
1231
|
+
the card as a whole; each LINE inside sets its own, which is what
|
|
1232
|
+
has to win. */
|
|
1233
|
+
white-space: normal !important;
|
|
1234
|
+
text-overflow: clip !important;
|
|
1235
|
+
/* A divider down the right of a full-width row is a line to nowhere. */
|
|
1236
|
+
border-right: 0 !important;
|
|
1237
|
+
}
|
|
1238
|
+
}
|