@boostdev/design-system-components 2.15.0 → 2.17.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/AGENTS.md +22 -15
- package/dist/client.cjs +271 -205
- package/dist/client.css +813 -650
- package/dist/client.d.cts +19 -4
- package/dist/client.d.ts +19 -4
- package/dist/client.js +206 -139
- package/dist/index.cjs +271 -205
- package/dist/index.css +813 -650
- package/dist/index.d.cts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +206 -139
- package/dist/web-components/chunk-2DHW2WJZ.js +154 -0
- package/dist/web-components/{chunk-3WAV6MDN.js → chunk-3FSGP35V.js} +1 -0
- package/dist/web-components/{chunk-SYNLFKD3.js → chunk-3LQKFSMR.js} +6 -0
- package/dist/web-components/{chunk-KA3VPKMY.js → chunk-4MB2ICSK.js} +1 -0
- package/dist/web-components/{chunk-6G22I2CK.js → chunk-5ATTKUCP.js} +33 -3
- package/dist/web-components/{chunk-IDSM74LM.js → chunk-6XL5P76D.js} +3 -3
- package/dist/web-components/{chunk-P24JYC5V.js → chunk-7VXRMCAL.js} +10 -4
- package/dist/web-components/{chunk-CUKQ2L5B.js → chunk-AGI6YQRC.js} +9 -16
- package/dist/web-components/chunk-BDRV452N.js +51 -0
- package/dist/web-components/{chunk-3AY2YKCM.js → chunk-CKCPV347.js} +1 -0
- package/dist/web-components/{chunk-HBLJ4SLY.js → chunk-DD3OUXNG.js} +5 -5
- package/dist/web-components/{chunk-RVF7W653.js → chunk-DGOCPKHI.js} +30 -10
- package/dist/web-components/{chunk-2OLVQUBU.js → chunk-EYCW7TKB.js} +8 -15
- package/dist/web-components/{chunk-VANAKB6D.js → chunk-GPJWLAKU.js} +33 -16
- package/dist/web-components/{chunk-VED2DFJ6.js → chunk-GQNQBEYR.js} +8 -15
- package/dist/web-components/{chunk-WFVKU4GX.js → chunk-IKAJLROF.js} +49 -4
- package/dist/web-components/{chunk-RSZ3BWBK.js → chunk-KSK4RZZQ.js} +30 -7
- package/dist/web-components/{chunk-FEDABOR5.js → chunk-LHFUXZ52.js} +7 -15
- package/dist/web-components/{chunk-M3ECULRH.js → chunk-RBF7WPLI.js} +10 -8
- package/dist/web-components/{chunk-BPYKZVDK.js → chunk-SJLMW2VR.js} +2 -2
- package/dist/web-components/chunk-SRKQ6GST.js +40 -0
- package/dist/web-components/{chunk-5HUAFCU6.js → chunk-TJV7UIXC.js} +7 -0
- package/dist/web-components/{chunk-LDIPCTRN.js → chunk-WWWJCSXN.js} +50 -4
- package/dist/web-components/{chunk-J3HVUIUH.js → chunk-XI2U6U26.js} +8 -15
- package/dist/web-components/{chunk-JH6LQKLK.js → chunk-XJU7KITO.js} +8 -15
- package/dist/web-components/{chunk-JV5QT2OU.js → chunk-YLDQOESI.js} +27 -13
- package/dist/web-components/globals.js +1 -1
- package/dist/web-components/index.d.ts +1 -0
- package/dist/web-components/index.js +35 -30
- package/dist/web-components/interaction/bds-dropdown-menu-group.d.ts +41 -0
- package/dist/web-components/interaction/bds-dropdown-menu-group.js +7 -0
- package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +14 -1
- package/dist/web-components/interaction/bds-dropdown-menu-item.js +1 -1
- package/dist/web-components/interaction/bds-dropdown-menu.d.ts +2 -1
- package/dist/web-components/interaction/bds-dropdown-menu.js +1 -1
- package/dist/web-components/interaction/bds-tab.d.ts +5 -0
- package/dist/web-components/interaction/bds-tab.js +1 -1
- package/dist/web-components/interaction/bds-tooltip.js +1 -1
- package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +6 -0
- package/dist/web-components/interaction/form/bds-checkbox-group.js +2 -1
- package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
- package/dist/web-components/interaction/form/bds-combobox.js +2 -1
- package/dist/web-components/interaction/form/bds-form-input.js +2 -1
- package/dist/web-components/interaction/form/bds-number-input.js +2 -1
- package/dist/web-components/interaction/form/bds-radio-group.d.ts +5 -0
- package/dist/web-components/interaction/form/bds-radio-group.js +2 -1
- package/dist/web-components/interaction/form/bds-radio.js +1 -1
- package/dist/web-components/interaction/form/bds-select.js +2 -1
- package/dist/web-components/interaction/form/bds-slider.js +2 -1
- package/dist/web-components/interaction/form/bds-switch.js +1 -1
- package/dist/web-components/interaction/form/bds-textarea.js +2 -1
- package/dist/web-components/ui/bds-alert.d.ts +8 -1
- package/dist/web-components/ui/bds-alert.js +1 -1
- package/dist/web-components/ui/bds-avatar.d.ts +11 -0
- package/dist/web-components/ui/bds-avatar.js +1 -1
- package/dist/web-components/ui/bds-badge.js +1 -1
- package/dist/web-components/ui/bds-notification-banner.d.ts +14 -1
- package/dist/web-components/ui/bds-notification-banner.js +1 -1
- package/dist/web-components/ui/bds-rating.js +1 -1
- package/dist/web-components/ui/bds-section-header.d.ts +6 -0
- package/dist/web-components/ui/bds-section-header.js +1 -1
- package/dist/web-components/ui/bds-separator.js +1 -1
- package/dist/web-components/ui/bds-table.js +1 -1
- package/package.json +6 -2
- package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +35 -4
- package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +58 -10
- package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +114 -1
- package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +25 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +69 -17
- package/src/components/interaction/Rating/Rating.mdx +4 -1
- package/src/components/interaction/Rating/Rating.module.css +9 -0
- package/src/components/interaction/Rating/Rating.spec.tsx +9 -0
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -0
- package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +7 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +2 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +8 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +27 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +1 -1
- package/src/components/interaction/form/Combobox/Combobox.module.css +1 -0
- package/src/components/interaction/form/Combobox/Combobox.spec.tsx +7 -0
- package/src/components/interaction/form/FormInput/FormInput.mdx +9 -0
- package/src/components/interaction/form/FormInput/FormInput.module.css +1 -0
- package/src/components/interaction/form/FormInput/FormInput.spec.tsx +7 -0
- package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -0
- package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +7 -0
- package/src/components/interaction/form/Radio/Radio.module.css +1 -0
- package/src/components/interaction/form/Radio/Radio.spec.tsx +7 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +2 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +8 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +27 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +1 -1
- package/src/components/interaction/form/Select/Select.module.css +1 -0
- package/src/components/interaction/form/Select/Select.spec.tsx +7 -0
- package/src/components/interaction/form/Switch/Switch.module.css +1 -0
- package/src/components/interaction/form/Switch/Switch.spec.tsx +7 -0
- package/src/components/interaction/form/Textarea/Textarea.module.css +1 -0
- package/src/components/interaction/form/Textarea/Textarea.spec.tsx +7 -0
- package/src/components/interaction/form/atoms/Message.module.css +14 -3
- package/src/components/interaction/form/atoms/Message.spec.tsx +22 -0
- package/src/components/interaction/form/atoms/Message.tsx +5 -0
- package/src/components/layout/SectionHeader/SectionHeader.mdx +18 -5
- package/src/components/layout/SectionHeader/SectionHeader.module.css +35 -14
- package/src/components/layout/SectionHeader/SectionHeader.spec.tsx +44 -0
- package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +5 -0
- package/src/components/layout/SectionHeader/SectionHeader.tsx +3 -1
- package/src/components/ui/Alert/Alert.mdx +12 -1
- package/src/components/ui/Alert/Alert.module.css +32 -3
- package/src/components/ui/Alert/Alert.spec.tsx +27 -0
- package/src/components/ui/Alert/Alert.stories.tsx +20 -0
- package/src/components/ui/Alert/Alert.tsx +8 -1
- package/src/components/ui/Avatar/Avatar.mdx +22 -2
- package/src/components/ui/Avatar/Avatar.module.css +29 -2
- package/src/components/ui/Avatar/Avatar.spec.tsx +26 -0
- package/src/components/ui/Avatar/Avatar.stories.tsx +23 -0
- package/src/components/ui/Avatar/Avatar.tsx +4 -2
- package/src/components/ui/Avatar/avatarTone.spec.ts +41 -0
- package/src/components/ui/Avatar/avatarTone.ts +22 -0
- package/src/components/ui/Badge/Badge.mdx +1 -1
- package/src/components/ui/Badge/Badge.module.css +2 -2
- package/src/components/ui/Badge/Badge.spec.tsx +8 -0
- package/src/components/ui/Collapsible/Collapsible.module.css +1 -2
- package/src/components/ui/NotificationBanner/NotificationBanner.mdx +27 -1
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +33 -3
- package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +35 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +25 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.tsx +15 -1
- package/src/components/ui/Separator/Separator.mdx +1 -0
- package/src/components/ui/Separator/Separator.module.css +6 -0
- package/src/components/ui/Separator/Separator.spec.tsx +19 -0
- package/src/components/ui/Separator/Separator.stories.tsx +15 -0
- package/src/components/ui/Table/Table.mdx +4 -1
- package/src/components/ui/Table/Table.module.css +7 -7
- package/src/components/ui/Table/Table.spec.tsx +11 -0
- package/src/components/ui/Tabs/Tabs.mdx +11 -5
- package/src/components/ui/Tabs/Tabs.module.css +23 -5
- package/src/components/ui/Tabs/Tabs.spec.tsx +39 -0
- package/src/components/ui/Tabs/Tabs.tsx +2 -1
- package/src/components/ui/Tabs/getNodeText.spec.tsx +23 -0
- package/src/components/ui/Tabs/getNodeText.ts +13 -0
- package/src/components/ui/Tooltip/Tooltip.mdx +6 -3
- package/src/components/ui/Tooltip/Tooltip.module.css +6 -2
- package/src/components/ui/Tooltip/Tooltip.spec.tsx +10 -0
- package/src/components/ui/Tooltip/Tooltip.stories.tsx +13 -0
- package/src/css/bdc.css +5 -2
- package/src/css/bdc.spec.ts +65 -0
- package/src/stories/DesignSystem/Borders.mdx +17 -9
- package/src/test/token-contract.spec.ts +1 -0
- package/src/web-components/globals.ts +10 -4
- package/src/web-components/index.ts +1 -0
- package/src/web-components/interaction/BdsAccordion.mdx +1 -1
- package/src/web-components/interaction/BdsAccordion.stories.tsx +1 -1
- package/src/web-components/interaction/BdsButton.mdx +1 -1
- package/src/web-components/interaction/BdsButton.stories.tsx +1 -1
- package/src/web-components/interaction/BdsCollapsible.mdx +1 -1
- package/src/web-components/interaction/BdsCollapsible.stories.tsx +1 -1
- package/src/web-components/interaction/BdsDialog.mdx +1 -1
- package/src/web-components/interaction/BdsDialog.stories.tsx +1 -1
- package/src/web-components/interaction/BdsDrawer.mdx +1 -1
- package/src/web-components/interaction/BdsDrawer.stories.tsx +1 -1
- package/src/web-components/interaction/BdsDropdownMenu.mdx +62 -6
- package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +32 -7
- package/src/web-components/interaction/BdsPopover.mdx +1 -1
- package/src/web-components/interaction/BdsPopover.stories.tsx +1 -1
- package/src/web-components/interaction/BdsSkipLink.mdx +1 -1
- package/src/web-components/interaction/BdsSkipLink.stories.tsx +1 -1
- package/src/web-components/interaction/BdsTabs.mdx +10 -1
- package/src/web-components/interaction/BdsTabs.stories.tsx +1 -1
- package/src/web-components/interaction/BdsToastProvider.mdx +1 -1
- package/src/web-components/interaction/BdsToastProvider.stories.tsx +1 -1
- package/src/web-components/interaction/BdsTooltip.mdx +13 -5
- package/src/web-components/interaction/BdsTooltip.stories.tsx +12 -12
- package/src/web-components/interaction/bds-dropdown-menu-group.ts +72 -0
- package/src/web-components/interaction/bds-dropdown-menu-item.ts +65 -17
- package/src/web-components/interaction/bds-dropdown-menu.spec.ts +135 -0
- package/src/web-components/interaction/bds-dropdown-menu.ts +5 -4
- package/src/web-components/interaction/bds-tab.spec.ts +34 -2
- package/src/web-components/interaction/bds-tab.ts +31 -6
- package/src/web-components/interaction/bds-tooltip.spec.ts +16 -0
- package/src/web-components/interaction/bds-tooltip.ts +10 -8
- package/src/web-components/interaction/form/BdsCheckbox.mdx +5 -1
- package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +5 -1
- package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsCombobox.mdx +5 -1
- package/src/web-components/interaction/form/BdsCombobox.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsFileInput.mdx +1 -1
- package/src/web-components/interaction/form/BdsFileInput.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsFormInput.mdx +8 -1
- package/src/web-components/interaction/form/BdsFormInput.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsNumberInput.mdx +5 -1
- package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsRadio.mdx +5 -1
- package/src/web-components/interaction/form/BdsRadio.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsRadioGroup.mdx +5 -1
- package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsSegmentedControl.mdx +1 -1
- package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsSelect.mdx +5 -1
- package/src/web-components/interaction/form/BdsSelect.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsSlider.mdx +5 -1
- package/src/web-components/interaction/form/BdsSlider.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsSwitch.mdx +5 -1
- package/src/web-components/interaction/form/BdsSwitch.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsTextarea.mdx +5 -1
- package/src/web-components/interaction/form/BdsTextarea.stories.tsx +1 -1
- package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +32 -3
- package/src/web-components/interaction/form/bds-checkbox-group.ts +33 -17
- package/src/web-components/interaction/form/bds-checkbox.ts +1 -0
- package/src/web-components/interaction/form/bds-combobox.ts +6 -16
- package/src/web-components/interaction/form/bds-form-input.ts +6 -16
- package/src/web-components/interaction/form/bds-number-input.ts +6 -16
- package/src/web-components/interaction/form/bds-radio-group.spec.ts +32 -3
- package/src/web-components/interaction/form/bds-radio-group.ts +26 -14
- package/src/web-components/interaction/form/bds-radio.ts +1 -0
- package/src/web-components/interaction/form/bds-select.ts +6 -16
- package/src/web-components/interaction/form/bds-slider.ts +5 -16
- package/src/web-components/interaction/form/bds-switch.ts +1 -0
- package/src/web-components/interaction/form/bds-textarea.ts +6 -16
- package/src/web-components/interaction/form/field-message.spec.ts +62 -0
- package/src/web-components/interaction/form/field-message.ts +55 -0
- package/src/web-components/layout/BdsGrid.mdx +1 -1
- package/src/web-components/layout/BdsGrid.stories.tsx +1 -1
- package/src/web-components/layout/BdsGridItem.mdx +1 -1
- package/src/web-components/layout/BdsGridItem.stories.tsx +1 -1
- package/src/web-components/ui/BdsAlert.mdx +20 -1
- package/src/web-components/ui/BdsAlert.stories.tsx +29 -3
- package/src/web-components/ui/BdsAvatar.mdx +20 -3
- package/src/web-components/ui/BdsAvatar.stories.tsx +25 -2
- package/src/web-components/ui/BdsBadge.mdx +2 -1
- package/src/web-components/ui/BdsBadge.stories.tsx +1 -1
- package/src/web-components/ui/BdsBreadcrumb.mdx +1 -1
- package/src/web-components/ui/BdsBreadcrumb.stories.tsx +1 -1
- package/src/web-components/ui/BdsButtonContainer.mdx +1 -1
- package/src/web-components/ui/BdsButtonContainer.stories.tsx +1 -1
- package/src/web-components/ui/BdsCalendar.mdx +1 -1
- package/src/web-components/ui/BdsCalendar.stories.tsx +1 -1
- package/src/web-components/ui/BdsCalendarRange.mdx +1 -1
- package/src/web-components/ui/BdsCalendarRange.stories.tsx +1 -1
- package/src/web-components/ui/BdsCard.mdx +1 -1
- package/src/web-components/ui/BdsCard.stories.tsx +1 -1
- package/src/web-components/ui/BdsCarousel.mdx +1 -1
- package/src/web-components/ui/BdsCarousel.stories.tsx +1 -1
- package/src/web-components/ui/BdsDescriptionList.mdx +1 -1
- package/src/web-components/ui/BdsDescriptionList.stories.tsx +1 -1
- package/src/web-components/ui/BdsIconWrapper.mdx +1 -1
- package/src/web-components/ui/BdsIconWrapper.stories.tsx +1 -1
- package/src/web-components/ui/BdsLink.mdx +1 -1
- package/src/web-components/ui/BdsLink.stories.tsx +1 -1
- package/src/web-components/ui/BdsLoading.mdx +1 -1
- package/src/web-components/ui/BdsLoading.stories.tsx +1 -1
- package/src/web-components/ui/BdsNotificationBanner.mdx +11 -2
- package/src/web-components/ui/BdsNotificationBanner.stories.tsx +29 -2
- package/src/web-components/ui/BdsPagination.mdx +1 -1
- package/src/web-components/ui/BdsPagination.stories.tsx +1 -1
- package/src/web-components/ui/BdsProgress.mdx +1 -1
- package/src/web-components/ui/BdsProgress.stories.tsx +1 -1
- package/src/web-components/ui/BdsProgressCircle.mdx +1 -1
- package/src/web-components/ui/BdsProgressCircle.stories.tsx +1 -1
- package/src/web-components/ui/BdsRating.mdx +5 -2
- package/src/web-components/ui/BdsRating.stories.tsx +1 -1
- package/src/web-components/ui/BdsSectionHeader.mdx +18 -6
- package/src/web-components/ui/BdsSectionHeader.stories.tsx +10 -2
- package/src/web-components/ui/BdsSeparator.mdx +2 -1
- package/src/web-components/ui/BdsSeparator.stories.tsx +13 -1
- package/src/web-components/ui/BdsSkeleton.mdx +1 -1
- package/src/web-components/ui/BdsSkeleton.stories.tsx +1 -1
- package/src/web-components/ui/BdsTable.mdx +5 -1
- package/src/web-components/ui/BdsTable.stories.tsx +1 -1
- package/src/web-components/ui/BdsTypography.mdx +1 -1
- package/src/web-components/ui/BdsTypography.stories.tsx +1 -1
- package/src/web-components/ui/bds-alert.spec.ts +34 -0
- package/src/web-components/ui/bds-alert.ts +36 -3
- package/src/web-components/ui/bds-avatar.spec.ts +47 -0
- package/src/web-components/ui/bds-avatar.ts +36 -3
- package/src/web-components/ui/bds-badge.spec.ts +6 -0
- package/src/web-components/ui/bds-badge.ts +2 -2
- package/src/web-components/ui/bds-notification-banner.spec.ts +52 -0
- package/src/web-components/ui/bds-notification-banner.ts +55 -3
- package/src/web-components/ui/bds-rating.spec.ts +7 -0
- package/src/web-components/ui/bds-rating.ts +6 -0
- package/src/web-components/ui/bds-section-header.spec.ts +36 -3
- package/src/web-components/ui/bds-section-header.ts +32 -10
- package/src/web-components/ui/bds-separator.spec.ts +8 -0
- package/src/web-components/ui/bds-separator.ts +7 -0
- package/src/web-components/ui/bds-table.spec.ts +11 -0
- package/src/web-components/ui/bds-table.ts +5 -5
- package/dist/web-components/chunk-QZAIRJ75.js +0 -111
|
@@ -35,6 +35,13 @@ Full-width banner for page-level system messages. Sits at the top of the page or
|
|
|
35
35
|
### Dismissible
|
|
36
36
|
<Canvas of={Stories.Dismissible} />
|
|
37
37
|
|
|
38
|
+
### With icon
|
|
39
|
+
<Canvas of={Stories.WithIcon} />
|
|
40
|
+
|
|
41
|
+
### Gradient
|
|
42
|
+
`appearance="gradient"` fades the variant tint to the surface and adds a shadow. The default appearance is unchanged.
|
|
43
|
+
<Canvas of={Stories.Gradient} />
|
|
44
|
+
|
|
38
45
|
## Props
|
|
39
46
|
|
|
40
47
|
> **HTML attributes** — all standard HTML attributes for the root element are forwarded via `{...rest}` (`id`, `className`, `style`, `data-*`, `aria-*`, event handlers, etc.). Component-controlled attributes take precedence.
|
|
@@ -55,6 +62,13 @@ The override surface is **layered**. Setting a public token on the banner (or an
|
|
|
55
62
|
<tr><td>`--banner_bg`</td><td>per variant</td><td>Banner background colour. Set to theme every variant at once.</td></tr>
|
|
56
63
|
<tr><td>`--banner_text`</td><td>per variant</td><td>Banner text colour.</td></tr>
|
|
57
64
|
<tr><td>`--banner_border`</td><td>per variant</td><td>Banner block-end border colour.</td></tr>
|
|
65
|
+
<tr><td>`--banner_border-width`</td><td>`3px`</td><td>Width of the block-end accent line.</td></tr>
|
|
66
|
+
<tr><td>`--banner_gradient`</td><td>`linear-gradient(100deg, transparent 35%, var(--bds-color_bg--strong, var(--bds-color_bg)) 100%)`</td><td>Gradient laid over the variant background. Only used with `appearance="gradient"`.</td></tr>
|
|
67
|
+
<tr><td>`--banner_shadow`</td><td>`var(--bds-shadow_s)`</td><td>Box shadow. Only used with `appearance="gradient"`; the default appearance has none.</td></tr>
|
|
68
|
+
<tr><td>`--banner_accent--info`</td><td>`var(--bds-color_on-blue--subtle)`</td><td>Accent line colour of the info variant with `appearance="gradient"`. `--banner_border--info` and `--banner_border` win over it.</td></tr>
|
|
69
|
+
<tr><td>`--banner_accent--success`</td><td>`var(--bds-color_on-success--subtle)`</td><td>Same, success variant.</td></tr>
|
|
70
|
+
<tr><td>`--banner_accent--warning`</td><td>`var(--bds-color_warning)`</td><td>Same, warning variant.</td></tr>
|
|
71
|
+
<tr><td>`--banner_accent--error`</td><td>`var(--bds-color_error)`</td><td>Same, error variant.</td></tr>
|
|
58
72
|
<tr><td>`--banner_dismiss-radius`</td><td>`var(--bds-border_radius--circle)`</td><td>Dismiss button border radius.</td></tr>
|
|
59
73
|
<tr><td>`--banner_dismiss-border`</td><td>`none`</td><td>Dismiss button border.</td></tr>
|
|
60
74
|
<tr><td>`--banner_dismiss-opacity--hover`</td><td>`0.7`</td><td>Dismiss button opacity on hover.</td></tr>
|
|
@@ -76,7 +90,19 @@ The override surface is **layered**. Setting a public token on the banner (or an
|
|
|
76
90
|
</tbody>
|
|
77
91
|
</table>
|
|
78
92
|
|
|
79
|
-
|
|
93
|
+
Each variant's actual default resolution:
|
|
94
|
+
|
|
95
|
+
<table>
|
|
96
|
+
<thead>
|
|
97
|
+
<tr><th>Variant</th><th>Background default</th><th>Text default</th><th>Border default</th></tr>
|
|
98
|
+
</thead>
|
|
99
|
+
<tbody>
|
|
100
|
+
<tr><td>`info`</td><td>`--bds-color_blue--subtle`</td><td>`--bds-color_on-blue--subtle`</td><td>`--bds-color_blue`</td></tr>
|
|
101
|
+
<tr><td>`success`</td><td>`--bds-color_success--subtle`</td><td>`--bds-color_on-success--subtle`</td><td>`--bds-color_success`</td></tr>
|
|
102
|
+
<tr><td>`warning`</td><td>`--bds-color_orange--subtle`</td><td>`--bds-color_on-orange--subtle`</td><td>`--bds-color_warning`</td></tr>
|
|
103
|
+
<tr><td>`error`</td><td>`--bds-color_error--subtle`</td><td>`--bds-color_on-error--subtle`</td><td>`--bds-color_error`</td></tr>
|
|
104
|
+
</tbody>
|
|
105
|
+
</table>
|
|
80
106
|
|
|
81
107
|
|
|
82
108
|
## Accessibility
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
win over per-variant defaults. Variants write to the inner --banner_*--variant slot. */
|
|
10
10
|
background-color: var(--banner_bg, var(--banner_bg--variant, var(--bds-color_blue--subtle)));
|
|
11
11
|
color: var(--banner_text, var(--banner_text--variant, var(--bds-color_on-blue--subtle)));
|
|
12
|
-
border-block-end: 3px solid var(--banner_border, var(--banner_border--variant, var(--bds-color_blue)));
|
|
12
|
+
border-block-end: var(--banner_border-width, 3px) solid var(--banner_border, var(--banner_border--variant, var(--bds-color_blue)));
|
|
13
13
|
font-size: var(--bds-font_size--body);
|
|
14
14
|
line-height: var(--bds-font_line-height--body);
|
|
15
15
|
inline-size: 100%;
|
|
@@ -34,11 +34,41 @@
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
.--variant_error {
|
|
37
|
-
--banner_bg--variant: var(--banner_bg--error,
|
|
38
|
-
--banner_text--variant: var(--banner_text--error, var(--bds-color_on-
|
|
37
|
+
--banner_bg--variant: var(--banner_bg--error, var(--bds-color_error--subtle));
|
|
38
|
+
--banner_text--variant: var(--banner_text--error, var(--bds-color_on-error--subtle));
|
|
39
39
|
--banner_border--variant: var(--banner_border--error, var(--bds-color_error));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/* Gradient appearance: the variant background stays the base colour and a
|
|
43
|
+
gradient fades it to the surface. The accent line uses a per-variant
|
|
44
|
+
accent slot; --banner_border still wins over it. */
|
|
45
|
+
.--appearance_gradient {
|
|
46
|
+
background-image: var(--banner_gradient, linear-gradient(100deg, transparent 35%, var(--bds-color_bg--strong, var(--bds-color_bg)) 100%));
|
|
47
|
+
box-shadow: var(--banner_shadow, var(--bds-shadow_s));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.--appearance_gradient.--variant_info {
|
|
51
|
+
--banner_border--variant: var(--banner_border--info, var(--banner_accent--info, var(--bds-color_on-blue--subtle)));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.--appearance_gradient.--variant_success {
|
|
55
|
+
--banner_border--variant: var(--banner_border--success, var(--banner_accent--success, var(--bds-color_on-success--subtle)));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.--appearance_gradient.--variant_warning {
|
|
59
|
+
--banner_border--variant: var(--banner_border--warning, var(--banner_accent--warning, var(--bds-color_warning)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.--appearance_gradient.--variant_error {
|
|
63
|
+
--banner_border--variant: var(--banner_border--error, var(--banner_accent--error, var(--bds-color_error)));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.icon {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
42
72
|
.content {
|
|
43
73
|
flex: 1;
|
|
44
74
|
}
|
|
@@ -59,4 +59,39 @@ describe('NotificationBanner', () => {
|
|
|
59
59
|
expect(dismissRule).toMatch(/min-inline-size:\s*var\(--banner_dismiss-size,\s*2\.75rem\)/);
|
|
60
60
|
expect(dismissRule).toMatch(/min-block-size:\s*var\(--banner_dismiss-size,\s*2\.75rem\)/);
|
|
61
61
|
});
|
|
62
|
+
|
|
63
|
+
it('adds the gradient appearance class with appearance="gradient"', () => {
|
|
64
|
+
render(<NotificationBanner appearance="gradient">Msg</NotificationBanner>);
|
|
65
|
+
expect(screen.getByRole('status').className).toMatch(/appearance_gradient/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('does not add the gradient class by default', () => {
|
|
69
|
+
render(<NotificationBanner>Msg</NotificationBanner>);
|
|
70
|
+
expect(screen.getByRole('status').className).not.toMatch(/appearance_gradient/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('renders an icon with aria-hidden', () => {
|
|
74
|
+
render(<NotificationBanner icon={<svg data-testid="icon" />}>Msg</NotificationBanner>);
|
|
75
|
+
expect(screen.getByTestId('icon').parentElement).toHaveAttribute('aria-hidden', 'true');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('does not render an icon wrapper without icon', () => {
|
|
79
|
+
render(<NotificationBanner>Msg</NotificationBanner>);
|
|
80
|
+
expect(screen.getByRole('status').querySelector('[aria-hidden="true"]')).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('wires the gradient hooks in the CSS', () => {
|
|
84
|
+
expect(cssSource).toMatch(/background-image:\s*var\(--banner_gradient,\s*linear-gradient\(100deg/);
|
|
85
|
+
expect(cssSource).toMatch(/box-shadow:\s*var\(--banner_shadow,\s*var\(--bds-shadow_s\)\)/);
|
|
86
|
+
expect(cssSource).toMatch(/border-block-end:\s*var\(--banner_border-width,\s*3px\)/);
|
|
87
|
+
['info', 'success', 'warning', 'error'].forEach(v => {
|
|
88
|
+
expect(cssSource).toContain(`--banner_accent--${v}`);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('paints the error variant with the error subtle pair, like the other variants', () => {
|
|
93
|
+
expect(cssSource).toMatch(/\.--variant_error \{[^}]*--banner_bg--variant:\s*var\(--banner_bg--error,\s*var\(--bds-color_error--subtle\)\)/);
|
|
94
|
+
expect(cssSource).toMatch(/\.--variant_error \{[^}]*--banner_text--variant:\s*var\(--banner_text--error,\s*var\(--bds-color_on-error--subtle\)\)/);
|
|
95
|
+
expect(cssSource).not.toContain('rgb(from var(--bds-color_error)');
|
|
96
|
+
});
|
|
62
97
|
});
|
|
@@ -2,9 +2,19 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { NotificationBanner } from './NotificationBanner';
|
|
3
3
|
import { Button } from '../../interaction/Button/Button';
|
|
4
4
|
|
|
5
|
+
const icons = {
|
|
6
|
+
info: <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4M12 8h.01" /></svg>,
|
|
7
|
+
success: <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><path d="m8 12 3 3 5-6" /></svg>,
|
|
8
|
+
warning: <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3 2 21h20L12 3Z" /><path d="M12 10v5M12 18h.01" /></svg>,
|
|
9
|
+
error: <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 7v6M12 16h.01" /></svg>,
|
|
10
|
+
};
|
|
11
|
+
|
|
5
12
|
const meta = {
|
|
6
13
|
title: 'React/UI/NotificationBanner',
|
|
7
14
|
component: NotificationBanner,
|
|
15
|
+
argTypes: {
|
|
16
|
+
appearance: { control: 'select', options: ['default', 'gradient'] },
|
|
17
|
+
},
|
|
8
18
|
} satisfies Meta<typeof NotificationBanner>;
|
|
9
19
|
|
|
10
20
|
export default meta;
|
|
@@ -28,3 +38,18 @@ export const Dismissible: Story = {
|
|
|
28
38
|
onDismiss: () => alert('dismissed'),
|
|
29
39
|
},
|
|
30
40
|
};
|
|
41
|
+
|
|
42
|
+
export const WithIcon: Story = {
|
|
43
|
+
args: { variant: 'info', icon: icons.info, children: 'A new version is available. Refresh to update.' },
|
|
44
|
+
};
|
|
45
|
+
export const Gradient: Story = {
|
|
46
|
+
render: () => (
|
|
47
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
48
|
+
{(['info', 'success', 'warning', 'error'] as const).map(v => (
|
|
49
|
+
<NotificationBanner key={v} variant={v} appearance="gradient" icon={icons[v]}>
|
|
50
|
+
This is a {v} notification banner.
|
|
51
|
+
</NotificationBanner>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
@@ -5,6 +5,8 @@ import type { WithClassName } from '../../../types';
|
|
|
5
5
|
|
|
6
6
|
interface NotificationBannerProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
|
|
7
7
|
variant?: 'info' | 'success' | 'warning' | 'error';
|
|
8
|
+
appearance?: 'default' | 'gradient';
|
|
9
|
+
icon?: ReactNode;
|
|
8
10
|
children: ReactNode;
|
|
9
11
|
action?: ReactNode;
|
|
10
12
|
onDismiss?: () => void;
|
|
@@ -12,6 +14,8 @@ interface NotificationBannerProps extends WithClassName, HTMLAttributes<HTMLDivE
|
|
|
12
14
|
|
|
13
15
|
export function NotificationBanner({
|
|
14
16
|
variant = 'info',
|
|
17
|
+
appearance = 'default',
|
|
18
|
+
icon,
|
|
15
19
|
children,
|
|
16
20
|
action,
|
|
17
21
|
onDismiss,
|
|
@@ -26,8 +30,18 @@ export function NotificationBanner({
|
|
|
26
30
|
role={isUrgent ? 'alert' : 'status'}
|
|
27
31
|
aria-live={isUrgent ? 'assertive' : 'polite'}
|
|
28
32
|
aria-atomic="true"
|
|
29
|
-
className={cn(
|
|
33
|
+
className={cn(
|
|
34
|
+
css.banner,
|
|
35
|
+
css[`--variant_${variant}`],
|
|
36
|
+
appearance === 'gradient' && css['--appearance_gradient'],
|
|
37
|
+
className,
|
|
38
|
+
)}
|
|
30
39
|
>
|
|
40
|
+
{icon && (
|
|
41
|
+
<span className={css.icon} aria-hidden="true">
|
|
42
|
+
{icon}
|
|
43
|
+
</span>
|
|
44
|
+
)}
|
|
31
45
|
<div className={css.content}>{children}</div>
|
|
32
46
|
{action && <div className={css.action}>{action}</div>}
|
|
33
47
|
{onDismiss && (
|
|
@@ -35,6 +35,7 @@ Visual divider between content sections. Renders a semantic `<hr>` (horizontal)
|
|
|
35
35
|
<tbody>
|
|
36
36
|
<tr><td>`--separator_color`</td><td>`var(--bds-color_on-bg--as-surface)`</td><td>Line colour. A separator is not text, so it takes the non-text member of the colour set (≥ 3:1) rather than the text colour. Falls back to `--bds-color_on-bg--border` and then `--bds-color_on-bg` on foundations older than 2.3.0.</td></tr>
|
|
37
37
|
<tr><td>`--separator_thickness`</td><td>`1px`</td><td>Line thickness (height for horizontal, width for vertical)</td></tr>
|
|
38
|
+
<tr><td>`--separator_spacing`</td><td>`var(--bds-space_s)` (vertical), `0` (horizontal)</td><td>Space around the line, without margin: padding on the separator with the colour clipped to the line. Vertical: inline space on both sides; horizontal: block space above and below.</td></tr>
|
|
38
39
|
<tr><td>`--separator_length`</td><td>`1em`</td><td>Minimum height of the vertical separator when rendered outside a flex/grid container. Inside a flex or grid container the separator still stretches to fill the cross axis (`align-self: stretch`) — this token only sets a floor for standalone usage.</td></tr>
|
|
39
40
|
</tbody>
|
|
40
41
|
</table>
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
display: block;
|
|
9
9
|
inline-size: 100%;
|
|
10
10
|
block-size: var(--separator_thickness, 1px);
|
|
11
|
+
box-sizing: content-box;
|
|
12
|
+
padding-block: var(--separator_spacing, 0);
|
|
13
|
+
background-clip: content-box;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
.separator.--vertical {
|
|
@@ -15,5 +18,8 @@
|
|
|
15
18
|
inline-size: var(--separator_thickness, 1px);
|
|
16
19
|
min-block-size: var(--separator_length, 1em);
|
|
17
20
|
align-self: stretch;
|
|
21
|
+
box-sizing: content-box;
|
|
22
|
+
padding-inline: var(--separator_spacing, var(--bds-space_s));
|
|
23
|
+
background-clip: content-box;
|
|
18
24
|
}
|
|
19
25
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
2
4
|
import { Separator } from './Separator';
|
|
3
5
|
|
|
6
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/ui/Separator/Separator.module.css'), 'utf8');
|
|
7
|
+
|
|
4
8
|
describe('Separator', () => {
|
|
5
9
|
it('renders as an hr element by default', () => {
|
|
6
10
|
const { container } = render(<Separator />);
|
|
@@ -23,6 +27,21 @@ describe('Separator', () => {
|
|
|
23
27
|
expect(screen.getByRole('separator')).toHaveAttribute('aria-orientation', 'vertical');
|
|
24
28
|
});
|
|
25
29
|
|
|
30
|
+
it('spaces the vertical line through --separator_spacing without a margin', () => {
|
|
31
|
+
const vertical = css.slice(css.indexOf('.separator.--vertical'));
|
|
32
|
+
expect(vertical).toContain('box-sizing: content-box');
|
|
33
|
+
expect(vertical).toContain('padding-inline: var(--separator_spacing, var(--bds-space_s))');
|
|
34
|
+
expect(vertical).toContain('background-clip: content-box');
|
|
35
|
+
expect(css).not.toMatch(/margin/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('spaces the horizontal line through --separator_spacing, 0 by default', () => {
|
|
39
|
+
const horizontal = css.slice(css.indexOf('.separator.--horizontal'), css.indexOf('.separator.--vertical'));
|
|
40
|
+
expect(horizontal).toContain('box-sizing: content-box');
|
|
41
|
+
expect(horizontal).toContain('padding-block: var(--separator_spacing, 0)');
|
|
42
|
+
expect(horizontal).toContain('background-clip: content-box');
|
|
43
|
+
});
|
|
44
|
+
|
|
26
45
|
it('accepts a custom className', () => {
|
|
27
46
|
const { container } = render(<Separator className="custom" />);
|
|
28
47
|
expect(container.querySelector('hr')).toHaveClass('custom');
|
|
@@ -37,3 +37,18 @@ export const Vertical: Story = {
|
|
|
37
37
|
),
|
|
38
38
|
],
|
|
39
39
|
};
|
|
40
|
+
|
|
41
|
+
export const BetweenLinks: Story = {
|
|
42
|
+
args: { orientation: 'vertical' },
|
|
43
|
+
decorators: [
|
|
44
|
+
Story => (
|
|
45
|
+
<nav aria-label="Example" style={{ display: 'flex', alignItems: 'center' }}>
|
|
46
|
+
<a href="#one">Home</a>
|
|
47
|
+
<Story />
|
|
48
|
+
<a href="#two">Pricing</a>
|
|
49
|
+
<Story />
|
|
50
|
+
<a href="#three">Contact</a>
|
|
51
|
+
</nav>
|
|
52
|
+
),
|
|
53
|
+
],
|
|
54
|
+
};
|
|
@@ -46,7 +46,10 @@ Data table with optional sortable columns and caption.
|
|
|
46
46
|
<tr><td>`--table_shadow`</td><td>`var(--bdc-outline_shadow)`</td><td>Container frame shadow (appended to the inset border box-shadow)</td></tr>
|
|
47
47
|
<tr><td>`--table_cell-padding`</td><td>`var(--bds-space_s) var(--bds-space_m)`</td><td>Padding shared by the caption, header cells and body cells</td></tr>
|
|
48
48
|
<tr><td>`--table_font-size`</td><td>`var(--bds-font_size--body)`</td><td>Body cell font size</td></tr>
|
|
49
|
-
<tr><td>`--table_header-font-size`</td><td>`var(--bds-font_size--body
|
|
49
|
+
<tr><td>`--table_header-font-size`</td><td>`var(--bds-font_size--body)`</td><td>Header cell / sort button font size</td></tr>
|
|
50
|
+
<tr><td>`--table_header-font-weight`</td><td>`var(--bds-font_weight--bold)`</td><td>Header cell / sort button font weight</td></tr>
|
|
51
|
+
<tr><td>`--table_header-color`</td><td>`var(--bds-color_on-bg--subtle)`</td><td>Header cell / sort button text colour</td></tr>
|
|
52
|
+
<tr><td>`--table_header-bg`</td><td>`var(--bds-color_bg--subtle)`</td><td>Header row background</td></tr>
|
|
50
53
|
<tr><td>`--table_sortBtn-color--hover`</td><td>`var(--bds-color_on-bg)`</td><td>Sort button text colour on hover</td></tr>
|
|
51
54
|
<tr><td>`--table_row-bg--hover`</td><td>`var(--bds-color_bg--subtle)`</td><td>Table row background on hover</td></tr>
|
|
52
55
|
</tbody>
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.thead {
|
|
34
|
-
background-color: var(--bds-color_bg--subtle);
|
|
34
|
+
background-color: var(--table_header-bg, var(--bds-color_bg--subtle));
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.th {
|
|
38
38
|
padding: var(--table_cell-padding, var(--bds-space_s) var(--bds-space_m));
|
|
39
39
|
text-align: start;
|
|
40
|
-
font-weight: var(--bds-font_weight--
|
|
41
|
-
font-size: var(--table_header-font-size, var(--bds-font_size--body
|
|
42
|
-
color: var(--bds-color_on-bg--subtle);
|
|
40
|
+
font-weight: var(--table_header-font-weight, var(--bds-font_weight--bold));
|
|
41
|
+
font-size: var(--table_header-font-size, var(--bds-font_size--body));
|
|
42
|
+
color: var(--table_header-color, var(--bds-color_on-bg--subtle));
|
|
43
43
|
white-space: nowrap;
|
|
44
44
|
border-block-end: 1px solid var(--table_divider-color, var(--bds-color_on-bg--as-surface, var(--bds-color_on-bg--border, var(--bds-color_bg--subtle))));
|
|
45
45
|
}
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
padding: var(--table_cell-padding, var(--bds-space_s) var(--bds-space_m));
|
|
57
57
|
inline-size: 100%;
|
|
58
58
|
cursor: pointer;
|
|
59
|
-
color: var(--bds-color_on-bg--subtle);
|
|
60
|
-
font-weight: var(--bds-font_weight--
|
|
61
|
-
font-size: var(--table_header-font-size, var(--bds-font_size--body
|
|
59
|
+
color: var(--table_header-color, var(--bds-color_on-bg--subtle));
|
|
60
|
+
font-weight: var(--table_header-font-weight, var(--bds-font_weight--bold));
|
|
61
|
+
font-size: var(--table_header-font-size, var(--bds-font_size--body));
|
|
62
62
|
transition: var(--bds-animation_transition);
|
|
63
63
|
box-sizing: border-box;
|
|
64
64
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
3
5
|
import { Table } from './Table';
|
|
4
6
|
|
|
7
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/ui/Table/Table.module.css'), 'utf8');
|
|
8
|
+
|
|
5
9
|
const columns = [
|
|
6
10
|
{ key: 'name', header: 'Name' },
|
|
7
11
|
{ key: 'role', header: 'Role', sortable: true },
|
|
@@ -122,4 +126,11 @@ describe('Table', () => {
|
|
|
122
126
|
render(<Table columns={columns} rows={rows} caption="Team members" aria-label="Custom label" />);
|
|
123
127
|
expect(screen.getByRole('region', { name: 'Custom label' })).toBeInTheDocument();
|
|
124
128
|
});
|
|
129
|
+
|
|
130
|
+
it('styles the header and the sort button through the --table_header-* hooks', () => {
|
|
131
|
+
expect(css).toContain('background-color: var(--table_header-bg, var(--bds-color_bg--subtle))');
|
|
132
|
+
expect(css.match(/var\(--table_header-font-weight, var\(--bds-font_weight--bold\)\)/g)).toHaveLength(2);
|
|
133
|
+
expect(css.match(/var\(--table_header-font-size, var\(--bds-font_size--body\)\)/g)).toHaveLength(2);
|
|
134
|
+
expect(css.match(/var\(--table_header-color, var\(--bds-color_on-bg--subtle\)\)/g)).toHaveLength(2);
|
|
135
|
+
});
|
|
125
136
|
});
|
|
@@ -48,6 +48,8 @@ The override surface is **layered**. Setting `--tab_color` / `--tab_border-color
|
|
|
48
48
|
<tbody>
|
|
49
49
|
<tr><td>`--tab_color`</td><td>per state</td><td>Tab text colour. Set to theme every state at once.</td></tr>
|
|
50
50
|
<tr><td>`--tab_border-color`</td><td>per state</td><td>Tab block-end border colour.</td></tr>
|
|
51
|
+
<tr><td>`--tab_font-weight`</td><td>per state</td><td>Tab font weight. Set to theme every state at once.</td></tr>
|
|
52
|
+
<tr><td>`--tab_indicator-width`</td><td>`3px`</td><td>Thickness of the block-end indicator line.</td></tr>
|
|
51
53
|
</tbody>
|
|
52
54
|
</table>
|
|
53
55
|
|
|
@@ -55,16 +57,16 @@ The override surface is **layered**. Setting `--tab_color` / `--tab_border-color
|
|
|
55
57
|
|
|
56
58
|
<table>
|
|
57
59
|
<thead>
|
|
58
|
-
<tr><th>State</th><th>Text</th><th>Border</th></tr>
|
|
60
|
+
<tr><th>State</th><th>Text</th><th>Border</th><th>Font weight</th></tr>
|
|
59
61
|
</thead>
|
|
60
62
|
<tbody>
|
|
61
|
-
<tr><td>resting</td><td
|
|
62
|
-
<tr><td>hover</td><td>`--tab_color--hover`</td><td>—</td></tr>
|
|
63
|
-
<tr><td>active</td><td>`--tab_color--active`</td><td>`--tab_border-color--active`</td></tr>
|
|
63
|
+
<tr><td>resting</td><td>`--tab_color--inactive`</td><td>(`transparent`)</td><td>`--tab_font-weight--inactive`</td></tr>
|
|
64
|
+
<tr><td>hover</td><td>`--tab_color--hover`</td><td>—</td><td>—</td></tr>
|
|
65
|
+
<tr><td>active</td><td>`--tab_color--active`</td><td>`--tab_border-color--active`</td><td>`--tab_font-weight--active`</td></tr>
|
|
64
66
|
</tbody>
|
|
65
67
|
</table>
|
|
66
68
|
|
|
67
|
-
Defaults:
|
|
69
|
+
Defaults: resting text is `--bds-color_on-bg--muted` (falls back to `--bds-color_on-bg`) at `--bds-font_weight--medium`; hover and active text are `--bds-color_on-bg`; active is `--bds-font_weight--bold` with a `--bds-color_interactive` indicator.
|
|
68
70
|
|
|
69
71
|
### Spacing tokens
|
|
70
72
|
|
|
@@ -80,6 +82,10 @@ Defaults: hover/active text resolves to `--bds-color_interactive_on-bg`; active
|
|
|
80
82
|
</tbody>
|
|
81
83
|
</table>
|
|
82
84
|
|
|
85
|
+
## Layout stability
|
|
86
|
+
|
|
87
|
+
The label sits in a `<span data-label>` with a hidden `::after` that repeats the text at the active weight (`--tab_font-weight--active`), so the tab keeps its bold width and selecting a tab does not shift the row. The text comes from `TabItem.label` (strings, numbers and nested elements); the hidden copy is invisible and not read by screen readers.
|
|
88
|
+
|
|
83
89
|
## Accessibility
|
|
84
90
|
|
|
85
91
|
- Implements ARIA tabs pattern: `role="tablist"`, `role="tab"`, `role="tabpanel"`
|
|
@@ -17,19 +17,37 @@
|
|
|
17
17
|
padding: var(--tab_padding, var(--bds-space_s) var(--bds-space_m));
|
|
18
18
|
font-family: var(--bds-font_family--body);
|
|
19
19
|
font-size: var(--bds-font_size--body);
|
|
20
|
-
font-weight: var(--bds-font_weight--
|
|
20
|
+
font-weight: var(--tab_font-weight, var(--tab_font-weight--state, var(--tab_font-weight--inactive, var(--bds-font_weight--medium))));
|
|
21
21
|
|
|
22
22
|
/* Layered cascade: public overrides (--tab_color, --tab_border-color) win over
|
|
23
23
|
per-state defaults. Active/hover write to the inner --tab_*--state slot. */
|
|
24
|
-
color: var(--tab_color, var(--tab_color--state, var(--bds-color_on-bg)));
|
|
25
|
-
border-block-end:
|
|
24
|
+
color: var(--tab_color, var(--tab_color--state, var(--tab_color--inactive, var(--bds-color_on-bg--muted, var(--bds-color_on-bg)))));
|
|
25
|
+
border-block-end: var(--tab_indicator-width, 3px) solid var(--tab_border-color, var(--tab_border-color--state, transparent));
|
|
26
26
|
margin-block-end: -1px;
|
|
27
27
|
cursor: pointer;
|
|
28
28
|
transition: var(--bds-animation_transition);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/* Reserves the width of the bold label so selecting a tab does not shift the row.
|
|
32
|
+
The hidden ::after repeats the label at the active weight in the same grid cell:
|
|
33
|
+
it adds width but no height and stays out of the accessibility tree. */
|
|
34
|
+
.label {
|
|
35
|
+
display: inline-grid;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.label::after {
|
|
39
|
+
content: attr(data-label);
|
|
40
|
+
grid-area: 1 / 1;
|
|
41
|
+
font-weight: var(--tab_font-weight, var(--tab_font-weight--active, var(--bds-font_weight--bold)));
|
|
42
|
+
visibility: hidden;
|
|
43
|
+
pointer-events: none;
|
|
44
|
+
block-size: 0;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
.tab.--active {
|
|
32
|
-
--tab_color--state: var(--tab_color--active, var(--bds-
|
|
49
|
+
--tab_color--state: var(--tab_color--active, var(--bds-color_on-bg));
|
|
50
|
+
--tab_font-weight--state: var(--tab_font-weight--active, var(--bds-font_weight--bold));
|
|
33
51
|
--tab_border-color--state: var(--tab_border-color--active, var(--bds-color_interactive));
|
|
34
52
|
}
|
|
35
53
|
|
|
@@ -46,7 +64,7 @@
|
|
|
46
64
|
|
|
47
65
|
@media (hover: hover) and (pointer: fine) {
|
|
48
66
|
.tab:not(:disabled, .--active):hover {
|
|
49
|
-
--tab_color--state: var(--tab_color--hover, var(--bds-
|
|
67
|
+
--tab_color--state: var(--tab_color--hover, var(--bds-color_on-bg));
|
|
50
68
|
}
|
|
51
69
|
}
|
|
52
70
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
1
3
|
import { render, screen } from '@testing-library/react';
|
|
2
4
|
import userEvent from '@testing-library/user-event';
|
|
3
5
|
import { Tabs } from './Tabs';
|
|
@@ -108,4 +110,41 @@ describe('Tabs', () => {
|
|
|
108
110
|
render(<Tabs tabs={tabs} />);
|
|
109
111
|
expect(screen.getByText('First panel').closest('[role="tabpanel"]')).toHaveAttribute('tabindex', '0');
|
|
110
112
|
});
|
|
113
|
+
|
|
114
|
+
it('wraps the label with a data-label that reserves the bold width', () => {
|
|
115
|
+
render(<Tabs tabs={[{ id: 'a', label: <>Inbox <em>4</em></>, content: null }]} />);
|
|
116
|
+
const label = screen.getByRole('tab').querySelector('span');
|
|
117
|
+
expect(label).toHaveAttribute('data-label', 'Inbox 4');
|
|
118
|
+
expect(label).toHaveTextContent('Inbox 4');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('styling hooks', () => {
|
|
122
|
+
it('reserves the active font weight through a hidden ::after', () => {
|
|
123
|
+
const rules = readFileSync(resolve(process.cwd(), 'src/components/ui/Tabs/Tabs.module.css'), 'utf8');
|
|
124
|
+
expect(rules).toMatch(/\.label\s*\{\s*display:\s*inline-grid;/);
|
|
125
|
+
expect(rules).toMatch(/\.label::after\s*\{[^}]*content:\s*attr\(data-label\)/);
|
|
126
|
+
expect(rules).toMatch(/\.label::after\s*\{[^}]*font-weight:\s*var\(--tab_font-weight,\s*var\(--tab_font-weight--active,\s*var\(--bds-font_weight--bold\)\)\)/);
|
|
127
|
+
expect(rules).toMatch(/\.label::after\s*\{[^}]*visibility:\s*hidden/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/ui/Tabs/Tabs.module.css'), 'utf8');
|
|
131
|
+
|
|
132
|
+
it('styles the resting tab muted and medium', () => {
|
|
133
|
+
expect(css).toMatch(/color:\s*var\(--tab_color,\s*var\(--tab_color--state,\s*var\(--tab_color--inactive,\s*var\(--bds-color_on-bg--muted,\s*var\(--bds-color_on-bg\)\)\)\)\)/);
|
|
134
|
+
expect(css).toMatch(/font-weight:\s*var\(--tab_font-weight,\s*var\(--tab_font-weight--state,\s*var\(--tab_font-weight--inactive,\s*var\(--bds-font_weight--medium\)\)\)\)/);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('styles the active tab in the full text colour and bold', () => {
|
|
138
|
+
expect(css).toMatch(/--tab_color--state:\s*var\(--tab_color--active,\s*var\(--bds-color_on-bg\)\)/);
|
|
139
|
+
expect(css).toMatch(/--tab_font-weight--state:\s*var\(--tab_font-weight--active,\s*var\(--bds-font_weight--bold\)\)/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('sizes the indicator through --tab_indicator-width', () => {
|
|
143
|
+
expect(css).toMatch(/border-block-end:\s*var\(--tab_indicator-width,\s*3px\) solid/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('shows the full text colour on hover', () => {
|
|
147
|
+
expect(css).toMatch(/--tab_color--state:\s*var\(--tab_color--hover,\s*var\(--bds-color_on-bg\)\)/);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
111
150
|
});
|
|
@@ -2,6 +2,7 @@ import { HTMLAttributes, KeyboardEvent, ReactNode, useId, useRef, useState } fro
|
|
|
2
2
|
import css from './Tabs.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
4
|
import type { WithClassName } from '../../../types';
|
|
5
|
+
import { getNodeText } from './getNodeText';
|
|
5
6
|
|
|
6
7
|
export interface TabItem {
|
|
7
8
|
id: string;
|
|
@@ -74,7 +75,7 @@ export function Tabs({ tabs, defaultTab, className, ...rest }: Readonly<TabsProp
|
|
|
74
75
|
onClick={() => setActiveTab(tab.id)}
|
|
75
76
|
onKeyDown={e => handleKeyDown(e, i)}
|
|
76
77
|
>
|
|
77
|
-
{tab.label}
|
|
78
|
+
<span className={css.label} data-label={getNodeText(tab.label)}>{tab.label}</span>
|
|
78
79
|
</button>
|
|
79
80
|
);
|
|
80
81
|
})}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getNodeText } from './getNodeText';
|
|
2
|
+
|
|
3
|
+
describe('getNodeText', () => {
|
|
4
|
+
it('returns strings and numbers as text', () => {
|
|
5
|
+
expect(getNodeText('Overview')).toBe('Overview');
|
|
6
|
+
expect(getNodeText(3)).toBe('3');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('ignores booleans, null and undefined', () => {
|
|
10
|
+
expect(getNodeText(null)).toBe('');
|
|
11
|
+
expect(getNodeText(undefined)).toBe('');
|
|
12
|
+
expect(getNodeText(false)).toBe('');
|
|
13
|
+
expect(getNodeText(true)).toBe('');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('joins arrays', () => {
|
|
17
|
+
expect(getNodeText(['Inbox ', 4, false])).toBe('Inbox 4');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('reads element children recursively', () => {
|
|
21
|
+
expect(getNodeText(<span>Inbox <strong>(<em>4</em>)</strong></span>)).toBe('Inbox (4)');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isValidElement } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plain text of a ReactNode: strings and numbers as written, arrays and element
|
|
6
|
+
* children joined, booleans/null/undefined ignored. Pure, so it also runs on the server.
|
|
7
|
+
*/
|
|
8
|
+
export function getNodeText(node: ReactNode): string {
|
|
9
|
+
if (typeof node === 'string' || typeof node === 'number') return String(node);
|
|
10
|
+
if (Array.isArray(node)) return node.map(getNodeText).join('');
|
|
11
|
+
if (isValidElement<{ children?: ReactNode }>(node)) return getNodeText(node.props.children);
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
@@ -29,6 +29,9 @@ Short contextual label that appears on hover or focus. Use to supplement icon bu
|
|
|
29
29
|
### Right
|
|
30
30
|
<Canvas of={Stories.Right} />
|
|
31
31
|
|
|
32
|
+
### Long text
|
|
33
|
+
<Canvas of={Stories.LongText} />
|
|
34
|
+
|
|
32
35
|
### All placements
|
|
33
36
|
<Canvas of={Stories.AllPlacements} />
|
|
34
37
|
|
|
@@ -48,11 +51,11 @@ Short contextual label that appears on hover or focus. Use to supplement icon bu
|
|
|
48
51
|
<tbody>
|
|
49
52
|
<tr><td>`--tooltip_bg`</td><td>`var(--bds-color_on-bg)`</td><td>Tooltip background colour</td></tr>
|
|
50
53
|
<tr><td>`--tooltip_text`</td><td>`var(--bds-color_bg)`</td><td>Tooltip text colour</td></tr>
|
|
51
|
-
<tr><td>`--tooltip_radius`</td><td>`var(--bds-border_radius--
|
|
52
|
-
<tr><td>`--tooltip_padding`</td><td>`var(--bds-space_xxs) var(--bds-
|
|
54
|
+
<tr><td>`--tooltip_radius`</td><td>`var(--bds-border_radius--xs)`</td><td>Tooltip corner radius</td></tr>
|
|
55
|
+
<tr><td>`--tooltip_padding`</td><td>`var(--bds-space_xxs) var(--bds-space_s)`</td><td>Tooltip padding</td></tr>
|
|
53
56
|
<tr><td>`--tooltip_font-size`</td><td>`var(--bds-font_size--body--s)`</td><td>Tooltip text size</td></tr>
|
|
54
57
|
<tr><td>`--tooltip_offset`</td><td>`var(--bds-space_xs)`</td><td>Gap between the trigger and the tooltip. Set to `0` to close the gap so the pointer can travel from the trigger onto the tooltip without a dead zone.</td></tr>
|
|
55
|
-
<tr><td>`--tooltip_max-inline-size`</td><td>`20rem`</td><td>Maximum inline size before content wraps</td></tr>
|
|
58
|
+
<tr><td>`--tooltip_max-inline-size`</td><td>`20rem`</td><td>Maximum inline size before content wraps. The tooltip is as wide as its text (`max-content`) up to this value</td></tr>
|
|
56
59
|
</tbody>
|
|
57
60
|
</table>
|
|
58
61
|
|
|
@@ -17,12 +17,16 @@
|
|
|
17
17
|
display: block;
|
|
18
18
|
position: absolute;
|
|
19
19
|
z-index: var(--bds-z-index_popover);
|
|
20
|
-
padding: var(--tooltip_padding, var(--bds-space_xxs) var(--bds-
|
|
21
|
-
border-radius: var(--tooltip_radius, var(--bds-border_radius--
|
|
20
|
+
padding: var(--tooltip_padding, var(--bds-space_xxs) var(--bds-space_s));
|
|
21
|
+
border-radius: var(--tooltip_radius, var(--bds-border_radius--xs));
|
|
22
22
|
background-color: var(--tooltip_bg, var(--bds-color_on-bg));
|
|
23
23
|
color: var(--tooltip_text, var(--bds-color_bg));
|
|
24
24
|
font-size: var(--tooltip_font-size, var(--bds-font_size--body--s));
|
|
25
25
|
line-height: var(--bds-font_line-height--body);
|
|
26
|
+
|
|
27
|
+
/* Size to the text, not to the (possibly narrow) trigger wrapper the
|
|
28
|
+
tooltip is absolutely positioned in; wrap only at the max width. */
|
|
29
|
+
inline-size: max-content;
|
|
26
30
|
max-inline-size: var(--tooltip_max-inline-size, 20rem);
|
|
27
31
|
white-space: normal;
|
|
28
32
|
pointer-events: none;
|