@visns-studio/visns-components 6.6.4 → 6.15.1
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/README.md +1623 -18
- package/package.json +10 -4
- package/src/components/DataGrid.jsx +181 -38
- package/src/components/Fetch.jsx +80 -3
- package/src/components/Form.jsx +9 -0
- package/src/components/Navigation.jsx +109 -50
- package/src/components/Notification.jsx +279 -9
- package/src/components/TableFilter.jsx +14 -1
- package/src/components/auth/AuthBrandPanel.jsx +43 -0
- package/src/components/auth/AuthLoading.jsx +78 -0
- package/src/components/auth/AuthShell.jsx +59 -0
- package/src/components/auth/ClientAuth.jsx +161 -0
- package/src/components/auth/ClientAuthFrame.jsx +59 -0
- package/src/components/auth/ClientLogin.jsx +266 -55
- package/src/components/auth/ClientOTPVerify.jsx +587 -115
- package/src/components/auth/ImpersonateGate.jsx +254 -0
- package/src/components/auth/Login.jsx +134 -41
- package/src/components/auth/LogoutScreen.jsx +112 -0
- package/src/components/auth/Reset.jsx +134 -77
- package/src/components/auth/TwoFactorAuth.jsx +475 -297
- package/src/components/auth/Verify.jsx +237 -126
- package/src/components/auth/authEndpoints.js +105 -0
- package/src/components/auth/authFont.js +23 -0
- package/src/components/auth/authHelpers.js +465 -0
- package/src/components/auth/clientAuthProtocols.js +240 -0
- package/src/components/auth/useOptionalRouter.js +49 -0
- package/src/components/callQueue/CallQueuePop.jsx +1502 -0
- package/src/components/callQueue/CallQueueSettings.jsx +508 -0
- package/src/components/callQueue/callQueueHelpers.js +280 -0
- package/src/components/callQueue/callQueueSettingsHelpers.js +75 -0
- package/src/components/columns/ColumnRenderers.jsx +53 -7
- package/src/components/generic/GenericAuth.jsx +163 -96
- package/src/components/generic/GenericDetail.jsx +215 -90
- package/src/components/generic/GenericIndex.jsx +20 -47
- package/src/components/generic/GenericMain.jsx +5 -0
- package/src/components/generic/GroupedReportRenderer.jsx +1 -5
- package/src/components/generic/StandardModal.jsx +17 -5
- package/src/components/generic/reportSemanticSteps/SemanticEntityStep.jsx +1 -6
- package/src/components/generic/reportSemanticSteps/SemanticFieldsStep.jsx +4 -11
- package/src/components/generic/reportSemanticSteps/SemanticFiltersStep.jsx +9 -27
- package/src/components/generic/reportSemanticSteps/SemanticGroupingStep.jsx +5 -13
- package/src/components/generic/reportSemanticSteps/SemanticParameterPrompt.jsx +5 -13
- package/src/components/generic/reportSemanticSteps/SemanticPreviewStep.jsx +14 -31
- package/src/components/generic/reportSemanticSteps/SemanticRelationsStep.jsx +3 -9
- package/src/components/generic/reportSemanticSteps/SemanticValueInput.jsx +2 -15
- package/src/components/navActive.js +60 -0
- package/src/components/notify/desktopNotifications.js +256 -0
- package/src/components/sketch/SketchField.jsx +12 -2
- package/src/components/sms/SmsComposeModal.jsx +495 -0
- package/src/components/sms/SmsInbox.jsx +596 -0
- package/src/components/sms/SmsInboxBadge.jsx +468 -0
- package/src/components/sms/SmsLineSettings.jsx +854 -0
- package/src/components/sms/SmsThreadPanel.jsx +1184 -0
- package/src/components/sms/smsEndpoints.js +56 -0
- package/src/components/sms/smsHelpers.js +1075 -0
- package/src/components/sms/smsLiveState.js +132 -0
- package/src/components/sms/useSmsLive.js +307 -0
- package/src/components/styles/CallQueuePop.module.scss +646 -0
- package/src/components/styles/CallQueueSettings.module.scss +460 -0
- package/src/components/styles/ClientAuth.module.scss +229 -0
- package/src/components/styles/GenericDetail.module.scss +153 -26
- package/src/components/styles/GenericIndex.module.scss +13 -0
- package/src/components/styles/GenericMain.module.scss +19 -2
- package/src/components/styles/ImpersonateGate.module.scss +85 -0
- package/src/components/styles/Login.module.scss +13 -122
- package/src/components/styles/LogoutScreen.module.scss +89 -0
- package/src/components/styles/Navigation.module.scss +509 -223
- package/src/components/styles/Notification.module.scss +103 -0
- package/src/components/styles/Reset.module.scss +52 -186
- package/src/components/styles/Sms.module.scss +2382 -0
- package/src/components/styles/TableFilter.module.scss +118 -98
- package/src/components/styles/TwoFactorAuth.module.scss +115 -176
- package/src/components/styles/Vault.module.scss +1983 -0
- package/src/components/styles/Verify.module.scss +57 -180
- package/src/components/styles/_authBrandPanel.scss +163 -0
- package/src/components/styles/_authShell.scss +369 -0
- package/src/components/styles/global-datagrid.css +27 -2
- package/src/components/utils/buildEnv.js +67 -0
- package/src/components/utils/displayValue.js +94 -0
- package/src/components/utils/useDensity.js +345 -9
- package/src/components/vault/OtpChip.jsx +208 -0
- package/src/components/vault/PasswordGenerator.jsx +145 -0
- package/src/components/vault/QrScanner.jsx +326 -0
- package/src/components/vault/VaultAccessLog.jsx +225 -0
- package/src/components/vault/VaultConfirmPanel.jsx +113 -0
- package/src/components/vault/VaultEntryForm.jsx +740 -0
- package/src/components/vault/VaultManager.jsx +1000 -0
- package/src/components/vault/VaultQuickSearch.jsx +681 -0
- package/src/components/vault/qrDecode.js +257 -0
- package/src/components/vault/useDebouncedValue.js +22 -0
- package/src/components/vault/useVaultReveal.js +160 -0
- package/src/components/vault/vaultClipboard.js +33 -0
- package/src/components/vault/vaultEndpoints.js +40 -0
- package/src/components/vault/vaultFit.js +116 -0
- package/src/components/vault/vaultHelpers.js +547 -0
- package/src/components/vault/vaultNavigation.jsx +58 -0
- package/src/components/vault/vaultOtp.js +105 -0
- package/src/index.js +233 -1
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign-in design language, as a mixin.
|
|
3
|
+
*
|
|
4
|
+
* Login.module.scss is the original: an asymmetric two-pane split, the brand
|
|
5
|
+
* photograph on the left and the task on the right, everything driven off the
|
|
6
|
+
* consuming app's token layer with neutral fallbacks. Before 6.7.0 it was the
|
|
7
|
+
* only screen in the journey that looked like that — TwoFactorAuth, Reset and
|
|
8
|
+
* Verify were still the pre-6.1 centred `.lcontainer / .lwrap / .login` card,
|
|
9
|
+
* so signing in with 2FA meant crossing a visible design seam halfway through.
|
|
10
|
+
*
|
|
11
|
+
* The rules live here rather than in one shared module because these are CSS
|
|
12
|
+
* modules: each screen needs its own scoped copy of the class names. Every
|
|
13
|
+
* screen `@use`s this file and `@include auth-shell;` at the top of its own
|
|
14
|
+
* module, then adds only what is particular to it.
|
|
15
|
+
*
|
|
16
|
+
* Anything changed here must be mirrored in Login.module.scss, which keeps its
|
|
17
|
+
* own copy so the login screen's rendering cannot be disturbed by edits made
|
|
18
|
+
* for another screen.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
@use './authBrandPanel' as brand;
|
|
22
|
+
|
|
23
|
+
@mixin auth-shell {
|
|
24
|
+
/* ---------------------------------------------------------------------- */
|
|
25
|
+
/* frame */
|
|
26
|
+
/* ---------------------------------------------------------------------- */
|
|
27
|
+
|
|
28
|
+
.auth {
|
|
29
|
+
/* Local aliases only — every brand value comes from the consuming app's
|
|
30
|
+
token layer so this library stays reusable. The fallbacks are neutral
|
|
31
|
+
on purpose: a project that defines nothing gets a plain, working
|
|
32
|
+
screen rather than any one project's colours. Identical to the block
|
|
33
|
+
at the top of Login.module.scss. */
|
|
34
|
+
--ink: var(--header-color, #10192a);
|
|
35
|
+
--navy: var(--primary-color, #26364a);
|
|
36
|
+
--navy-deep: var(--primary-color-darker, #16202e);
|
|
37
|
+
--flag: var(--accent-color, #8a8f98);
|
|
38
|
+
--paper: var(--surface-color, #ffffff);
|
|
39
|
+
--warm: var(--hover-color, #f4f4f2);
|
|
40
|
+
--line: var(--border-color, #e4e1dc);
|
|
41
|
+
--muted: var(--muted-color, #6b7688);
|
|
42
|
+
|
|
43
|
+
position: fixed;
|
|
44
|
+
inset: 0;
|
|
45
|
+
display: grid;
|
|
46
|
+
grid-template-columns: 52fr 48fr;
|
|
47
|
+
background: var(--paper);
|
|
48
|
+
/* Read from a token first so an app can put its own face on the whole
|
|
49
|
+
journey; 'Barlow' is imported by the screens themselves. */
|
|
50
|
+
font-family: var(--auth-font-family, 'Barlow', system-ui, sans-serif);
|
|
51
|
+
color: var(--ink);
|
|
52
|
+
|
|
53
|
+
@media (max-width: 900px) {
|
|
54
|
+
grid-template-columns: 1fr;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------
|
|
59
|
+
// brand panel — shared with the staff Login and the client split layout.
|
|
60
|
+
// See _authBrandPanel.scss; `hide` is the staff screens' collapse.
|
|
61
|
+
// ---------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
@include brand.auth-brand-panel(hide);
|
|
64
|
+
|
|
65
|
+
/* ---------------------------------------------------------------------- */
|
|
66
|
+
/* form panel */
|
|
67
|
+
/* ---------------------------------------------------------------------- */
|
|
68
|
+
|
|
69
|
+
.panel {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
padding: 2rem clamp(1.5rem, 5vw, 4.5rem);
|
|
74
|
+
background: var(--paper);
|
|
75
|
+
overflow-y: auto;
|
|
76
|
+
|
|
77
|
+
@media (max-width: 900px) {
|
|
78
|
+
background: linear-gradient(var(--warm), var(--paper) 22rem);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.panelInner {
|
|
83
|
+
width: 100%;
|
|
84
|
+
max-width: 24rem;
|
|
85
|
+
text-align: left;
|
|
86
|
+
animation: rise 0.55s cubic-bezier(0.2, 0.7, 0.3, 1) both;
|
|
87
|
+
animation-delay: 0.08s;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.logo {
|
|
91
|
+
display: block;
|
|
92
|
+
width: auto;
|
|
93
|
+
height: 3.1rem;
|
|
94
|
+
margin-bottom: 2.4rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.title {
|
|
98
|
+
margin: 0 0 0.35rem;
|
|
99
|
+
font-size: 1.9rem;
|
|
100
|
+
font-weight: 700;
|
|
101
|
+
letter-spacing: -0.02em;
|
|
102
|
+
line-height: 1.1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.subtitle {
|
|
106
|
+
margin: 0 0 2rem;
|
|
107
|
+
font-size: 0.95rem;
|
|
108
|
+
font-weight: 400;
|
|
109
|
+
color: var(--muted);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.form {
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
gap: 0.9rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Floating label: the label sits in the field until it holds something, so
|
|
119
|
+
the field is self-describing without a separate line above it. */
|
|
120
|
+
.field {
|
|
121
|
+
position: relative;
|
|
122
|
+
|
|
123
|
+
input {
|
|
124
|
+
width: 100%;
|
|
125
|
+
height: 3.35rem;
|
|
126
|
+
padding: 1.15rem 0.9rem 0.4rem;
|
|
127
|
+
border: 1px solid var(--line);
|
|
128
|
+
border-radius: 0.6rem;
|
|
129
|
+
background: var(--paper);
|
|
130
|
+
font-family: inherit;
|
|
131
|
+
font-size: 1rem;
|
|
132
|
+
color: var(--ink);
|
|
133
|
+
transition:
|
|
134
|
+
border-color 0.15s ease,
|
|
135
|
+
box-shadow 0.15s ease;
|
|
136
|
+
|
|
137
|
+
&:hover {
|
|
138
|
+
border-color: #c9c5be;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
&:focus {
|
|
142
|
+
outline: none;
|
|
143
|
+
border-color: var(--navy);
|
|
144
|
+
box-shadow: 0 0 0 3px
|
|
145
|
+
var(--focus-ring-soft, rgba(38, 54, 74, 0.16));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&:disabled {
|
|
149
|
+
background: var(--warm);
|
|
150
|
+
color: var(--muted);
|
|
151
|
+
cursor: default;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
label {
|
|
156
|
+
position: absolute;
|
|
157
|
+
left: 0.95rem;
|
|
158
|
+
top: 1.05rem;
|
|
159
|
+
font-size: 1rem;
|
|
160
|
+
font-weight: 400;
|
|
161
|
+
color: var(--muted);
|
|
162
|
+
pointer-events: none;
|
|
163
|
+
transition:
|
|
164
|
+
transform 0.14s ease,
|
|
165
|
+
font-size 0.14s ease,
|
|
166
|
+
color 0.14s ease;
|
|
167
|
+
transform-origin: 0 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* :not(:placeholder-shown) is why every input carries placeholder=" ". */
|
|
171
|
+
input:focus + label,
|
|
172
|
+
input:not(:placeholder-shown) + label {
|
|
173
|
+
transform: translateY(-0.72rem);
|
|
174
|
+
font-size: 0.72rem;
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
letter-spacing: 0.02em;
|
|
177
|
+
color: var(--navy);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
&:has(.reveal) input {
|
|
181
|
+
padding-right: 3rem;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Set imperatively by the submit handlers on a failed attempt, so it is a
|
|
186
|
+
plain global class rather than a module one. */
|
|
187
|
+
:global(.inputError) {
|
|
188
|
+
border-color: var(--danger-color, #b3261e) !important;
|
|
189
|
+
box-shadow: 0 0 0 3px var(--danger-ring, rgba(179, 38, 30, 0.14)) !important;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.reveal {
|
|
193
|
+
position: absolute;
|
|
194
|
+
right: 0.55rem;
|
|
195
|
+
top: 50%;
|
|
196
|
+
transform: translateY(-50%);
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: center;
|
|
200
|
+
width: 2.1rem;
|
|
201
|
+
height: 2.1rem;
|
|
202
|
+
border: none;
|
|
203
|
+
border-radius: 0.4rem;
|
|
204
|
+
background: none;
|
|
205
|
+
color: var(--muted);
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
|
|
208
|
+
&:hover {
|
|
209
|
+
background: var(--warm);
|
|
210
|
+
color: var(--ink);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.row {
|
|
215
|
+
display: flex;
|
|
216
|
+
align-items: center;
|
|
217
|
+
justify-content: space-between;
|
|
218
|
+
gap: 1rem;
|
|
219
|
+
margin: 0.1rem 0 0.5rem;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.link {
|
|
223
|
+
font-size: 0.88rem;
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
color: var(--navy);
|
|
226
|
+
text-decoration: none;
|
|
227
|
+
background: none;
|
|
228
|
+
border: none;
|
|
229
|
+
padding: 0;
|
|
230
|
+
font-family: inherit;
|
|
231
|
+
cursor: pointer;
|
|
232
|
+
|
|
233
|
+
&:hover {
|
|
234
|
+
text-decoration: underline;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
&:disabled {
|
|
238
|
+
color: var(--muted);
|
|
239
|
+
cursor: default;
|
|
240
|
+
text-decoration: none;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Primary on these screens. Unlike Login — which keeps its email button
|
|
245
|
+
outlined because SSO is the filled action above it — verifying a code or
|
|
246
|
+
setting a password is the only thing the screen asks for, so it carries
|
|
247
|
+
the fill. */
|
|
248
|
+
.submit {
|
|
249
|
+
width: 100%;
|
|
250
|
+
height: 3.1rem;
|
|
251
|
+
border: 1px solid var(--navy);
|
|
252
|
+
border-radius: 0.6rem;
|
|
253
|
+
background: var(--navy);
|
|
254
|
+
color: #fff;
|
|
255
|
+
font-family: inherit;
|
|
256
|
+
font-size: 0.95rem;
|
|
257
|
+
font-weight: 600;
|
|
258
|
+
letter-spacing: 0.01em;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
transition:
|
|
261
|
+
border-color 0.15s ease,
|
|
262
|
+
background 0.15s ease,
|
|
263
|
+
transform 0.08s ease;
|
|
264
|
+
|
|
265
|
+
&:hover:not(:disabled) {
|
|
266
|
+
background: var(--navy-deep);
|
|
267
|
+
border-color: var(--navy-deep);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
&:active:not(:disabled) {
|
|
271
|
+
transform: translateY(1px);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
&:focus-visible {
|
|
275
|
+
outline: 3px solid var(--focus-ring-strong, rgba(38, 54, 74, 0.3));
|
|
276
|
+
outline-offset: 2px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
&:disabled {
|
|
280
|
+
opacity: 0.65;
|
|
281
|
+
cursor: default;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* A quiet way to a secondary path. Not a link — it performs an action on
|
|
286
|
+
this page rather than navigating anywhere. */
|
|
287
|
+
.altToggle {
|
|
288
|
+
display: block;
|
|
289
|
+
width: 100%;
|
|
290
|
+
margin-top: 1rem;
|
|
291
|
+
padding: 0.6rem;
|
|
292
|
+
border: none;
|
|
293
|
+
border-radius: 0.5rem;
|
|
294
|
+
background: none;
|
|
295
|
+
font-family: inherit;
|
|
296
|
+
font-size: 0.9rem;
|
|
297
|
+
font-weight: 600;
|
|
298
|
+
color: var(--navy);
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
|
|
301
|
+
&:hover {
|
|
302
|
+
background: var(--warm);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
&:focus-visible {
|
|
306
|
+
outline: 3px solid var(--focus-ring-strong, rgba(38, 54, 74, 0.3));
|
|
307
|
+
outline-offset: 2px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
&:disabled {
|
|
311
|
+
color: var(--muted);
|
|
312
|
+
cursor: default;
|
|
313
|
+
|
|
314
|
+
&:hover {
|
|
315
|
+
background: none;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.divider {
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
gap: 0.9rem;
|
|
324
|
+
margin: 1.4rem 0;
|
|
325
|
+
color: var(--muted);
|
|
326
|
+
font-size: 0.78rem;
|
|
327
|
+
letter-spacing: 0.08em;
|
|
328
|
+
text-transform: uppercase;
|
|
329
|
+
|
|
330
|
+
&::before,
|
|
331
|
+
&::after {
|
|
332
|
+
content: '';
|
|
333
|
+
flex: 1;
|
|
334
|
+
height: 1px;
|
|
335
|
+
background: var(--line);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* A muted line under the form — expiry copy, "having trouble?", the sign
|
|
340
|
+
off. Never a control. */
|
|
341
|
+
.note {
|
|
342
|
+
margin: 1.4rem 0 0;
|
|
343
|
+
font-size: 0.82rem;
|
|
344
|
+
line-height: 1.5;
|
|
345
|
+
color: var(--muted);
|
|
346
|
+
text-align: left;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.foot {
|
|
350
|
+
margin: 2.5rem 0 0;
|
|
351
|
+
font-size: 0.78rem;
|
|
352
|
+
color: var(--muted);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
@include brand.auth-rise-keyframes;
|
|
356
|
+
|
|
357
|
+
@media (prefers-reduced-motion: reduce) {
|
|
358
|
+
.brandInk,
|
|
359
|
+
.panelInner {
|
|
360
|
+
animation: none;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.submit,
|
|
364
|
+
.field input,
|
|
365
|
+
.field label {
|
|
366
|
+
transition: none;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -444,6 +444,8 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
444
444
|
.visns-native-date-filter {
|
|
445
445
|
position: relative;
|
|
446
446
|
display: flex;
|
|
447
|
+
/* Lets the rules below react to the column's width, not the window's. */
|
|
448
|
+
container-type: inline-size;
|
|
447
449
|
/* stretch, not center: the input has to fill the 32px the theme pins the
|
|
448
450
|
filter to, or the clickable area is smaller than the box it draws. */
|
|
449
451
|
align-items: stretch;
|
|
@@ -483,14 +485,18 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
483
485
|
min-width: 0;
|
|
484
486
|
height: auto;
|
|
485
487
|
margin: 0;
|
|
486
|
-
padding: 0 2px 0
|
|
488
|
+
padding: 0 2px 0 6px;
|
|
487
489
|
border: none;
|
|
488
490
|
outline: none;
|
|
489
491
|
background: none;
|
|
490
492
|
color: inherit;
|
|
491
493
|
font-family: inherit;
|
|
492
|
-
|
|
494
|
+
/* A notch under the grid's 14px: the locale mask is ten characters and
|
|
495
|
+
the default filter row is narrow. */
|
|
496
|
+
font-size: 13px;
|
|
497
|
+
letter-spacing: -0.01em;
|
|
493
498
|
line-height: 1;
|
|
499
|
+
font-variant-numeric: tabular-nums;
|
|
494
500
|
-webkit-appearance: none;
|
|
495
501
|
appearance: none;
|
|
496
502
|
}
|
|
@@ -516,6 +522,25 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
516
522
|
opacity: 1;
|
|
517
523
|
}
|
|
518
524
|
|
|
525
|
+
/* In a narrow column the glyph costs the last two digits of the year; the
|
|
526
|
+
field still opens the picker on click/Space, so drop the glyph first and the
|
|
527
|
+
clear button only when there is really no room. */
|
|
528
|
+
@container (max-width: 128px) {
|
|
529
|
+
.visns-native-date-filter__input::-webkit-calendar-picker-indicator {
|
|
530
|
+
display: none;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.visns-native-date-filter__input {
|
|
534
|
+
padding-right: 0;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@container (max-width: 104px) {
|
|
539
|
+
.visns-native-date-filter__clear {
|
|
540
|
+
display: none;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
519
544
|
/* Browser-supplied clear / spin affordances are replaced by our own button,
|
|
520
545
|
which is consistent across engines. */
|
|
521
546
|
.visns-native-date-filter__input::-webkit-clear-button,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time environment variables, whichever bundler compiled this.
|
|
3
|
+
*
|
|
4
|
+
* `import.meta.env` is Vite's interface, and two files in this library read it
|
|
5
|
+
* directly (Fetch.jsx for the payload ceiling, DataGrid.jsx for the asset
|
|
6
|
+
* path). That made the whole library Vite-only: under webpack — which is what
|
|
7
|
+
* the Next.js portal runs — `import.meta` survives the compile as a bare `{}`,
|
|
8
|
+
* so the property read threw a TypeError on the first request the app made.
|
|
9
|
+
* The portal needed a DefinePlugin shim to get past it.
|
|
10
|
+
*
|
|
11
|
+
* Both accesses here are guarded and both are wrapped, because the bundlers
|
|
12
|
+
* fail differently: one leaves `import.meta` undefined, the other leaves it an
|
|
13
|
+
* object with no `env`. Vite's behaviour is unchanged — its value is still
|
|
14
|
+
* found first and still wins.
|
|
15
|
+
*
|
|
16
|
+
* Pure module (no React, no DOM) so it can be unit-tested with `node --test`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read one variable, or `undefined`.
|
|
21
|
+
*
|
|
22
|
+
* Lookup order: Vite's `import.meta.env`, then `process.env` under the name as
|
|
23
|
+
* given, then `process.env` under a `NEXT_PUBLIC_` prefix — Next.js will only
|
|
24
|
+
* expose a variable to the browser under that prefix, so a `VITE_FOO` becomes
|
|
25
|
+
* `NEXT_PUBLIC_FOO` there.
|
|
26
|
+
*/
|
|
27
|
+
export const readBuildEnv = (key) => {
|
|
28
|
+
try {
|
|
29
|
+
// eslint-disable-next-line no-undef
|
|
30
|
+
const meta = typeof import.meta === 'undefined' ? null : import.meta;
|
|
31
|
+
const value = meta && meta.env ? meta.env[key] : undefined;
|
|
32
|
+
|
|
33
|
+
if (value !== undefined) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// No `import.meta` in this module format. Expected under a CommonJS
|
|
38
|
+
// output and under Jest; never worth reporting.
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
43
|
+
const direct = process.env[key];
|
|
44
|
+
|
|
45
|
+
if (direct !== undefined) {
|
|
46
|
+
return direct;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return process.env[
|
|
50
|
+
`NEXT_PUBLIC_${key.replace(/^(VITE_|REACT_APP_)/, '')}`
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// No `process` either. Fall through to the caller's own default.
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** `readBuildEnv` with a default for the (common) absent case. */
|
|
61
|
+
export const buildEnv = (key, fallback = '') => {
|
|
62
|
+
const value = readBuildEnv(key);
|
|
63
|
+
|
|
64
|
+
return value === undefined || value === null ? fallback : value;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default readBuildEnv;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small, pure display helpers shared by the generic detail page and the
|
|
3
|
+
* DataGrid column renderers.
|
|
4
|
+
*
|
|
5
|
+
* These live in their own module — with no React and no CSS import — so the
|
|
6
|
+
* rules they encode can be tested directly under `node --test` rather than
|
|
7
|
+
* inferred from a rendered tree. Every consumer of a `.jsx` in this repo has
|
|
8
|
+
* to bring a bundler with it; this file deliberately does not.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How many items are in a value that a column is counting?
|
|
13
|
+
*
|
|
14
|
+
* The grid's `arrayCount` column reads a JSON field straight off the API
|
|
15
|
+
* payload, and what actually arrives there varies by backend and by driver:
|
|
16
|
+
* a real array, a JSON *string* that was never decoded (a `json` cast the
|
|
17
|
+
* model forgot), an object keyed by id, or null for a record that has none.
|
|
18
|
+
* A bare `value.length` handles exactly the first of those and yields
|
|
19
|
+
* `undefined` — rendered as an empty cell — for the rest, which reads as
|
|
20
|
+
* "unknown" when the truthful answer is a number.
|
|
21
|
+
*
|
|
22
|
+
* @param {*} value raw field value
|
|
23
|
+
* @returns {number} item count; 0 when there is nothing countable
|
|
24
|
+
*/
|
|
25
|
+
export function arrayCountFrom(value) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof value === 'string') {
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
|
|
33
|
+
if (trimmed === '') {
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
return arrayCountFrom(JSON.parse(trimmed));
|
|
39
|
+
} catch {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// A keyed object (`{"1": {...}, "2": {...}}`) is the shape PHP produces
|
|
45
|
+
// when a list's keys stop being consecutive — same list, different JSON.
|
|
46
|
+
if (value && typeof value === 'object') {
|
|
47
|
+
return Object.keys(value).length;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Is a resolved field value one the page should show a placeholder for?
|
|
55
|
+
*
|
|
56
|
+
* `null` reaches the UI as the string "null" often enough (a stringified
|
|
57
|
+
* payload, a concatenated relation path) that treating it as content puts the
|
|
58
|
+
* literal word on the page. Zero and `false` are answers, not blanks.
|
|
59
|
+
*
|
|
60
|
+
* @param {*} value
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
export function isBlankValue(value) {
|
|
64
|
+
if (value === null || value === undefined) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof value === 'string') {
|
|
69
|
+
const trimmed = value.trim();
|
|
70
|
+
|
|
71
|
+
return trimmed === '' || trimmed === 'null' || trimmed === 'undefined';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The label shown above an overview value.
|
|
79
|
+
*
|
|
80
|
+
* Overview labels were written as inline run-in labels ("Label: value"), so a
|
|
81
|
+
* good number of the JSON configs carry their own trailing colon. Stacked
|
|
82
|
+
* above the value the colon is noise, and it is not something we can go and
|
|
83
|
+
* strip from every project's config — so it is normalised here, once.
|
|
84
|
+
*
|
|
85
|
+
* @param {*} label
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
export function overviewFieldLabel(label) {
|
|
89
|
+
if (label === null || label === undefined) {
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return String(label).trim().replace(/[::]\s*$/, '');
|
|
94
|
+
}
|