benivo-ui-library 1.9.76 → 1.9.77

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 CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  `benivo-ui-library` is a collection of react hooks and components for faster development.
6
6
 
7
+ Accessibility is tested automatically with jest-axe — run `npm run a11y:audit` and see
8
+ [docs/accessibility-testing.md](docs/accessibility-testing.md) for the enforcement model.
9
+
7
10
  # Installation
8
11
 
9
12
  ```sh
@@ -36,3 +39,22 @@ function MyApp() {
36
39
  );
37
40
  }
38
41
  ```
42
+
43
+ # Publishing
44
+
45
+ Publishing auth comes from the `NPM_TOKEN` environment variable — never from a token
46
+ committed to this repo. Both `.npmrc` files reference it as `${NPM_TOKEN}`, which npm
47
+ expands at run time.
48
+
49
+ ```bash
50
+ export NPM_TOKEN=<your granular npm publish token for benivo-ui-library>
51
+ npm run publish:lib
52
+ ```
53
+
54
+ `publish:lib` runs `check:npm-token` first and stops with a clear message if `NPM_TOKEN`
55
+ is unset, rather than failing part-way through a publish.
56
+
57
+ Use a **granular** npm access token scoped to the `benivo-ui-library` package only, with
58
+ publish rights and an expiry — not a classic automation token on the whole `benivo`
59
+ account. Keep it in your own environment or a CI secret. Do not add it to `.npmrc`,
60
+ `.storybook/.npmrc`, or any other tracked file.
@@ -0,0 +1,394 @@
1
+ //
2
+ // Activity feed
3
+ //
4
+ // A timeline / message log built from FeedItems, matching the DS "Feed item base"
5
+ // and "Activity feed" (Figma page 253-14231). The container owns spacing and
6
+ // separators; the item owns its anatomy and the connector rail.
7
+ //
8
+ // Colours are tokens only (see scripts/check-tokens.sh): text on --black /
9
+ // --text-400 / --text-600, links and the "new" dot on --brand-700, rail and
10
+ // separators on --border-200, the date band on --bg-100.
11
+ //
12
+
13
+ @feed-bubble-shadow: 0 1px 1px rgba(16, 24, 40, 0.05);
14
+ @feed-gap: 32px; // spacing between items (Figma: 32px in every divider mode)
15
+
16
+ .bnv-feed {
17
+ display: flex;
18
+ flex-direction: column;
19
+ font-family: @font-family;
20
+
21
+ // ---- spacing per divider style ----
22
+ // none: 32px between items.
23
+ &--divider-none {
24
+ gap: @feed-gap;
25
+ }
26
+
27
+ // line: a full-width 1px separator sits in a 16px gap either side (~32px total).
28
+ &--divider-line {
29
+ gap: 16px;
30
+ }
31
+
32
+ // connector: the rail is a flex segment inside the item, so the item carries
33
+ // its own bottom spacing (Figma: content pb 32). No container gap.
34
+ &--divider-connector {
35
+ gap: 0;
36
+
37
+ .bnv-feed-item:last-child .bnv-feed-item__body {
38
+ padding-bottom: 0;
39
+ }
40
+ }
41
+
42
+ // With a 32px container gap, the date's own 12px block margin would make the
43
+ // date↔item spacing 44px, which reads as disconnected. Drop it here so it is a
44
+ // clean 32px, uniform with the item spacing. (Line mode keeps the 12px → 28px.)
45
+ &--divider-none &__date,
46
+ &--divider-connector &__date {
47
+ margin: 0;
48
+ }
49
+
50
+ // ---- separators ----
51
+ &__separator {
52
+ height: 1px;
53
+ background-color: var(--border-200);
54
+ border: 0;
55
+ flex-shrink: 0;
56
+ }
57
+
58
+ // ---- day / date grouping header ----
59
+ // A pill (bg-100) with a calendar glyph and a bold date. The 12px block
60
+ // padding sits on top of the container gap, matching Figma's date frame.
61
+ &__date {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 10px;
65
+ box-sizing: border-box;
66
+ width: 100%;
67
+ margin: 12px 0;
68
+ padding: 8px 12px;
69
+ background-color: var(--bg-100);
70
+ border-radius: 0.5rem;
71
+ }
72
+
73
+ &__date-icon {
74
+ flex-shrink: 0;
75
+ color: var(--text-600);
76
+ }
77
+
78
+ &__date-label {
79
+ font-size: @font-size; // 16px
80
+ line-height: 24px;
81
+ font-weight: @font-weight-semibold;
82
+ color: var(--black);
83
+ white-space: nowrap;
84
+ }
85
+ }
86
+
87
+ // ------------------------------------------------------------------ FeedItem
88
+ .bnv-feed-item {
89
+ --fi-avatar: 48px;
90
+ position: relative;
91
+ display: flex;
92
+ gap: 12px;
93
+ align-items: flex-start;
94
+ box-sizing: border-box;
95
+ font-size: @font-size; // 16px
96
+ line-height: 24px;
97
+ color: var(--text-600);
98
+
99
+ &--sm {
100
+ --fi-avatar: 32px;
101
+ }
102
+
103
+ // ---------- leading column + rail ----------
104
+ &__leading {
105
+ display: flex;
106
+ flex-direction: column;
107
+ align-items: center;
108
+ flex-shrink: 0;
109
+ width: var(--fi-avatar);
110
+ }
111
+
112
+ &--connector &__leading {
113
+ align-self: stretch;
114
+ gap: 4px;
115
+ padding-bottom: 4px;
116
+ }
117
+
118
+ &__avatar {
119
+ display: inline-flex;
120
+ flex-shrink: 0;
121
+ }
122
+
123
+ // Fallback marker when there is no avatar/icon.
124
+ &__marker {
125
+ width: 10px;
126
+ height: 10px;
127
+ margin-top: 7px;
128
+ border-radius: 50%;
129
+ background-color: var(--text-300);
130
+ flex-shrink: 0;
131
+ }
132
+
133
+ // Decorative timeline rail — grows to fill the column below the avatar.
134
+ &__rail {
135
+ flex: 1 0 0;
136
+ width: 2px;
137
+ min-height: 8px;
138
+ border-radius: 2px;
139
+ background-color: var(--border-200);
140
+ }
141
+
142
+ // ---------- body ----------
143
+ &__body {
144
+ flex: 1 1 0;
145
+ min-width: 0;
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: 12px;
149
+ }
150
+
151
+ &--connector &__body {
152
+ padding-bottom: @feed-gap;
153
+ }
154
+
155
+ // Header + action line sit tight together; 12px separates them from supporting.
156
+ &__text-block {
157
+ display: flex;
158
+ flex-direction: column;
159
+ }
160
+
161
+ &__heading {
162
+ display: flex;
163
+ flex-direction: column;
164
+ min-width: 0;
165
+ }
166
+
167
+ &__meta {
168
+ display: flex;
169
+ align-items: baseline;
170
+ flex-wrap: wrap;
171
+ gap: 8px;
172
+ min-width: 0;
173
+ }
174
+
175
+ &__actor {
176
+ font-weight: @font-weight-semibold;
177
+ color: var(--black);
178
+ }
179
+
180
+ &__timestamp {
181
+ font-weight: @font-weight-normal;
182
+ color: var(--text-400);
183
+ white-space: nowrap;
184
+ }
185
+
186
+ // @handle sub-line (messages).
187
+ &__handle {
188
+ font-size: @font-size-sm;
189
+ line-height: 20px;
190
+ color: var(--text-400);
191
+ }
192
+
193
+ // Status pill sits below the action line, left-aligned.
194
+ &__status {
195
+ display: flex;
196
+ }
197
+
198
+ // Edit/delete sit top-right, right of the name. Absolute + centred on the name
199
+ // row (24px) so taller buttons never push the description down.
200
+ &__trailing {
201
+ position: absolute;
202
+ top: 0;
203
+ right: 0;
204
+ height: 24px;
205
+ display: flex;
206
+ align-items: center;
207
+ gap: 8px;
208
+ }
209
+
210
+ &__actions {
211
+ display: flex;
212
+ align-items: center;
213
+ gap: 2px;
214
+ }
215
+
216
+ // Row actions use the DS Button (icon-only). It carries its own styling — this
217
+ // class is only a hook for context tweaks if ever needed.
218
+ &__action-btn {
219
+ flex-shrink: 0;
220
+ }
221
+
222
+ // Visually-hidden label (e.g. "Unread") — exposed to assistive tech only.
223
+ &__sr-only {
224
+ position: absolute;
225
+ width: 1px;
226
+ height: 1px;
227
+ margin: -1px;
228
+ padding: 0;
229
+ border: 0;
230
+ overflow: hidden;
231
+ clip: rect(0 0 0 0);
232
+ clip-path: inset(50%);
233
+ white-space: nowrap;
234
+ }
235
+
236
+ // Unread indicator.
237
+ &__new-dot {
238
+ width: 10px;
239
+ height: 10px;
240
+ border-radius: 50%;
241
+ background-color: var(--brand-700);
242
+ flex-shrink: 0;
243
+ }
244
+
245
+ // ---------- action line ----------
246
+ // Kept uniformly 16px/24px across every variant so the name→description gap is
247
+ // identical whether the item has a file, labels, text or nothing.
248
+ &__action {
249
+ color: var(--text-600);
250
+
251
+ a,
252
+ .bnv-feed-item__link {
253
+ color: var(--brand-700);
254
+ font-weight: @font-weight-medium;
255
+ text-decoration: none;
256
+
257
+ &:hover { text-decoration: underline; }
258
+
259
+ &:focus-visible {
260
+ outline: 2px solid var(--primary);
261
+ outline-offset: 2px;
262
+ border-radius: 0.125rem;
263
+ }
264
+ }
265
+ }
266
+
267
+ // ---------- supporting: file ----------
268
+ // The file is its own hoverable target — download/delete belong to the document,
269
+ // so they sit on its right and reveal on hover/focus. A "card" outline is drawn
270
+ // just outside the content so nothing shifts when it appears.
271
+ &__file {
272
+ position: relative;
273
+ display: flex;
274
+ gap: 12px;
275
+ align-items: flex-start;
276
+ width: 100%;
277
+ min-width: 0;
278
+
279
+ &::before {
280
+ content: '';
281
+ position: absolute;
282
+ inset: -8px -12px;
283
+ border: 1px solid transparent;
284
+ border-radius: 0.5rem;
285
+ pointer-events: none;
286
+ }
287
+
288
+ &:hover::before,
289
+ &:focus-within::before {
290
+ border-color: var(--border-200);
291
+ background-color: var(--white);
292
+ }
293
+
294
+ > * { position: relative; } // keep content above the ::before card
295
+ }
296
+
297
+ &__file-icon {
298
+ flex-shrink: 0;
299
+ width: 32px;
300
+ height: 40px;
301
+ }
302
+
303
+ &__file-text {
304
+ display: flex;
305
+ flex-direction: column;
306
+ flex: 1 1 auto;
307
+ min-width: 0;
308
+ }
309
+
310
+ &__file-name {
311
+ color: var(--black);
312
+ overflow: hidden;
313
+ text-overflow: ellipsis;
314
+ white-space: nowrap;
315
+ }
316
+
317
+ &__file-size {
318
+ font-size: @font-size-sm;
319
+ line-height: 20px;
320
+ color: var(--text-600);
321
+ }
322
+
323
+ // Actions on the right of the file row — hidden until hover/focus.
324
+ &__file-actions {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 2px;
328
+ flex-shrink: 0;
329
+ align-self: center;
330
+ opacity: 0;
331
+ transition: opacity 0.12s ease;
332
+ }
333
+
334
+ &__file:hover &__file-actions,
335
+ &__file:focus-within &__file-actions {
336
+ opacity: 1;
337
+ }
338
+
339
+ // Touch devices have no hover — keep the actions visible there.
340
+ @media (hover: none) {
341
+ &__file-actions { opacity: 1; }
342
+ }
343
+
344
+ // ---------- supporting: labels ----------
345
+ &__labels {
346
+ display: flex;
347
+ flex-wrap: wrap;
348
+ gap: 4px;
349
+ }
350
+
351
+ // ---------- supporting: text (message bubble) ----------
352
+ &__bubble {
353
+ box-sizing: border-box;
354
+ width: 100%;
355
+ padding: 12px;
356
+ background-color: var(--white);
357
+ border: 1px solid var(--border-200);
358
+ // Speech-bubble: square top-left, rounded elsewhere.
359
+ border-radius: 0 8px 8px 8px;
360
+ box-shadow: @feed-bubble-shadow;
361
+ }
362
+
363
+ &__text {
364
+ color: var(--black);
365
+ white-space: pre-wrap;
366
+ overflow-wrap: anywhere;
367
+
368
+ &.is-clamped {
369
+ display: -webkit-box;
370
+ -webkit-box-orient: vertical;
371
+ -webkit-line-clamp: 4; // overridden inline via clampLines
372
+ overflow: hidden;
373
+ position: relative;
374
+ }
375
+
376
+ // Fade the last visible line while clamped and actually overflowing.
377
+ &.is-clamped.is-overflowing::after {
378
+ content: '';
379
+ position: absolute;
380
+ left: 0;
381
+ right: 0;
382
+ bottom: 0;
383
+ height: 1.4em;
384
+ background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--white));
385
+ pointer-events: none;
386
+ }
387
+ }
388
+
389
+ // The toggle is the DS Button (text/link). Only position it below the text.
390
+ &__toggle {
391
+ align-self: flex-start;
392
+ margin-top: 8px;
393
+ }
394
+ }
@@ -1,13 +1,17 @@
1
1
  @import '../components/input/input.less';
