@visns-studio/visns-components 6.6.3 → 6.15.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/README.md +1658 -18
- package/package.json +10 -4
- package/src/components/DataGrid.jsx +619 -158
- 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/AutoGrowCell.jsx +141 -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/DataGrid.module.scss +10 -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 +29 -0
- 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
|
+
}
|
|
@@ -110,6 +110,35 @@ body.tablet-mode .InovuaReactDataGrid__cell.cell-word-wrap > div {
|
|
|
110
110
|
overflow: hidden !important;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/* Auto-grow cells — a column with `"autoGrow": true`.
|
|
114
|
+
|
|
115
|
+
The opposite trade to the tablet word-wrap clamp above: instead of the cell
|
|
116
|
+
clamping its content to the row, the ROW grows to its content (DataGrid
|
|
117
|
+
measures it through AutoGrowCell and raises that one row via the grid's
|
|
118
|
+
RowHeightManager). So the content must be allowed to take a second and third
|
|
119
|
+
line — the nowrap/ellipsis rule that applies to every other non-wrapping
|
|
120
|
+
cell would otherwise make the content one line tall forever and there would
|
|
121
|
+
be nothing to grow for.
|
|
122
|
+
|
|
123
|
+
The CELL keeps `overflow: hidden`: between the frame the content grows in
|
|
124
|
+
and the frame the row grows in, the extra lines are clipped at the row
|
|
125
|
+
boundary rather than spilling over the row below, which is what the grid
|
|
126
|
+
looks like today. The inner wrapper stays `overflow: visible` so nothing
|
|
127
|
+
clips once the row has caught up, and `align-self: center` on the grid's own
|
|
128
|
+
content wrapper keeps the value vertically centred in the taller row, next
|
|
129
|
+
to the neighbouring cells and the action buttons. */
|
|
130
|
+
.InovuaReactDataGrid__cell.cell-auto-grow,
|
|
131
|
+
.InovuaReactDataGrid__cell.cell-auto-grow .InovuaReactDataGrid__cell__content,
|
|
132
|
+
.InovuaReactDataGrid__cell.cell-auto-grow .autoGrowCell {
|
|
133
|
+
white-space: normal !important;
|
|
134
|
+
text-overflow: clip !important;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.InovuaReactDataGrid__cell.cell-auto-grow .InovuaReactDataGrid__cell__content,
|
|
138
|
+
.InovuaReactDataGrid__cell.cell-auto-grow .autoGrowCell {
|
|
139
|
+
overflow: visible !important;
|
|
140
|
+
}
|
|
141
|
+
|
|
113
142
|
/* NOTE: this fork renders header cells as .InovuaReactDataGrid__column-header
|
|
114
143
|
(there is no __header-cell class in its DOM), so header padding is left to
|
|
115
144
|
the module stylesheet; a __header-cell padding rule that used to sit here
|
|
@@ -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
|
+
}
|