@visns-studio/visns-components 6.3.16 → 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 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.16",
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 className={styles.dropdownWrapper} ref={wrapperRef}>
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
- <div ref={wrapperRef}>
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
- <PasswordStrengthBar
484
- password={profile.password}
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 className={styles.formItem}>
498
- <label className={styles.fi__label}>
508
+ <div
509
+ className={`${styles.formItem} ${styles.formItemFull}`}
510
+ >
511
+ <div className={styles.signatureField}>
499
512
  <span
500
- className={`${styles.fi__span} ${styles.prefocus}`}
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
- </label>
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
- padding: 2px;
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: block;
134
- background: rgba(var(--primary-rgb), 0.05);
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: 10px 20px;
137
- border-radius: var(--br);
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
- font-weight: 700;
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
- display: flex;
188
- align-items: flex-start;
189
- flex-wrap: wrap;
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: 50%;
194
- padding: 0.35em;
236
+ width: auto;
237
+ padding: 0;
195
238
  position: relative;
196
239
  box-sizing: border-box;
240
+ }
197
241
 
198
- @media (max-width: 768px) {
199
- width: 100%;
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
- border: 1px solid transparent;
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
- /* Removed signatureContainer and signatureLabel styles as they're no longer needed */
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
- gap: 0.5em;
282
- z-index: 10;
283
-
284
- @media (max-width: 768px) {
285
- position: static;
286
- margin: 1.5rem 0;
287
- justify-content: flex-end;
288
- width: 100%;
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: 0.75rem;
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
- background-color: var(--primary-color);
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
- background-color: #f1f1f1;
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
- background-color: #e74c3c;
416
- color: white;
417
- font-size: 0.85rem;
418
- padding: 0.5rem 0.85rem;
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
- background-color: rgba(231, 76, 60, 0.85);
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: white;
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: white;
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
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
539
- transition: transform 0.2s ease, box-shadow 0.2s ease;
540
-
541
- &:hover {
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 {