2
2
  @import '../components/tooltip/tooltip.less';
3
3
  @import '../components/button/button.less';
4
+ @import '../components/closeButton/closeButton.less';
4
5
  @import '../components/spinner/spinner.less';
5
6
  @import '../components/fantasyButton/fantasyButton.less';
7
+ @import '../components/miButton/miButton.less';
6
8
  @import '../components/checkbox/checkbox.less';
7
9
  @import '../components/radiobutton/radiobutton.less';
10
+ @import '../components/checkboxGroup/checkboxGroup.less';
8
11
  @import '../components/slider/slider.less';
9
12
  @import '../components/progress/progress.less';
10
13
  @import '../icons/featuredIcon/featuredIcon.less';
14
+ @import '../icons/flagFeatureIcon/flagFeatureIcon.less';
11
15
  @import '../icons/starIcon/starIcon.less';
12
16
  @import '../icons/checkIcon/checkIcon.less';
13
17
  @import '../components/checkItem/checkItem.less';
@@ -15,11 +19,16 @@
15
19
  @import '../icons/folderIcon/folderIcon.less';
16
20
  @import '../icons/fileTypeIcon/fileTypeIcon.less';
17
21
  @import '../components/badge/badge.less';
22
+ @import '../components/activityFeed/activityFeed.less';
18
23
  @import '../components/notificationCard/notificationCard.less';
