@visns-studio/visns-components 6.3.15 → 6.3.17
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 +1 -1
- package/src/components/Notification.jsx +8 -1
- package/src/components/SwitchAccount.jsx +3 -1
- package/src/components/auth/Profile.jsx +39 -27
- package/src/components/styles/Navigation.module.scss +15 -0
- package/src/components/styles/Profile.module.scss +186 -91
- package/src/components/styles/global.css +10 -0
package/package.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
92
92
|
},
|
|
93
93
|
"name": "@visns-studio/visns-components",
|
|
94
|
-
"version": "6.3.
|
|
94
|
+
"version": "6.3.17",
|
|
95
95
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
96
96
|
"main": "src/index.js",
|
|
97
97
|
"files": [
|
|
@@ -239,7 +239,14 @@ function Notification(props) {
|
|
|
239
239
|
</button>
|
|
240
240
|
|
|
241
241
|
{show && (
|
|
242
|
-
<div
|
|
242
|
+
<div
|
|
243
|
+
className={styles.dropdownWrapper}
|
|
244
|
+
ref={wrapperRef}
|
|
245
|
+
/* Tells a scrollable host — the navigation's action pill —
|
|
246
|
+
to stop clipping while this is open. See
|
|
247
|
+
Navigation.module.scss `.hactions > ul`. */
|
|
248
|
+
data-nav-overlay="open"
|
|
249
|
+
>
|
|
243
250
|
<div className={styles.notibox}>
|
|
244
251
|
<div className={styles.notiboxHeader}>
|
|
245
252
|
<h3>Notifications</h3>
|
|
@@ -87,7 +87,9 @@ function SwitchAccount({ setSystemAuth, setting, userProfile }) {
|
|
|
87
87
|
</a>
|
|
88
88
|
|
|
89
89
|
{show && (
|
|
90
|
-
|
|
90
|
+
/* `data-nav-overlay` stops the navigation's scrollable action
|
|
91
|
+
pill from clipping this list. See Navigation.module.scss. */
|
|
92
|
+
<div ref={wrapperRef} data-nav-overlay="open">
|
|
91
93
|
{data.length > 0 ? (
|
|
92
94
|
<div className={styles.notibox}>
|
|
93
95
|
{data.map((item) => (
|
|
@@ -480,9 +480,20 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
480
480
|
label="Password"
|
|
481
481
|
error={inputClasses.password}
|
|
482
482
|
>
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
483
|
+
{/* Only once there is something to
|
|
484
|
+
measure. Rendered unconditionally it
|
|
485
|
+
sat under an empty password box
|
|
486
|
+
reporting "too short" before the user
|
|
487
|
+
had typed a character. */}
|
|
488
|
+
{profile.password ? (
|
|
489
|
+
<PasswordStrengthBar
|
|
490
|
+
password={profile.password}
|
|
491
|
+
className={styles.strengthMeter}
|
|
492
|
+
scoreWordClassName={
|
|
493
|
+
styles.strengthWord
|
|
494
|
+
}
|
|
495
|
+
/>
|
|
496
|
+
) : null}
|
|
486
497
|
</FormField>
|
|
487
498
|
|
|
488
499
|
<FormField
|
|
@@ -494,10 +505,12 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
494
505
|
error={inputClasses.password_confirmation}
|
|
495
506
|
/>
|
|
496
507
|
|
|
497
|
-
<div
|
|
498
|
-
|
|
508
|
+
<div
|
|
509
|
+
className={`${styles.formItem} ${styles.formItemFull}`}
|
|
510
|
+
>
|
|
511
|
+
<div className={styles.signatureField}>
|
|
499
512
|
<span
|
|
500
|
-
className={
|
|
513
|
+
className={styles.signatureLabel}
|
|
501
514
|
>
|
|
502
515
|
Signature
|
|
503
516
|
</span>
|
|
@@ -516,31 +529,38 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
516
529
|
}}
|
|
517
530
|
redrawOnResize
|
|
518
531
|
canvasProps={{
|
|
532
|
+
/* The frame lives on
|
|
533
|
+
`.sigcanvas` now. Its
|
|
534
|
+
geometry is unchanged —
|
|
535
|
+
border and padding move
|
|
536
|
+
where the ink lands. */
|
|
519
537
|
style: {
|
|
520
|
-
border: '2px solid #ccc',
|
|
521
|
-
borderRadius: '10px',
|
|
522
|
-
padding: '10px',
|
|
523
538
|
width: '100%',
|
|
524
539
|
},
|
|
525
540
|
className: styles.sigcanvas,
|
|
526
541
|
}}
|
|
527
542
|
/>
|
|
528
|
-
<small
|
|
529
|
-
style={{
|
|
530
|
-
display: 'block',
|
|
531
|
-
marginTop: '5px',
|
|
532
|
-
color: '#666',
|
|
533
|
-
}}
|
|
534
|
-
>
|
|
535
|
-
Draw your signature above
|
|
536
|
-
</small>
|
|
537
543
|
</div>
|
|
538
|
-
|
|
544
|
+
<small
|
|
545
|
+
className={styles.signatureHint}
|
|
546
|
+
>
|
|
547
|
+
Draw your signature above
|
|
548
|
+
</small>
|
|
549
|
+
</div>
|
|
539
550
|
</div>
|
|
540
551
|
</form>
|
|
541
552
|
</div>
|
|
542
553
|
|
|
554
|
+
{/* The form card's footer. Secondary before primary. */}
|
|
543
555
|
<div className={styles.polActions}>
|
|
556
|
+
<button
|
|
557
|
+
className={`${styles.btn} ${styles.btnSecondary}`}
|
|
558
|
+
onClick={handleClearSignature}
|
|
559
|
+
disabled={isLoading}
|
|
560
|
+
type="button"
|
|
561
|
+
>
|
|
562
|
+
Clear Signature
|
|
563
|
+
</button>
|
|
544
564
|
<button
|
|
545
565
|
className={`${styles.btn} ${
|
|
546
566
|
isLoading ? styles.loading : ''
|
|
@@ -551,14 +571,6 @@ const Profile = ({ userProfile, setUserProfile }) => {
|
|
|
551
571
|
>
|
|
552
572
|
{isLoading ? 'Saving...' : 'Save'}
|
|
553
573
|
</button>
|
|
554
|
-
<button
|
|
555
|
-
className={styles.btn}
|
|
556
|
-
onClick={handleClearSignature}
|
|
557
|
-
disabled={isLoading}
|
|
558
|
-
type="button"
|
|
559
|
-
>
|
|
560
|
-
Clear Signature
|
|
561
|
-
</button>
|
|
562
574
|
</div>
|
|
563
575
|
</div>
|
|
564
576
|
);
|
|
@@ -425,6 +425,21 @@
|
|
|
425
425
|
display: none;
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
+
/* A scroll container clips on BOTH axes: `overflow-x: auto` makes the
|
|
429
|
+
computed `overflow-y` `auto` as well, however `visible` it was
|
|
430
|
+
written. So the popovers that mounted components anchor to their own
|
|
431
|
+
chip — the notification dropdown, the account switcher list, both
|
|
432
|
+
`position: absolute; top: 100%` — were being cut off at the bottom of
|
|
433
|
+
this ~44px pill and never appeared on screen. The bell looked dead.
|
|
434
|
+
|
|
435
|
+
While one of those overlays is open the group stops scrolling and
|
|
436
|
+
lets it out. The overlays are only in the DOM while open, so they
|
|
437
|
+
advertise themselves with `data-nav-overlay` and nothing here has to
|
|
438
|
+
know which component owns them or track any open state. */
|
|
439
|
+
&:has([data-nav-overlay='open']) {
|
|
440
|
+
overflow: visible;
|
|
441
|
+
}
|
|
442
|
+
|
|
428
443
|
margin: 0;
|
|
429
444
|
flex-wrap: nowrap;
|
|
430
445
|
flex-direction: row;
|
|
@@ -115,7 +115,10 @@ select:focus + .fi__span {
|
|
|
115
115
|
border-radius: var(--br);
|
|
116
116
|
border: 1px solid rgba(var(--primary-rgb), 0.05);
|
|
117
117
|
box-sizing: border-box;
|
|
118
|
-
|
|
118
|
+
/* Was 2px, which left the section header's hairline floating a couple of
|
|
119
|
+
pixels short of the panel edge. The panel is a card; its header, body and
|
|
120
|
+
footer own their own padding, so the card itself has none. */
|
|
121
|
+
padding: 0;
|
|
119
122
|
margin: 8px 0;
|
|
120
123
|
height: auto;
|
|
121
124
|
position: relative;
|
|
@@ -128,21 +131,45 @@ select:focus + .fi__span {
|
|
|
128
131
|
margin: 0 0 30px 0;
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
/* The panel's section header. It follows the same spec as the unified page
|
|
135
|
+
header documented at the top of GenericIndex.module.scss — the panel's own
|
|
136
|
+
surface, one bottom hairline, the title left-aligned on the same padding as
|
|
137
|
+
the body beneath it — a step down in type size because this names a section
|
|
138
|
+
inside a page rather than the page.
|
|
139
|
+
|
|
140
|
+
It used to be a pale centred band with its own fill and radius: a second
|
|
141
|
+
card sitting on top of the card, and the only centred heading in the
|
|
142
|
+
library. */
|
|
131
143
|
.gridtxt__header {
|
|
132
144
|
width: 100%;
|
|
133
|
-
display:
|
|
134
|
-
|
|
145
|
+
display: flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
justify-content: space-between;
|
|
148
|
+
gap: var(--spacing-md, 1rem);
|
|
135
149
|
box-sizing: border-box;
|
|
136
|
-
padding:
|
|
137
|
-
|
|
150
|
+
padding: var(--spacing-md, 1rem);
|
|
151
|
+
background: none;
|
|
152
|
+
border-bottom: 1px solid var(--border-color, #e1e1e1);
|
|
153
|
+
border-radius: 0;
|
|
138
154
|
margin-bottom: 0;
|
|
139
|
-
|
|
140
|
-
text-align: center;
|
|
155
|
+
text-align: left;
|
|
141
156
|
color: var(--paragraph-color);
|
|
142
157
|
}
|
|
143
158
|
|
|
144
159
|
.gridtxt__header span {
|
|
145
160
|
font-weight: 700;
|
|
161
|
+
/* One step under the page title above it, so the two read as a hierarchy
|
|
162
|
+
rather than as two competing headings. */
|
|
163
|
+
font-size: var(--font-size-lg, 1.125rem);
|
|
164
|
+
line-height: 1.25;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.gridtxt {
|
|
168
|
+
/* One column width, shared by the form body and the footer under it, so
|
|
169
|
+
Save lands beneath the right edge of the fields instead of out at the
|
|
170
|
+
card's edge. A project can retune it from `:root` like the rest of the
|
|
171
|
+
library's density hooks. */
|
|
172
|
+
--profile-form-width: 56rem;
|
|
146
173
|
}
|
|
147
174
|
|
|
148
175
|
.gridtxt > ul {
|
|
@@ -180,24 +207,42 @@ select:focus + .fi__span {
|
|
|
180
207
|
|
|
181
208
|
.formcontainer {
|
|
182
209
|
width: 100%;
|
|
210
|
+
/* Two columns across the full width of a 1600px panel gave every field a
|
|
211
|
+
~600px line — far past the point where a name box reads as a box. The
|
|
212
|
+
form takes the width it needs and the rest of the panel stays quiet.
|
|
213
|
+
The 2FA tab is a document rather than a form and opts out with its own
|
|
214
|
+
`max-width: 100%`. */
|
|
215
|
+
max-width: var(--profile-form-width, 56rem);
|
|
216
|
+
box-sizing: border-box;
|
|
183
217
|
}
|
|
184
218
|
|
|
185
219
|
.modalForm {
|
|
186
220
|
width: 100%;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
221
|
+
box-sizing: border-box;
|
|
222
|
+
/* Was flex-wrap with 50%-wide children, so a field that needed the full
|
|
223
|
+
row had no way to say so and the row it started stayed half empty. */
|
|
224
|
+
display: grid;
|
|
225
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
226
|
+
align-items: start;
|
|
227
|
+
gap: var(--spacing-lg, 1.5rem) var(--spacing-md, 1rem);
|
|
228
|
+
padding: var(--spacing-md, 1rem);
|
|
229
|
+
|
|
230
|
+
@media (max-width: 768px) {
|
|
231
|
+
grid-template-columns: 1fr;
|
|
232
|
+
}
|
|
190
233
|
}
|
|
191
234
|
|
|
192
235
|
.formItem {
|
|
193
|
-
width:
|
|
194
|
-
padding: 0
|
|
236
|
+
width: auto;
|
|
237
|
+
padding: 0;
|
|
195
238
|
position: relative;
|
|
196
239
|
box-sizing: border-box;
|
|
240
|
+
}
|
|
197
241
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
242
|
+
/* A field that spans the row — the signature, which is a canvas rather than a
|
|
243
|
+
line of text and does not pair with anything beside it. */
|
|
244
|
+
.formItemFull {
|
|
245
|
+
grid-column: 1 / -1;
|
|
201
246
|
}
|
|
202
247
|
|
|
203
248
|
.fi__label {
|
|
@@ -230,7 +275,10 @@ select:focus + .fi__span {
|
|
|
230
275
|
.select {
|
|
231
276
|
background: var(--item-color);
|
|
232
277
|
border-radius: var(--br);
|
|
233
|
-
|
|
278
|
+
/* Was transparent: on a white panel the fields had no edge at all and the
|
|
279
|
+
form read as text floating in space. The width stays 1px so the floating
|
|
280
|
+
label, which is positioned against this box, does not move. */
|
|
281
|
+
border: 1px solid var(--border-color, #e1e1e1);
|
|
234
282
|
padding: 1rem;
|
|
235
283
|
outline: none;
|
|
236
284
|
width: 100%;
|
|
@@ -252,52 +300,105 @@ select:focus + .fi__span {
|
|
|
252
300
|
border: 1px solid rgba(var(--highlight-color-rgb), 0.85);
|
|
253
301
|
}
|
|
254
302
|
|
|
303
|
+
/* --- Password strength ---------------------------------------------------
|
|
304
|
+
`react-password-strength-bar` styles its own markup inline, so a class can
|
|
305
|
+
only reach it with `!important` — that is what every rule below is for, and
|
|
306
|
+
the only reason for it. The bar itself is four flex children; the score word
|
|
307
|
+
is the `<p>` after them.
|
|
308
|
+
|
|
309
|
+
The meter is rendered only once the field has a value (see Profile.jsx), so
|
|
310
|
+
nothing here has to hide the "too short" state that used to sit under an
|
|
311
|
+
empty password box telling the user off before they had typed. */
|
|
312
|
+
.strengthMeter {
|
|
313
|
+
/* Sits directly under the input it measures, close enough to read as part
|
|
314
|
+
of the field rather than as a rule floating beneath it. */
|
|
315
|
+
margin-top: 0.35rem;
|
|
316
|
+
|
|
317
|
+
/* The segments: 2px hairlines with 4px of white between them read as four
|
|
318
|
+
unrelated underlines. A little more weight and a rounded cap makes them
|
|
319
|
+
one meter. */
|
|
320
|
+
div > div {
|
|
321
|
+
height: 4px !important;
|
|
322
|
+
border-radius: var(--radius-pill, 999px) !important;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.strengthWord {
|
|
327
|
+
margin: 0.3rem 0 0 !important;
|
|
328
|
+
font-size: var(--font-size-xs, 0.75rem) !important;
|
|
329
|
+
line-height: 1.2 !important;
|
|
330
|
+
color: var(--muted-color, #6b7688) !important;
|
|
331
|
+
text-align: right !important;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* --- Signature ------------------------------------------------------------
|
|
335
|
+
The canvas geometry (2px border, 10px padding) is deliberately unchanged:
|
|
336
|
+
signature_pad maps pointer coordinates through the element's bounding rect,
|
|
337
|
+
so border and padding shift where the ink lands. Only the colour and radius
|
|
338
|
+
are tokenised, and the frame moved off the inline `canvasProps.style` onto
|
|
339
|
+
this class where a project can reach it. */
|
|
255
340
|
.sigcanvas {
|
|
256
|
-
border: 2px solid #ccc;
|
|
257
|
-
border-radius: 10px;
|
|
341
|
+
border: 2px solid var(--border-color, #ccc);
|
|
342
|
+
border-radius: var(--radius-md, 10px);
|
|
258
343
|
padding: 10px;
|
|
344
|
+
box-sizing: border-box;
|
|
259
345
|
}
|
|
260
346
|
|
|
261
|
-
|
|
347
|
+
.signatureField {
|
|
348
|
+
display: flex;
|
|
349
|
+
flex-direction: column;
|
|
350
|
+
gap: 0.4rem;
|
|
351
|
+
/* A signature is a fixed-size mark, not a field that benefits from more
|
|
352
|
+
room. Left to fill the row it became the largest thing on the page. */
|
|
353
|
+
max-width: 32rem;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* The label was a floating `.fi__span`, absolutely positioned so it landed on
|
|
357
|
+
top of the canvas border — the treatment belongs to inputs that have a value
|
|
358
|
+
to sit above. A canvas has none, so this is an ordinary label. */
|
|
359
|
+
.signatureLabel {
|
|
360
|
+
font-size: var(--font-size-sm, 0.8125rem);
|
|
361
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
362
|
+
color: var(--paragraph-color);
|
|
363
|
+
}
|
|
262
364
|
|
|
263
365
|
.signaturePadWrapper {
|
|
264
366
|
width: 100%;
|
|
265
|
-
margin-bottom: 0.5rem;
|
|
266
367
|
position: relative;
|
|
267
368
|
z-index: 1;
|
|
268
369
|
|
|
269
370
|
canvas {
|
|
270
371
|
max-width: 100%;
|
|
271
372
|
height: auto !important;
|
|
373
|
+
display: block;
|
|
272
374
|
}
|
|
273
375
|
}
|
|
274
376
|
|
|
377
|
+
.signatureHint {
|
|
378
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
379
|
+
color: var(--muted-color, #666);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* The form card's footer. This was `position: fixed` in the bottom-right
|
|
383
|
+
corner of the viewport: the page's Save button floated over whatever the
|
|
384
|
+
user had scrolled to, unattached to the form it saved. It is now the bottom
|
|
385
|
+
edge of the card, on the same hairline-and-padding spec as the header at the
|
|
386
|
+
top of it. Secondary before primary, so the destructive-ish action is not
|
|
387
|
+
the one under the thumb. */
|
|
275
388
|
.polActions {
|
|
276
|
-
position: fixed;
|
|
277
|
-
bottom: 20px;
|
|
278
|
-
right: 20px;
|
|
279
|
-
width: auto;
|
|
280
389
|
display: flex;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
390
|
+
align-items: center;
|
|
391
|
+
justify-content: flex-end;
|
|
392
|
+
gap: var(--spacing-sm, 0.5rem);
|
|
393
|
+
width: 100%;
|
|
394
|
+
max-width: var(--profile-form-width, 56rem);
|
|
395
|
+
box-sizing: border-box;
|
|
396
|
+
padding: var(--spacing-md, 1rem);
|
|
397
|
+
border-top: 1px solid var(--border-color, #e1e1e1);
|
|
290
398
|
}
|
|
291
399
|
|
|
292
400
|
.polActions button {
|
|
293
|
-
padding: 0.65em 1em;
|
|
294
401
|
margin: 0;
|
|
295
|
-
transition: all 0.2s ease;
|
|
296
|
-
|
|
297
|
-
&:disabled {
|
|
298
|
-
opacity: 0.6;
|
|
299
|
-
cursor: not-allowed;
|
|
300
|
-
}
|
|
301
402
|
}
|
|
302
403
|
|
|
303
404
|
|
|
@@ -333,7 +434,7 @@ select:focus + .fi__span {
|
|
|
333
434
|
|
|
334
435
|
/* Two-Factor Authentication Styles */
|
|
335
436
|
.twoFactorSection {
|
|
336
|
-
padding:
|
|
437
|
+
padding: var(--spacing-md, 1rem);
|
|
337
438
|
width: 100%;
|
|
338
439
|
display: flex;
|
|
339
440
|
flex-direction: column;
|
|
@@ -356,79 +457,76 @@ select:focus + .fi__span {
|
|
|
356
457
|
line-height: 1.4;
|
|
357
458
|
color: var(--paragraph-color);
|
|
358
459
|
font-size: 0.9rem;
|
|
460
|
+
/* This tab is deliberately full width — it holds a QR code and a grid
|
|
461
|
+
of recovery codes — but the prose explaining 2FA ran the whole way
|
|
462
|
+
across a 1600px panel. Only the paragraphs are capped. */
|
|
463
|
+
max-width: 68ch;
|
|
359
464
|
}
|
|
360
465
|
}
|
|
361
466
|
|
|
362
467
|
.statusEnabled {
|
|
363
|
-
color: #2ecc71;
|
|
468
|
+
color: var(--success-color, #2ecc71);
|
|
364
469
|
font-weight: 500;
|
|
365
470
|
padding: 0.2rem 0.4rem;
|
|
366
|
-
background-color: rgba(46, 204, 113, 0.1);
|
|
367
|
-
border-radius: 4px;
|
|
471
|
+
background-color: var(--success-color-light, rgba(46, 204, 113, 0.1));
|
|
472
|
+
border-radius: var(--radius-sm, 4px);
|
|
368
473
|
font-size: 0.85rem;
|
|
369
474
|
}
|
|
370
475
|
|
|
371
476
|
.statusDisabled {
|
|
372
|
-
color: #e74c3c;
|
|
477
|
+
color: var(--danger-color, #e74c3c);
|
|
373
478
|
font-weight: 500;
|
|
374
479
|
padding: 0.2rem 0.4rem;
|
|
375
|
-
background-color: rgba(231, 76, 60, 0.1);
|
|
376
|
-
border-radius: 4px;
|
|
480
|
+
background-color: var(--danger-light, rgba(231, 76, 60, 0.1));
|
|
481
|
+
border-radius: var(--radius-sm, 4px);
|
|
377
482
|
font-size: 0.85rem;
|
|
378
483
|
}
|
|
379
484
|
|
|
380
485
|
.confirmedMessage {
|
|
381
486
|
margin: 1rem 0;
|
|
382
487
|
padding: 0.75rem;
|
|
383
|
-
background-color: rgba(46, 204, 113, 0.1);
|
|
384
|
-
border-left: 3px solid #2ecc71;
|
|
488
|
+
background-color: var(--success-color-light, rgba(46, 204, 113, 0.1));
|
|
489
|
+
border-left: 3px solid var(--success-color, #2ecc71);
|
|
385
490
|
border-radius: var(--br);
|
|
386
491
|
color: #333;
|
|
387
492
|
font-size: 0.9rem;
|
|
388
493
|
}
|
|
389
494
|
|
|
495
|
+
/* The three variants each carried their own font size and padding, so a row of
|
|
496
|
+
them next to `.btn` — which comes from the shared `_controls` mixins — sat a
|
|
497
|
+
few pixels short and half a step smaller. They are colour only now; the
|
|
498
|
+
shape comes from the one place that defines it. */
|
|
390
499
|
.btnPrimary {
|
|
391
|
-
|
|
392
|
-
color: white;
|
|
393
|
-
font-size: 0.85rem;
|
|
394
|
-
padding: 0.5rem 0.85rem;
|
|
395
|
-
|
|
396
|
-
&:hover:not(:disabled) {
|
|
397
|
-
background-color: rgba(var(--primary-rgb), 0.9);
|
|
398
|
-
}
|
|
500
|
+
@include button-primary;
|
|
399
501
|
}
|
|
400
502
|
|
|
401
503
|
.btnSecondary {
|
|
402
|
-
|
|
403
|
-
color: #333;
|
|
404
|
-
border: 1px solid #ddd;
|
|
405
|
-
font-size: 0.85rem;
|
|
406
|
-
padding: 0.5rem 0.85rem;
|
|
407
|
-
|
|
408
|
-
&:hover:not(:disabled) {
|
|
409
|
-
background-color: var(--bg-color, #f5f3ef);
|
|
410
|
-
border-color: var(--border-color, #e1e1e1);
|
|
411
|
-
}
|
|
504
|
+
@include button-secondary;
|
|
412
505
|
}
|
|
413
506
|
|
|
414
507
|
.btnDanger {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
508
|
+
@include button-base;
|
|
509
|
+
|
|
510
|
+
background: var(--danger-color, #e74c3c);
|
|
511
|
+
border-color: var(--danger-color, #e74c3c);
|
|
512
|
+
color: #fff;
|
|
419
513
|
|
|
420
514
|
&:hover:not(:disabled) {
|
|
421
|
-
|
|
515
|
+
/* Deliberately still a solid fill rather than the library's ghost
|
|
516
|
+
`button-danger`: this is the control that turns a second factor off,
|
|
517
|
+
and it should not read as quieter than the button that turned it
|
|
518
|
+
on. */
|
|
519
|
+
filter: brightness(0.9);
|
|
422
520
|
}
|
|
423
521
|
}
|
|
424
522
|
|
|
425
523
|
.twoFactorSetup {
|
|
426
524
|
margin-top: 1.5rem;
|
|
427
525
|
padding: 1.5rem;
|
|
428
|
-
background-color:
|
|
526
|
+
background-color: var(--paper, #fff);
|
|
429
527
|
border-radius: var(--br);
|
|
430
|
-
border: 1px solid #e0e0e0;
|
|
431
|
-
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.05);
|
|
528
|
+
border: 1px solid var(--border-color, #e0e0e0);
|
|
529
|
+
box-shadow: var(--shadow-sm, 0 3px 8px rgba(0, 0, 0, 0.05));
|
|
432
530
|
width: 100%;
|
|
433
531
|
|
|
434
532
|
h3 {
|
|
@@ -437,7 +535,7 @@ select:focus + .fi__span {
|
|
|
437
535
|
font-weight: 500;
|
|
438
536
|
color: var(--primary-color);
|
|
439
537
|
padding-bottom: 0.75rem;
|
|
440
|
-
border-bottom: 1px solid #f0f0f0;
|
|
538
|
+
border-bottom: 1px solid var(--border-color, #f0f0f0);
|
|
441
539
|
}
|
|
442
540
|
}
|
|
443
541
|
|
|
@@ -531,17 +629,14 @@ select:focus + .fi__span {
|
|
|
531
629
|
justify-content: center;
|
|
532
630
|
margin: 0.75rem 0;
|
|
533
631
|
padding: 1rem;
|
|
534
|
-
background-color:
|
|
632
|
+
background-color: var(--paper, #fff);
|
|
535
633
|
border-radius: var(--br);
|
|
536
|
-
border: 1px solid #e0e0e0;
|
|
634
|
+
border: 1px solid var(--border-color, #e0e0e0);
|
|
537
635
|
width: 100%;
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
transform: translateY(-2px);
|
|
543
|
-
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
|
|
544
|
-
}
|
|
636
|
+
/* The panel used to lift and deepen its shadow on hover. Nothing here is
|
|
637
|
+
clickable — it is a QR code to point a phone at — so the affordance was
|
|
638
|
+
promising an interaction that does not exist. */
|
|
639
|
+
box-shadow: var(--shadow-sm, 0 2px 6px rgba(0, 0, 0, 0.08));
|
|
545
640
|
}
|
|
546
641
|
|
|
547
642
|
.qrCodeSvg {
|
|
@@ -588,7 +683,7 @@ select:focus + .fi__span {
|
|
|
588
683
|
background-color: #f9f9f9;
|
|
589
684
|
padding: 0.75rem;
|
|
590
685
|
border-radius: var(--br);
|
|
591
|
-
border: 1px solid #e0e0e0;
|
|
686
|
+
border: 1px solid var(--border-color, #e0e0e0);
|
|
592
687
|
width: 100%;
|
|
593
688
|
}
|
|
594
689
|
|
|
@@ -635,7 +730,7 @@ select:focus + .fi__span {
|
|
|
635
730
|
background-color: #f9f9f9;
|
|
636
731
|
padding: 1rem;
|
|
637
732
|
border-radius: var(--br);
|
|
638
|
-
border: 1px solid #e0e0e0;
|
|
733
|
+
border: 1px solid var(--border-color, #e0e0e0);
|
|
639
734
|
width: 100%;
|
|
640
735
|
}
|
|
641
736
|
|
|
@@ -670,7 +765,7 @@ select:focus + .fi__span {
|
|
|
670
765
|
padding: 1rem;
|
|
671
766
|
background-color: #f9f9f9;
|
|
672
767
|
border-radius: var(--br);
|
|
673
|
-
border: 1px solid #e0e0e0;
|
|
768
|
+
border: 1px solid var(--border-color, #e0e0e0);
|
|
674
769
|
width: 100%;
|
|
675
770
|
}
|
|
676
771
|
|
|
@@ -710,8 +805,8 @@ select:focus + .fi__span {
|
|
|
710
805
|
.recoveryCodesWarning {
|
|
711
806
|
margin-top: 0.75rem;
|
|
712
807
|
padding: 0.75rem;
|
|
713
|
-
background-color: rgba(231, 76, 60, 0.1);
|
|
714
|
-
border-left: 3px solid #e74c3c;
|
|
808
|
+
background-color: var(--danger-light, rgba(231, 76, 60, 0.1));
|
|
809
|
+
border-left: 3px solid var(--danger-color, #e74c3c);
|
|
715
810
|
border-radius: var(--br);
|
|
716
811
|
color: #333;
|
|
717
812
|
font-size: 0.85rem;
|
|
@@ -722,7 +817,7 @@ select:focus + .fi__span {
|
|
|
722
817
|
justify-content: flex-end;
|
|
723
818
|
margin-top: 1.5rem;
|
|
724
819
|
padding-top: 1rem;
|
|
725
|
-
border-top: 1px solid #f0f0f0;
|
|
820
|
+
border-top: 1px solid var(--border-color, #f0f0f0);
|
|
726
821
|
}
|
|
727
822
|
|
|
728
823
|
.crmtitle {
|
|
@@ -39,6 +39,16 @@ select {
|
|
|
39
39
|
font: inherit;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/* Floor under every textarea: the browser default (`rows` omitted) is two
|
|
43
|
+
lines, which renders a multi-line field as a slit. Three lines of text
|
|
44
|
+
plus the field's own padding is the least a free-text box can be and
|
|
45
|
+
still read as one. A bare element selector, so any component that sets
|
|
46
|
+
its own height with a class still wins. */
|
|
47
|
+
textarea {
|
|
48
|
+
min-height: 5.5em; /* ~3 lines + padding, for browsers without lh */
|
|
49
|
+
min-height: calc(3lh + 2rem);
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
/* Global radio button styling */
|
|
43
53
|
input[type='radio'],
|
|
44
54
|
.radio-input {
|