@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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
2
4
|
import { SectionHeader } from './SectionHeader';
|
|
3
5
|
|
|
4
6
|
describe('SectionHeader', () => {
|
|
@@ -33,4 +35,46 @@ describe('SectionHeader', () => {
|
|
|
33
35
|
const { container } = render(<SectionHeader title="XS Title" size="xs" />);
|
|
34
36
|
expect(container.firstChild).toHaveClass('--xs');
|
|
35
37
|
});
|
|
38
|
+
|
|
39
|
+
it.each(['xs', 'small', 'medium', 'large'] as const)('maps the %s size to its own class', (size) => {
|
|
40
|
+
const { container } = render(<SectionHeader title="Title" size={size} />);
|
|
41
|
+
expect(container.firstChild).toHaveClass(`--${size}`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('does not add the gradient class by default', () => {
|
|
45
|
+
const { container } = render(<SectionHeader title="Title" />);
|
|
46
|
+
expect(container.firstChild).not.toHaveClass('--gradient');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('adds the gradient class for appearance="gradient"', () => {
|
|
50
|
+
const { container } = render(<SectionHeader title="Title" appearance="gradient" />);
|
|
51
|
+
expect(container.firstChild).toHaveClass('--gradient');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('styles', () => {
|
|
55
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/layout/SectionHeader/SectionHeader.module.css'), 'utf8');
|
|
56
|
+
|
|
57
|
+
it('gives every size its own title and subtitle token', () => {
|
|
58
|
+
const table: Record<string, [string, string]> = {
|
|
59
|
+
xs: ['body', 'body--s'],
|
|
60
|
+
small: ['heading-3', 'body'],
|
|
61
|
+
medium: ['heading-2', 'body'],
|
|
62
|
+
large: ['heading-1', 'heading-3'],
|
|
63
|
+
};
|
|
64
|
+
Object.entries(table).forEach(([size, [title, subtitle]]) => {
|
|
65
|
+
expect(css).toMatch(new RegExp(`\\.--${size} \\.title \\{[^}]*--sectionHeader_title-size, var\\(--bds-font_size--${title}\\)`));
|
|
66
|
+
expect(css).toMatch(new RegExp(`\\.--${size} \\.subtitle \\{[^}]*--sectionHeader_subtitle-size, var\\(--bds-font_size--${subtitle}\\)`));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('uses the heading font family and muted subtitle colour by default', () => {
|
|
71
|
+
expect(css).toMatch(/--sectionHeader_title-font-family, var\(--bds-font_family--heading\)/);
|
|
72
|
+
expect(css).toMatch(/--sectionHeader_subtitle-color, var\(--bds-color_on-bg--muted, var\(--bds-color_grey--strong\)\)/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('clips the gradient to the text inside @supports with a forced-colors fallback', () => {
|
|
76
|
+
expect(css).toMatch(/@supports \(background-clip: text\)[^]*--sectionHeader_title-gradient[^]*background-clip: text/);
|
|
77
|
+
expect(css).toMatch(/@media \(forced-colors: active\)[^]*CanvasText/);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
36
80
|
});
|
|
@@ -7,6 +7,7 @@ const meta = {
|
|
|
7
7
|
argTypes: {
|
|
8
8
|
alignment: { control: 'select', options: ['start', 'center', 'end'] },
|
|
9
9
|
size: { control: 'select', options: ['xs', 'small', 'medium', 'large'] },
|
|
10
|
+
appearance: { control: 'select', options: ['default', 'gradient'] },
|
|
10
11
|
},
|
|
11
12
|
} satisfies Meta<typeof SectionHeader>;
|
|
12
13
|
|
|
@@ -19,3 +20,7 @@ export const Centered: Story = { args: { title: 'Centered Title', subtitle: 'Cen
|
|
|
19
20
|
export const ExtraSmall: Story = { args: { title: 'Extra Small Header', subtitle: 'Extra small size.', size: 'xs' } };
|
|
20
21
|
export const Small: Story = { args: { title: 'Small Header', subtitle: 'Small size.', size: 'small' } };
|
|
21
22
|
export const Large: Story = { args: { title: 'Large Header', subtitle: 'Large size.', size: 'large' } };
|
|
23
|
+
export const Gradient: Story = { args: { title: 'Gradient Header', subtitle: 'Gradient title.', appearance: 'gradient' } };
|
|
24
|
+
export const GradientExtraSmall: Story = { args: { title: 'Gradient Extra Small', subtitle: 'Gradient title.', appearance: 'gradient', size: 'xs' } };
|
|
25
|
+
export const GradientSmall: Story = { args: { title: 'Gradient Small', subtitle: 'Gradient title.', appearance: 'gradient', size: 'small' } };
|
|
26
|
+
export const GradientLarge: Story = { args: { title: 'Gradient Large', subtitle: 'Gradient title.', appearance: 'gradient', size: 'large' } };
|
|
@@ -10,6 +10,7 @@ type SectionHeaderProps = WithClassName & Omit<HTMLAttributes<HTMLDivElement>, '
|
|
|
10
10
|
subtitle?: string;
|
|
11
11
|
alignment?: 'start' | 'center' | 'end';
|
|
12
12
|
size?: 'xs' | 'small' | 'medium' | 'large';
|
|
13
|
+
appearance?: 'default' | 'gradient';
|
|
13
14
|
titleAs?: IntrinsicElement;
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -19,12 +20,13 @@ export function SectionHeader({
|
|
|
19
20
|
className,
|
|
20
21
|
alignment = 'start',
|
|
21
22
|
size = 'medium',
|
|
23
|
+
appearance = 'default',
|
|
22
24
|
titleAs = 'h2',
|
|
23
25
|
...rest
|
|
24
26
|
}: Readonly<SectionHeaderProps>) {
|
|
25
27
|
const Title = titleAs;
|
|
26
28
|
return (
|
|
27
|
-
<div {...rest} className={cn(css.sectionHeader, css[`--${alignment}`], css[`--${size}`], className)}>
|
|
29
|
+
<div {...rest} className={cn(css.sectionHeader, css[`--${alignment}`], css[`--${size}`], appearance === 'gradient' && css['--gradient'], className)}>
|
|
28
30
|
<Title className={css.title}>{title}</Title>
|
|
29
31
|
{subtitle && <p className={css.subtitle}>{subtitle}</p>}
|
|
30
32
|
</div>
|
|
@@ -38,6 +38,10 @@ Inline contextual feedback message. Use for confirmations, warnings, errors, and
|
|
|
38
38
|
### Dismissible
|
|
39
39
|
<Canvas of={Stories.Dismissible} />
|
|
40
40
|
|
|
41
|
+
### Gradient
|
|
42
|
+
`appearance="gradient"` fades the variant tint to the surface, uses a 3px accent line and a shadow. The default appearance is unchanged.
|
|
43
|
+
<Canvas of={Stories.Gradient} />
|
|
44
|
+
|
|
41
45
|
## Props
|
|
42
46
|
|
|
43
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.
|
|
@@ -72,6 +76,13 @@ The override surface is **layered**. Setting a public token on the alert (or any
|
|
|
72
76
|
<tr><td>`--alert_bg`</td><td>per variant</td><td>Background colour. Set this to theme every variant at once.</td></tr>
|
|
73
77
|
<tr><td>`--alert_text`</td><td>per variant</td><td>Text and icon colour.</td></tr>
|
|
74
78
|
<tr><td>`--alert_border`</td><td>per variant</td><td>Inline-start border (the coloured strip).</td></tr>
|
|
79
|
+
<tr><td>`--alert_border-width`</td><td>`var(--bds-space_s)` (`3px` with `appearance="gradient"`)</td><td>Width of the inline-start accent line.</td></tr>
|
|
80
|
+
<tr><td>`--alert_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>
|
|
81
|
+
<tr><td>`--alert_shadow`</td><td>`var(--bds-shadow_s)`</td><td>Box shadow. Only used with `appearance="gradient"`; the default appearance has none.</td></tr>
|
|
82
|
+
<tr><td>`--alert_accent--info`</td><td>`var(--bds-color_on-blue--subtle)`</td><td>Accent line colour of the info variant with `appearance="gradient"`. `--alert_border--info` and `--alert_border` win over it.</td></tr>
|
|
83
|
+
<tr><td>`--alert_accent--success`</td><td>`var(--bds-color_on-success--subtle)`</td><td>Same, success variant.</td></tr>
|
|
84
|
+
<tr><td>`--alert_accent--warning`</td><td>`var(--bds-color_warning)`</td><td>Same, warning variant.</td></tr>
|
|
85
|
+
<tr><td>`--alert_accent--error`</td><td>`var(--bds-color_error)`</td><td>Same, error variant.</td></tr>
|
|
75
86
|
<tr><td>`--alert_radius`</td><td>`var(--bds-border_radius--s)`</td><td>Root border radius.</td></tr>
|
|
76
87
|
<tr><td>`--alert_dismiss-border`</td><td>`none`</td><td>Dismiss button border.</td></tr>
|
|
77
88
|
<tr><td>`--alert_dismiss-radius`</td><td>`var(--bds-border_radius--circle)`</td><td>Dismiss button border radius.</td></tr>
|
|
@@ -105,7 +116,7 @@ Each variant's actual default resolution:
|
|
|
105
116
|
<tr><td>`info`</td><td>`--bds-color_blue--subtle`</td><td>`--bds-color_on-blue--subtle`</td><td>`--bds-color_blue`</td></tr>
|
|
106
117
|
<tr><td>`success`</td><td>`--bds-color_success--subtle`</td><td>`--bds-color_on-success--subtle`</td><td>`--bds-color_success`</td></tr>
|
|
107
118
|
<tr><td>`warning`</td><td>`--bds-color_orange--subtle`</td><td>`--bds-color_on-orange--subtle`</td><td>`--bds-color_warning`</td></tr>
|
|
108
|
-
<tr><td>`error`</td><td
|
|
119
|
+
<tr><td>`error`</td><td>`--bds-color_error--subtle`</td><td>`--bds-color_on-error--subtle`</td><td>`--bds-color_error`</td></tr>
|
|
109
120
|
</tbody>
|
|
110
121
|
</table>
|
|
111
122
|
|
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
defaults. Variants write to the inner --alert_*--variant slot. */
|
|
12
12
|
background-color: var(--alert_bg, var(--alert_bg--variant, var(--bds-color_blue--subtle)));
|
|
13
13
|
color: var(--alert_text, var(--alert_text--variant, var(--bds-color_on-blue--subtle)));
|
|
14
|
-
border-inline-start: var(--bds-space_s) solid var(--alert_border, var(--alert_border--variant, var(--bds-color_blue)));
|
|
14
|
+
border-inline-start: var(--alert_border-width, var(--alert_border-width--variant, var(--bds-space_s))) solid var(--alert_border, var(--alert_border--variant, var(--bds-color_blue)));
|
|
15
15
|
font-size: var(--bds-font_size--body);
|
|
16
16
|
line-height: var(--bds-font_line-height--body);
|
|
17
|
+
|
|
18
|
+
/* Reset the slot so a nested default alert does not inherit a parent's gradient width. */
|
|
19
|
+
--alert_border-width--variant: initial;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
/* Per-variant resting overrides — consumers can theme per-variant via
|
|
@@ -37,11 +40,37 @@
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
.--variant_error {
|
|
40
|
-
--alert_bg--variant: var(--alert_bg--error,
|
|
41
|
-
--alert_text--variant: var(--alert_text--error, var(--bds-color_on-
|
|
43
|
+
--alert_bg--variant: var(--alert_bg--error, var(--bds-color_error--subtle));
|
|
44
|
+
--alert_text--variant: var(--alert_text--error, var(--bds-color_on-error--subtle));
|
|
42
45
|
--alert_border--variant: var(--alert_border--error, var(--bds-color_error));
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/* Gradient appearance: the variant background stays the base colour and a
|
|
49
|
+
gradient fades it to the surface. The accent line is thinner and uses a
|
|
50
|
+
per-variant accent slot; --alert_border still wins over it. */
|
|
51
|
+
.--appearance_gradient {
|
|
52
|
+
--alert_border-width--variant: 3px;
|
|
53
|
+
|
|
54
|
+
background-image: var(--alert_gradient, linear-gradient(100deg, transparent 35%, var(--bds-color_bg--strong, var(--bds-color_bg)) 100%));
|
|
55
|
+
box-shadow: var(--alert_shadow, var(--bds-shadow_s));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.--appearance_gradient.--variant_info {
|
|
59
|
+
--alert_border--variant: var(--alert_border--info, var(--alert_accent--info, var(--bds-color_on-blue--subtle)));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.--appearance_gradient.--variant_success {
|
|
63
|
+
--alert_border--variant: var(--alert_border--success, var(--alert_accent--success, var(--bds-color_on-success--subtle)));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.--appearance_gradient.--variant_warning {
|
|
67
|
+
--alert_border--variant: var(--alert_border--warning, var(--alert_accent--warning, var(--bds-color_warning)));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.--appearance_gradient.--variant_error {
|
|
71
|
+
--alert_border--variant: var(--alert_border--error, var(--alert_accent--error, var(--bds-color_error)));
|
|
72
|
+
}
|
|
73
|
+
|
|
45
74
|
.icon {
|
|
46
75
|
display: flex;
|
|
47
76
|
align-items: center;
|
|
@@ -80,4 +80,31 @@ describe('Alert', () => {
|
|
|
80
80
|
expect(dismissRule).toMatch(/min-inline-size:\s*var\(--alert_dismiss-size,\s*2\.75rem\)/);
|
|
81
81
|
expect(dismissRule).toMatch(/min-block-size:\s*var\(--alert_dismiss-size,\s*2\.75rem\)/);
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
it('adds the gradient appearance class with appearance="gradient"', () => {
|
|
85
|
+
render(<Alert appearance="gradient">Message</Alert>);
|
|
86
|
+
expect(screen.getByRole('status').className).toMatch(/appearance_gradient/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('does not add the gradient class by default', () => {
|
|
90
|
+
render(<Alert>Message</Alert>);
|
|
91
|
+
expect(screen.getByRole('status').className).not.toMatch(/appearance_gradient/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('wires the gradient hooks in the CSS', () => {
|
|
95
|
+
expect(cssSource).toMatch(/background-image:\s*var\(--alert_gradient,\s*linear-gradient\(100deg/);
|
|
96
|
+
expect(cssSource).toMatch(/box-shadow:\s*var\(--alert_shadow,\s*var\(--bds-shadow_s\)\)/);
|
|
97
|
+
expect(cssSource).toMatch(/--alert_border-width--variant:\s*3px/);
|
|
98
|
+
expect(cssSource).toMatch(/\.alert \{[^}]*--alert_border-width--variant:\s*initial/);
|
|
99
|
+
expect(cssSource).toMatch(/var\(--alert_border-width,\s*var\(--alert_border-width--variant,\s*var\(--bds-space_s\)\)\)/);
|
|
100
|
+
['info', 'success', 'warning', 'error'].forEach(v => {
|
|
101
|
+
expect(cssSource).toContain(`--alert_accent--${v}`);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('paints the error variant with the error subtle pair, like the other variants', () => {
|
|
106
|
+
expect(cssSource).toMatch(/\.--variant_error \{[^}]*--alert_bg--variant:\s*var\(--alert_bg--error,\s*var\(--bds-color_error--subtle\)\)/);
|
|
107
|
+
expect(cssSource).toMatch(/\.--variant_error \{[^}]*--alert_text--variant:\s*var\(--alert_text--error,\s*var\(--bds-color_on-error--subtle\)\)/);
|
|
108
|
+
expect(cssSource).not.toContain('rgb(from var(--bds-color_error)');
|
|
109
|
+
});
|
|
83
110
|
});
|
|
@@ -2,11 +2,19 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Alert } from './Alert';
|
|
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/Alert',
|
|
7
14
|
component: Alert,
|
|
8
15
|
argTypes: {
|
|
9
16
|
variant: { control: 'select', options: ['info', 'success', 'warning', 'error'] },
|
|
17
|
+
appearance: { control: 'select', options: ['default', 'gradient'] },
|
|
10
18
|
},
|
|
11
19
|
} satisfies Meta<typeof Alert>;
|
|
12
20
|
|
|
@@ -50,3 +58,15 @@ export const AllVariants: Story = {
|
|
|
50
58
|
</div>
|
|
51
59
|
),
|
|
52
60
|
};
|
|
61
|
+
|
|
62
|
+
export const Gradient: Story = {
|
|
63
|
+
render: () => (
|
|
64
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
65
|
+
{(['info', 'success', 'warning', 'error'] as const).map(v => (
|
|
66
|
+
<Alert key={v} variant={v} appearance="gradient" icon={icons[v]} title={v.charAt(0).toUpperCase() + v.slice(1)}>
|
|
67
|
+
This is a {v} alert message.
|
|
68
|
+
</Alert>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
),
|
|
72
|
+
};
|
|
@@ -5,6 +5,7 @@ import type { WithClassName } from '../../../types';
|
|
|
5
5
|
|
|
6
6
|
interface AlertProps extends WithClassName, Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
7
7
|
variant?: 'info' | 'success' | 'warning' | 'error';
|
|
8
|
+
appearance?: 'default' | 'gradient';
|
|
8
9
|
icon?: ReactNode;
|
|
9
10
|
title?: string;
|
|
10
11
|
onDismiss?: () => void;
|
|
@@ -12,6 +13,7 @@ interface AlertProps extends WithClassName, Omit<HTMLAttributes<HTMLDivElement>,
|
|
|
12
13
|
|
|
13
14
|
export function Alert({
|
|
14
15
|
variant = 'info',
|
|
16
|
+
appearance = 'default',
|
|
15
17
|
icon,
|
|
16
18
|
title,
|
|
17
19
|
children,
|
|
@@ -27,7 +29,12 @@ export function Alert({
|
|
|
27
29
|
role={isUrgent ? 'alert' : 'status'}
|
|
28
30
|
aria-live={isUrgent ? 'assertive' : 'polite'}
|
|
29
31
|
aria-atomic="true"
|
|
30
|
-
className={cn(
|
|
32
|
+
className={cn(
|
|
33
|
+
css.alert,
|
|
34
|
+
css[`--variant_${variant}`],
|
|
35
|
+
appearance === 'gradient' && css['--appearance_gradient'],
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
31
38
|
>
|
|
32
39
|
{icon && (
|
|
33
40
|
<span className={css.icon} aria-hidden="true">
|
|
@@ -22,6 +22,12 @@ Circular user representation. Displays a profile image when available, falling b
|
|
|
22
22
|
### All sizes
|
|
23
23
|
<Canvas of={Stories.AllSizes} />
|
|
24
24
|
|
|
25
|
+
### Tones
|
|
26
|
+
<Canvas of={Stories.AllTones} />
|
|
27
|
+
|
|
28
|
+
### Automatic tone from the name
|
|
29
|
+
<Canvas of={Stories.AutoTone} />
|
|
30
|
+
|
|
25
31
|
### Single name (one initial)
|
|
26
32
|
<Canvas of={Stories.SingleName} />
|
|
27
33
|
|
|
@@ -34,6 +40,10 @@ Circular user representation. Displays a profile image when available, falling b
|
|
|
34
40
|
|
|
35
41
|
<ArgTypes of={Stories} />
|
|
36
42
|
|
|
43
|
+
## Tone
|
|
44
|
+
|
|
45
|
+
`tone` colours the initials fallback with the subtle set of a palette: `'auto'` (default), `'primary'`, `'secondary'`, `'accent-1'`, `'accent-2'` or `'accent-3'`. With `auto` the tone is picked from `name`, so the same name always gets the same colour; no name gives `primary`. Image avatars ignore `tone`.
|
|
46
|
+
|
|
37
47
|
## CSS variables
|
|
38
48
|
|
|
39
49
|
The override surface is **layered** — `--avatar_size` wins over the per-`size`-prop default, so you can theme one avatar or every avatar under an ancestor selector.
|
|
@@ -45,8 +55,18 @@ The override surface is **layered** — `--avatar_size` wins over the per-`size`
|
|
|
45
55
|
<tbody>
|
|
46
56
|
<tr><td>`--avatar_size`</td><td>per `size` (`2em` / `3em` / `4.5em`)</td><td>Avatar diameter. Set on the avatar or an ancestor to override the `size` prop's default.</td></tr>
|
|
47
57
|
<tr><td>`--avatar_radius`</td><td>`var(--bds-border_radius--circle)`</td><td>Avatar corner radius</td></tr>
|
|
48
|
-
<tr><td>`--avatar_bg`</td><td
|
|
49
|
-
<tr><td>`--avatar_text`</td><td
|
|
58
|
+
<tr><td>`--avatar_bg`</td><td>per `tone`</td><td>Background colour for the initials fallback; wins over every tone</td></tr>
|
|
59
|
+
<tr><td>`--avatar_text`</td><td>per `tone`</td><td>Text colour for the initials; wins over every tone</td></tr>
|
|
60
|
+
<tr><td>`--avatar_bg--primary`</td><td>`var(--bds-color_primary--subtle)`</td><td>Background for `tone="primary"`</td></tr>
|
|
61
|
+
<tr><td>`--avatar_text--primary`</td><td>`var(--bds-color_on-primary--subtle)`</td><td>Text for `tone="primary"`</td></tr>
|
|
62
|
+
<tr><td>`--avatar_bg--secondary`</td><td>`var(--bds-color_secondary--subtle)`</td><td>Background for `tone="secondary"`</td></tr>
|
|
63
|
+
<tr><td>`--avatar_text--secondary`</td><td>`var(--bds-color_on-secondary--subtle)`</td><td>Text for `tone="secondary"`</td></tr>
|
|
64
|
+
<tr><td>`--avatar_bg--accent-1`</td><td>`var(--bds-color_accent-1--subtle)`</td><td>Background for `tone="accent-1"`</td></tr>
|
|
65
|
+
<tr><td>`--avatar_text--accent-1`</td><td>`var(--bds-color_on-accent-1--subtle)`</td><td>Text for `tone="accent-1"`</td></tr>
|
|
66
|
+
<tr><td>`--avatar_bg--accent-2`</td><td>`var(--bds-color_accent-2--subtle)`</td><td>Background for `tone="accent-2"`</td></tr>
|
|
67
|
+
<tr><td>`--avatar_text--accent-2`</td><td>`var(--bds-color_on-accent-2--subtle)`</td><td>Text for `tone="accent-2"`</td></tr>
|
|
68
|
+
<tr><td>`--avatar_bg--accent-3`</td><td>`var(--bds-color_accent-3--subtle)`</td><td>Background for `tone="accent-3"`</td></tr>
|
|
69
|
+
<tr><td>`--avatar_text--accent-3`</td><td>`var(--bds-color_on-accent-3--subtle)`</td><td>Text for `tone="accent-3"`</td></tr>
|
|
50
70
|
</tbody>
|
|
51
71
|
</table>
|
|
52
72
|
|
|
@@ -16,11 +16,38 @@
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
.avatar.--fallback {
|
|
19
|
-
background-color: var(--avatar_bg, var(--
|
|
20
|
-
color: var(--avatar_text, var(--
|
|
19
|
+
background-color: var(--avatar_bg, var(--avatar_bg--variant));
|
|
20
|
+
color: var(--avatar_text, var(--avatar_text--variant));
|
|
21
21
|
font-weight: var(--bds-font_weight--semibold);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/* Tone slots: the outer --avatar_bg / --avatar_text still win, and each
|
|
25
|
+
tone has its own public hook (--avatar_bg--primary etc.). */
|
|
26
|
+
.avatar.--tone_primary {
|
|
27
|
+
--avatar_bg--variant: var(--avatar_bg--primary, var(--bds-color_primary--subtle, var(--bds-color_blue--subtle)));
|
|
28
|
+
--avatar_text--variant: var(--avatar_text--primary, var(--bds-color_on-primary--subtle, var(--bds-color_on-blue--subtle)));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.avatar.--tone_secondary {
|
|
32
|
+
--avatar_bg--variant: var(--avatar_bg--secondary, var(--bds-color_secondary--subtle, var(--bds-color_blue--subtle)));
|
|
33
|
+
--avatar_text--variant: var(--avatar_text--secondary, var(--bds-color_on-secondary--subtle, var(--bds-color_on-blue--subtle)));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.avatar.--tone_accent-1 {
|
|
37
|
+
--avatar_bg--variant: var(--avatar_bg--accent-1, var(--bds-color_accent-1--subtle, var(--bds-color_green--subtle)));
|
|
38
|
+
--avatar_text--variant: var(--avatar_text--accent-1, var(--bds-color_on-accent-1--subtle, var(--bds-color_on-green--subtle)));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.avatar.--tone_accent-2 {
|
|
42
|
+
--avatar_bg--variant: var(--avatar_bg--accent-2, var(--bds-color_accent-2--subtle, var(--bds-color_orange--subtle)));
|
|
43
|
+
--avatar_text--variant: var(--avatar_text--accent-2, var(--bds-color_on-accent-2--subtle, var(--bds-color_on-orange--subtle)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.avatar.--tone_accent-3 {
|
|
47
|
+
--avatar_bg--variant: var(--avatar_bg--accent-3, var(--bds-color_accent-3--subtle, var(--bds-color_blue--subtle)));
|
|
48
|
+
--avatar_text--variant: var(--avatar_text--accent-3, var(--bds-color_on-accent-3--subtle, var(--bds-color_on-blue--subtle)));
|
|
49
|
+
}
|
|
50
|
+
|
|
24
51
|
.avatar.--size_small { --avatar_size--variant: 2em; }
|
|
25
52
|
.avatar.--size_medium { --avatar_size--variant: 3em; }
|
|
26
53
|
.avatar.--size_large { --avatar_size--variant: 4.5em; }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { Avatar } from './Avatar';
|
|
3
|
+
import { toneFromName } from './avatarTone';
|
|
3
4
|
|
|
4
5
|
describe('Avatar', () => {
|
|
5
6
|
it('renders an image when src is provided', () => {
|
|
@@ -46,4 +47,29 @@ describe('Avatar', () => {
|
|
|
46
47
|
render(<Avatar name="Jane Doe" className="custom" />);
|
|
47
48
|
expect(screen.getByRole('img')).toHaveClass('custom');
|
|
48
49
|
});
|
|
50
|
+
|
|
51
|
+
it('applies the tone class derived from the name by default', () => {
|
|
52
|
+
render(<Avatar name="Jane Doe" />);
|
|
53
|
+
expect(screen.getByRole('img').className).toContain(`--tone_${toneFromName('Jane Doe')}`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('applies primary when there is no name and no tone', () => {
|
|
57
|
+
render(<Avatar />);
|
|
58
|
+
expect(screen.getByRole('img').className).toContain('--tone_primary');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('lets an explicit tone win over auto', () => {
|
|
62
|
+
render(<Avatar name="Jane Doe" tone="accent-3" />);
|
|
63
|
+
expect(screen.getByRole('img').className).toContain('--tone_accent-3');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('applies the same tone class for tone="auto" as for no tone', () => {
|
|
67
|
+
render(<Avatar name="Jane Doe" tone="auto" />);
|
|
68
|
+
expect(screen.getByRole('img').className).toContain(`--tone_${toneFromName('Jane Doe')}`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('does not apply a tone class to image avatars', () => {
|
|
72
|
+
const { container } = render(<Avatar src="https://example.com/photo.jpg" name="Jane Doe" tone="accent-1" />);
|
|
73
|
+
expect(container.firstElementChild?.className).not.toContain('--tone_');
|
|
74
|
+
});
|
|
49
75
|
});
|
|
@@ -6,6 +6,7 @@ const meta = {
|
|
|
6
6
|
component: Avatar,
|
|
7
7
|
argTypes: {
|
|
8
8
|
size: { control: 'radio', options: ['small', 'medium', 'large'] },
|
|
9
|
+
tone: { control: 'select', options: ['auto', 'primary', 'secondary', 'accent-1', 'accent-2', 'accent-3'] },
|
|
9
10
|
},
|
|
10
11
|
} satisfies Meta<typeof Avatar>;
|
|
11
12
|
|
|
@@ -41,3 +42,25 @@ export const AllSizes: Story = {
|
|
|
41
42
|
</div>
|
|
42
43
|
),
|
|
43
44
|
};
|
|
45
|
+
|
|
46
|
+
export const AllTones: Story = {
|
|
47
|
+
render: () => (
|
|
48
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
|
49
|
+
<Avatar name="Primary" tone="primary" />
|
|
50
|
+
<Avatar name="Secondary" tone="secondary" />
|
|
51
|
+
<Avatar name="Accent One" tone="accent-1" />
|
|
52
|
+
<Avatar name="Accent Two" tone="accent-2" />
|
|
53
|
+
<Avatar name="Accent Three" tone="accent-3" />
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const AutoTone: Story = {
|
|
59
|
+
render: () => (
|
|
60
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
|
61
|
+
{['Jane Doe', 'Bjorn Boonen', 'Sam Visser', 'Noor de Vries', 'Lars Bakker'].map(name => (
|
|
62
|
+
<Avatar key={name} name={name} />
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
),
|
|
66
|
+
};
|
|
@@ -2,12 +2,14 @@ import { HTMLAttributes } from 'react';
|
|
|
2
2
|
import css from './Avatar.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
4
|
import type { WithClassName } from '../../../types';
|
|
5
|
+
import { resolveAvatarTone, type AvatarTone } from './avatarTone';
|
|
5
6
|
|
|
6
7
|
interface AvatarProps extends WithClassName, HTMLAttributes<HTMLSpanElement> {
|
|
7
8
|
src?: string;
|
|
8
9
|
alt?: string;
|
|
9
10
|
name?: string;
|
|
10
11
|
size?: 'small' | 'medium' | 'large';
|
|
12
|
+
tone?: AvatarTone;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
function getInitials(name: string): string {
|
|
@@ -19,7 +21,7 @@ function getInitials(name: string): string {
|
|
|
19
21
|
.join('');
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
export function Avatar({ src, alt, name, size = 'medium', className, ...rest }: Readonly<AvatarProps>) {
|
|
24
|
+
export function Avatar({ src, alt, name, size = 'medium', tone = 'auto', className, ...rest }: Readonly<AvatarProps>) {
|
|
23
25
|
const sizeClass = css[`--size_${size}`];
|
|
24
26
|
|
|
25
27
|
if (src) {
|
|
@@ -37,7 +39,7 @@ export function Avatar({ src, alt, name, size = 'medium', className, ...rest }:
|
|
|
37
39
|
{...rest}
|
|
38
40
|
role="img"
|
|
39
41
|
aria-label={name ?? 'User avatar'}
|
|
40
|
-
className={cn(css.avatar, css['--fallback'], sizeClass, className)}
|
|
42
|
+
className={cn(css.avatar, css['--fallback'], css[`--tone_${resolveAvatarTone(tone, name)}`], sizeClass, className)}
|
|
41
43
|
>
|
|
42
44
|
<span className={css.initials} aria-hidden="true">
|
|
43
45
|
{initials}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AVATAR_TONES, resolveAvatarTone, toneFromName } from './avatarTone';
|
|
2
|
+
|
|
3
|
+
describe('avatarTone', () => {
|
|
4
|
+
it('returns the same tone for the same name', () => {
|
|
5
|
+
expect(toneFromName('Jane Doe')).toBe(toneFromName('Jane Doe'));
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('ignores case and surrounding whitespace', () => {
|
|
9
|
+
expect(toneFromName(' JANE doe ')).toBe(toneFromName('jane doe'));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('returns primary when there is no name', () => {
|
|
13
|
+
expect(toneFromName()).toBe('primary');
|
|
14
|
+
expect(toneFromName('')).toBe('primary');
|
|
15
|
+
expect(toneFromName(' ')).toBe('primary');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('only returns known tones', () => {
|
|
19
|
+
const names = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi'];
|
|
20
|
+
names.forEach(name => expect(AVATAR_TONES).toContain(toneFromName(name)));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('spreads different names over more than one tone', () => {
|
|
24
|
+
const names = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace', 'Heidi'];
|
|
25
|
+
expect(new Set(names.map(toneFromName)).size).toBeGreaterThan(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('falls back to the name for an unknown tone', () => {
|
|
29
|
+
// @ts-expect-error simulates an invalid attribute value from HTML
|
|
30
|
+
expect(resolveAvatarTone('foo', 'Jane Doe')).toBe(toneFromName('Jane Doe'));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('lets an explicit tone win over the name', () => {
|
|
34
|
+
expect(resolveAvatarTone('accent-2', 'Jane Doe')).toBe('accent-2');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('derives the tone from the name for auto and undefined', () => {
|
|
38
|
+
expect(resolveAvatarTone('auto', 'Jane Doe')).toBe(toneFromName('Jane Doe'));
|
|
39
|
+
expect(resolveAvatarTone(undefined, 'Jane Doe')).toBe(toneFromName('Jane Doe'));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const AVATAR_TONES = ['primary', 'accent-1', 'accent-2', 'accent-3', 'secondary'] as const;
|
|
2
|
+
|
|
3
|
+
export type AvatarToneName = (typeof AVATAR_TONES)[number];
|
|
4
|
+
export type AvatarTone = 'auto' | AvatarToneName;
|
|
5
|
+
|
|
6
|
+
/** Picks a stable tone from a name: the same name always gives the same tone. */
|
|
7
|
+
export function toneFromName(name?: string): AvatarToneName {
|
|
8
|
+
const normalised = (name ?? '').trim().toLowerCase();
|
|
9
|
+
if (!normalised) return 'primary';
|
|
10
|
+
|
|
11
|
+
const hash = [...normalised].reduce(
|
|
12
|
+
(h, ch) => (Math.imul(h, 31) + (ch.codePointAt(0) ?? 0)) >>> 0,
|
|
13
|
+
0,
|
|
14
|
+
);
|
|
15
|
+
return AVATAR_TONES[hash % AVATAR_TONES.length];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** An explicit tone wins; `auto` (or no tone) derives one from the name. */
|
|
19
|
+
export function resolveAvatarTone(tone: AvatarTone | undefined, name?: string): AvatarToneName {
|
|
20
|
+
const isKnownTone = (value: string): value is AvatarToneName => AVATAR_TONES.some(known => known === value);
|
|
21
|
+
return tone && isKnownTone(tone) ? tone : toneFromName(name);
|
|
22
|
+
}
|
|
@@ -75,7 +75,7 @@ The override surface is **layered**. Setting a public token on the badge (or any
|
|
|
75
75
|
</tbody>
|
|
76
76
|
</table>
|
|
77
77
|
|
|
78
|
-
Defaults resolve to foundation tokens per variant: `success`/`error`/`warning` map directly to `--bds-color_{variant}` / `--bds-color_on-{variant}`. `primary` maps to `--bds-color_interactive` / `--bds-color_on-interactive`, and `secondary` maps to `--bds-
|
|
78
|
+
Defaults resolve to foundation tokens per variant: `success`/`error`/`warning` map directly to `--bds-color_{variant}` / `--bds-color_on-{variant}`. `primary` maps to `--bds-color_interactive` / `--bds-color_on-interactive`, and `secondary` maps to the neutral `--bds-color_grey` / `--bds-color_on-grey`, so it has a solid surface like the other variants. Neither follows the generic `{variant}` naming.
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
## Accessibility
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
.--variant_secondary {
|
|
25
|
-
--badge_bg--variant: var(--badge_bg--secondary, var(--bds-
|
|
26
|
-
--badge_text--variant: var(--badge_text--secondary, var(--bds-color_on-
|
|
25
|
+
--badge_bg--variant: var(--badge_bg--secondary, var(--bds-color_grey));
|
|
26
|
+
--badge_text--variant: var(--badge_text--secondary, var(--bds-color_on-grey));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.--variant_success {
|
|
@@ -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 { Badge } from './Badge';
|
|
3
5
|
|
|
@@ -16,4 +18,10 @@ describe('Badge', () => {
|
|
|
16
18
|
render(<Badge className="custom">Test</Badge>);
|
|
17
19
|
expect(screen.getByText('Test')).toHaveClass('custom');
|
|
18
20
|
});
|
|
21
|
+
|
|
22
|
+
it('gives the secondary variant a solid neutral surface through its own hooks', () => {
|
|
23
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/ui/Badge/Badge.module.css'), 'utf8');
|
|
24
|
+
expect(css).toMatch(/\.--variant_secondary \{[^}]*--badge_bg--variant:\s*var\(--badge_bg--secondary,\s*var\(--bds-color_grey\)\)/);
|
|
25
|
+
expect(css).toMatch(/\.--variant_secondary \{[^}]*--badge_text--variant:\s*var\(--badge_text--secondary,\s*var\(--bds-color_on-grey\)\)/);
|
|
26
|
+
});
|
|
19
27
|
});
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
.collapsible {
|
|
3
3
|
border: var(--collapsible_border, none);
|
|
4
4
|
--bdc_color: var(--collapsible_border-color, var(--bds-color_bg--subtle));
|
|
5
|
-
--bdc-outline_width: var(--collapsible_border-width, 1px);
|
|
6
5
|
--bdc-outline_radius: var(--collapsible_border-radius, var(--bds-border_radius--m));
|
|
7
6
|
|
|
8
7
|
border-radius: var(--bdc-outline_radius);
|
|
9
|
-
box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
|
|
8
|
+
box-shadow: inset 0 0 0 var(--collapsible_border-width, var(--bdc-outline_width)) var(--bdc_color);
|
|
10
9
|
overflow: hidden;
|
|
11
10
|
}
|
|
12
11
|
|