19
24
  @import '../components/alertBanner/alertBanner.less';
20
25
  @import '../components/alert/alert.less';
21
26
  @import '../icons/pageHeaderIcon/pageHeaderIcon.less';
22
27
  @import '../components/breadcrumbs/breadcrumbs.less';
28
+ @import '../components/pageIndicator/pageIndicator.less';
29
+ @import '../components/fileUpload/fileUpload.less';
30
+ @import '../components/pagination/pagination.less';
31
+ @import '../components/shared/optionList.less';
23
32
  @import '../components/select/select.less';
24
33
  @import '../components/datePicker/datePicker.less';
25
34
  @import '../components/navigation/navigation.less';
@@ -28,6 +37,9 @@
28
37
  @import '../components/copyInput/copyInput.less';
29
38
  @import '../components/currencyInput/currencyInput.less';
30
39
  @import '../components/phoneNumber/phoneNumber.less';
40
+ @import '../components/emptyState/emptyState.less';
41
+ @import '../components/emptyState/illustration/illustration.less';
42
+ @import '../components/menu/menu.less';
31
43
  @import '../components/heroInput/heroInput.less';
32
44
  @import '../components/formSection/formSection.less';
33
45
  @import '../components/verificationCode/verificationCode.less';
@@ -39,6 +51,8 @@
39
51
  @import '../components/countryFlag/countryFlag.less';
40
52
  @import '../components/flag/flag.less';
41
53
  @import '../components/textarea/textarea.less';
54
+ @import '../components/modal/modal.less';
55
+ @import '../components/fileCard/fileCard.less';
42
56
  @import './forms.less';
43
57
  @import './spacing.less';
44
58
  @import './typography.less';
package/button.less CHANGED
@@ -1,6 +1,13 @@
1
1
  //
2
2
  // Buttons
3
3
  //
4
+ // WCAG 1.4.3 (BPO-14588, EPApp sheet row 110322 "Update button in the profile page has
5
+ // insufficient contrast ratio"): all eight disabled variants below coloured their label
6
+ // var(--text-400) = #98A2B3. Measured on the profile page's Update button, which opens in its
7
+ // disabled state, that is 2.464:1 against the button's own background - the label is there but
8
+ // cannot be read. SC 1.4.3 does exempt "inactive user interface components", so this is fixed
9
+ // because an unreadable label is a defect either way, not because the criterion forces it.
10
+ // var(--text-500) measures 4.6:1 on --bg-100 and 4.8:1 on --bg-50.
4
11
  .client-hub {
5
12
  .dropdown .btn {
6
13
  &:first-child {
@@ -344,7 +351,7 @@
344
351
  opacity: 1;
345
352
  background: var(--bg-100);
346
353
  border-color: var(--bg-200);
347
- color: var(--text-400);
354
+ color: var(--text-500);
348
355
  }
349
356
  }
350
357
 
@@ -372,7 +379,7 @@
372
379
  opacity: 1;
373
380
  background: var(--bg-100);
374
381
  border-color: var(--bg-200);
375
- color: var(--text-400);
382
+ color: var(--text-500);
376
383
  }
377
384
 
378
385
  .bn-spinner div {
@@ -414,7 +421,7 @@
414
421
  opacity: 1;
415
422
  background: var(--white);
416
423
  border-color: var(--bg-200);
417
- color: var(--text-400);
424
+ color: var(--text-500);
418
425
  }
419
426
 
420
427
  .bn-spinner div {
@@ -445,7 +452,7 @@
445
452
  &[disabled] {
446
453
  opacity: 1;
447
454
  background: transparent;
448
- color: var(--text-400);
455
+ color: var(--text-500);
449
456
  }
450
457
 
451
458
  .bn-spinner div {
@@ -477,7 +484,7 @@
477
484
  &[disabled] {
478
485
  opacity: 1;
479
486
  background: transparent;
480
- color: var(--text-400);
487
+ color: var(--text-500);
481
488
  }
482
489
 
483
490
  .bn-spinner div {
@@ -527,7 +534,7 @@
527
534
  opacity: 1;
528
535
  background: var(--bg-100);
529
536
  border-color: var(--bg-200);
530
- color: var(--text-400);
537
+ color: var(--text-500);
531
538
  }
532
539
  }
533
540
 
@@ -556,7 +563,7 @@
556
563
  opacity: 1;
557
564
  background: var(--white);
558
565
  border-color: var(--bg-200);
559
- color: var(--text-400);
566
+ color: var(--text-500);
560
567
  }
561
568
 
562
569
  .bn-spinner div {
@@ -587,7 +594,7 @@
587
594
  &[disabled] {
588
595
  opacity: 1;
589
596
  background: transparent;
590
- color: var(--text-400);
597
+ color: var(--text-500);
591
598
  }
592
599
 
593
600
  .bn-spinner div {
@@ -632,4 +639,23 @@
632
639
  &.has-error {
633
640
  border-color: var(--error-main) !important;
634
641
  }
635
- }
642
+ }
643
+
644
+ // =====================================================================================
645
+ // SC 1.4.11 (BPO-14588, sheet rows 110087 / 110150): a button "appearing as normal text"
646
+ // in the Aquatic / Desert contrast themes.
647
+ //
648
+ // A contrast theme forces every background to Canvas, so a variant whose whole button-ness
649
+ // is its fill has nothing left. Most variants here also set border-color, which survives -
650
+ // but btn-color-link sets `border: none`, and any button carrying a variant class this
651
+ // stylesheet does not define falls back to the base rule and depends on the consumer having
652
+ // overridden @btn-border. Giving every .btn a ButtonText edge in forced colours costs
653
+ // nothing in normal rendering and makes the shape independent of which variant is in play -
654
+ // which matters most for CMS-authored buttons, whose class list is not in any repo.
655
+ // =====================================================================================
656
+ @media (forced-colors: active) {
657
+
658
+ .btn {
659
+ border: 1px solid ButtonText;
660
+ }
661
+ }