@boostdev/design-system-components 2.15.0 → 2.16.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 +19 -14
- package/dist/client.cjs +271 -205
- package/dist/client.css +801 -642
- 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 +801 -642
- 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-LDIPCTRN.js → chunk-2MJTO2IW.js} +48 -2
- package/dist/web-components/{chunk-3WAV6MDN.js → chunk-3FSGP35V.js} +1 -0
- package/dist/web-components/{chunk-KA3VPKMY.js → chunk-4MB2ICSK.js} +1 -0
- 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-6G22I2CK.js → chunk-RZH5IVB5.js} +31 -1
- 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-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 +33 -28
- 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-notification-banner.d.ts +14 -1
- package/dist/web-components/ui/bds-notification-banner.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/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 +1 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +7 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +22 -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 +1 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +7 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +22 -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 +11 -0
- package/src/components/ui/Alert/Alert.module.css +30 -1
- package/src/components/ui/Alert/Alert.spec.tsx +21 -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/Collapsible/Collapsible.module.css +1 -2
- package/src/components/ui/NotificationBanner/NotificationBanner.mdx +14 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +31 -1
- package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +29 -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/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 +19 -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 +1 -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 +10 -1
- 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 +1 -1
- 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 +27 -0
- package/src/web-components/ui/bds-alert.ts +34 -1
- 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-notification-banner.spec.ts +45 -0
- package/src/web-components/ui/bds-notification-banner.ts +53 -1
- 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,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
1
3
|
import { useState } from 'react';
|
|
2
4
|
import { render, screen } from '@testing-library/react';
|
|
3
5
|
import userEvent from '@testing-library/user-event';
|
|
@@ -97,4 +99,9 @@ describe('NumberInput', () => {
|
|
|
97
99
|
expect(input).toHaveValue(null);
|
|
98
100
|
expect(onChange).not.toHaveBeenCalledWith(0);
|
|
99
101
|
});
|
|
102
|
+
|
|
103
|
+
it('widens the outline in the error state', () => {
|
|
104
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/NumberInput/NumberInput.module.css'), 'utf8');
|
|
105
|
+
expect(css).toMatch(/\.inputError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
|
|
106
|
+
});
|
|
100
107
|
});
|
|
@@ -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 { Radio } from './Radio';
|
|
@@ -50,4 +52,9 @@ describe('Radio', () => {
|
|
|
50
52
|
const radio = screen.getByLabelText('Option');
|
|
51
53
|
expect(radio).toHaveAttribute('id', 'custom-radio-id');
|
|
52
54
|
});
|
|
55
|
+
|
|
56
|
+
it('widens the outline in the error state', () => {
|
|
57
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Radio/Radio.module.css'), 'utf8');
|
|
58
|
+
expect(css).toMatch(/\.radioError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
|
|
59
|
+
});
|
|
53
60
|
});
|
|
@@ -57,4 +57,5 @@ All public override tokens. Set on the RadioGroup or any ancestor; they cascade.
|
|
|
57
57
|
- `disabled` on the group sets the HTML `disabled` attribute on the `<fieldset>`, which disables all child inputs
|
|
58
58
|
- When `required` is true, the legend shows a visible `*` (hidden from assistive tech via `aria-hidden`) plus a visually-hidden "(required)" span, so the fieldset's accessible name resolves to `"<legend> (required)"` (e.g. `"Preferred contact (required)"`). `aria-required` is intentionally **not** set on the `<fieldset>` — it is not supported ARIA on a `role=group` element and would be ignored by assistive tech
|
|
59
59
|
- Error and hint messages are linked via `aria-describedby`
|
|
60
|
+
- With `error`, the child controls show the error ring (`--bdc-outline_width--error`, `--bdc_color--error`) and the message gets a warning icon. Message hooks (`--formMessage_*`) are listed on FormInput
|
|
60
61
|
- Arrow keys move between options within the group; `Tab` moves focus out of the group
|
|
@@ -42,4 +42,11 @@
|
|
|
42
42
|
flex-direction: column;
|
|
43
43
|
gap: var(--radioGroup_items-gap, var(--bds-space_xs));
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
/* Error ring on the controls inside; state rules on the control itself
|
|
47
|
+
(checked, focus) still win because they have higher specificity. */
|
|
48
|
+
.groupError input {
|
|
49
|
+
--bdc_color: var(--bdc_color--error);
|
|
50
|
+
--bdc-outline_width: var(--bdc-outline_width--error, 2px);
|
|
51
|
+
}
|
|
45
52
|
}
|
|
@@ -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 { Radio } from '../Radio';
|
|
3
5
|
import { RadioGroup } from './RadioGroup';
|
|
@@ -102,4 +104,24 @@ describe('RadioGroup', () => {
|
|
|
102
104
|
);
|
|
103
105
|
expect(screen.getByRole('group')).toBeDisabled();
|
|
104
106
|
});
|
|
107
|
+
|
|
108
|
+
it('marks the group with the error class so the controls inside show the error ring', () => {
|
|
109
|
+
render(
|
|
110
|
+
<RadioGroup legend="Pick" error="Required">
|
|
111
|
+
<Radio label="One" name="one" value="1" />
|
|
112
|
+
</RadioGroup>
|
|
113
|
+
);
|
|
114
|
+
expect(screen.getByRole('group', { name: 'Pick' }).className).toContain('groupError');
|
|
115
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/RadioGroup/RadioGroup.module.css'), 'utf8');
|
|
116
|
+
expect(css).toMatch(/\.groupError input \{[^}]*--bdc_color: var\(--bdc_color--error\)[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('does not mark the group without an error', () => {
|
|
120
|
+
render(
|
|
121
|
+
<RadioGroup legend="Pick">
|
|
122
|
+
<Radio label="One" name="one" value="1" />
|
|
123
|
+
</RadioGroup>
|
|
124
|
+
);
|
|
125
|
+
expect(screen.getByRole('group', { name: 'Pick' }).className).not.toContain('groupError');
|
|
126
|
+
});
|
|
105
127
|
});
|
|
@@ -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 { Select } from './Select';
|
|
@@ -93,4 +95,9 @@ describe('Select', () => {
|
|
|
93
95
|
expect(select).toHaveAttribute('aria-describedby', 'custom-iderror');
|
|
94
96
|
expect(screen.getByText('Required')).toHaveAttribute('id', 'custom-iderror');
|
|
95
97
|
});
|
|
98
|
+
|
|
99
|
+
it('widens the outline in the error state', () => {
|
|
100
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Select/Select.module.css'), 'utf8');
|
|
101
|
+
expect(css).toMatch(/\.selectError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
|
|
102
|
+
});
|
|
96
103
|
});
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
/* Error state — declared before checked/focus so higher-specificity states can override */
|
|
75
75
|
.switch.switchError + .track {
|
|
76
76
|
--bdc_color: var(--switch_border-color--error, var(--bds-color_error));
|
|
77
|
+
--bdc-outline_width: var(--bdc-outline_width--error, 2px);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
/* Checked state via CSS sibling combinator */
|
|
@@ -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 { Switch } from './Switch';
|
|
@@ -88,4 +90,9 @@ describe('Switch', () => {
|
|
|
88
90
|
render(<Switch label="Toggle" name="toggle" role="checkbox" />);
|
|
89
91
|
expect(screen.getByRole('switch')).toBeInTheDocument();
|
|
90
92
|
});
|
|
93
|
+
|
|
94
|
+
it('widens the outline in the error state', () => {
|
|
95
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Switch/Switch.module.css'), 'utf8');
|
|
96
|
+
expect(css).toMatch(/\.switchError \+ \.track \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
|
|
97
|
+
});
|
|
91
98
|
});
|
|
@@ -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 { Textarea } from './Textarea';
|
|
@@ -63,4 +65,9 @@ describe('Textarea', () => {
|
|
|
63
65
|
?.split(' ')
|
|
64
66
|
.forEach((describedById) => expect(describedById.startsWith('custom-id')).toBe(true));
|
|
65
67
|
});
|
|
68
|
+
|
|
69
|
+
it('widens the outline in the error state', () => {
|
|
70
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/Textarea/Textarea.module.css'), 'utf8');
|
|
71
|
+
expect(css).toMatch(/\.textareaError \{[^}]*--bdc-outline_width: var\(--bdc-outline_width--error, 2px\)/);
|
|
72
|
+
});
|
|
66
73
|
});
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
@layer boostdev.component {
|
|
2
2
|
.error,
|
|
3
3
|
.hint {
|
|
4
|
-
font-size: var(--bds-font_size--body--s);
|
|
4
|
+
font-size: var(--formMessage_font-size, var(--bds-font_size--body--s));
|
|
5
5
|
margin-inline-start: var(--labelPosition, 0);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
.error {
|
|
9
|
-
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: flex-start;
|
|
11
|
+
gap: var(--formMessage_gap, var(--bds-space_xxs));
|
|
12
|
+
color: var(--formMessage_color--error, var(--bds-color_error_on-bg));
|
|
13
|
+
font-weight: var(--formMessage_font-weight--error, var(--bds-font_weight--semibold));
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
.hint {
|
|
13
|
-
|
|
17
|
+
color: var(--formMessage_color--hint, inherit);
|
|
18
|
+
font-style: var(--formMessage_font-style--hint, italic);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.icon {
|
|
22
|
+
inline-size: var(--formMessage_icon-size, 1.15em);
|
|
23
|
+
block-size: var(--formMessage_icon-size, 1.15em);
|
|
24
|
+
flex-shrink: 0;
|
|
14
25
|
}
|
|
15
26
|
}
|
|
@@ -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 { Message } from './Message';
|
|
3
5
|
|
|
@@ -26,4 +28,24 @@ describe('Message', () => {
|
|
|
26
28
|
render(<Message inputId="field-1" type="error" message="Required" />);
|
|
27
29
|
expect(screen.getByRole('alert')).toHaveAttribute('id', 'field-1error');
|
|
28
30
|
});
|
|
31
|
+
|
|
32
|
+
it('renders an aria-hidden icon before the error text', () => {
|
|
33
|
+
render(<Message inputId="field-1" type="error" message="Required" />);
|
|
34
|
+
const icon = screen.getByRole('alert').querySelector('svg');
|
|
35
|
+
expect(icon).toHaveAttribute('aria-hidden', 'true');
|
|
36
|
+
expect(screen.getByRole('alert')).toHaveTextContent('Required');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders no icon for a hint', () => {
|
|
40
|
+
render(<Message inputId="field-1" type="hint" message="Hint" />);
|
|
41
|
+
expect(screen.getByText('Hint').querySelector('svg')).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('exposes the message hooks in the stylesheet', () => {
|
|
45
|
+
const css = readFileSync(resolve(process.cwd(), 'src/components/interaction/form/atoms/Message.module.css'), 'utf8');
|
|
46
|
+
['--formMessage_gap', '--formMessage_icon-size', '--formMessage_color--error', '--formMessage_font-weight--error',
|
|
47
|
+
'--formMessage_color--hint', '--formMessage_font-style--hint', '--formMessage_font-size'].forEach((hook) => {
|
|
48
|
+
expect(css).toContain(hook);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
29
51
|
});
|
|
@@ -13,6 +13,11 @@ export const Message = ({ message, type, inputId, className }: MessageProps) =>
|
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
15
|
<p id={inputId + type} role={type === 'error' ? 'alert' : undefined} className={cn(css[type], className)}>
|
|
16
|
+
{type === 'error' && (
|
|
17
|
+
<svg className={css.icon} aria-hidden="true" viewBox="0 0 256 256" fill="currentColor">
|
|
18
|
+
<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-8,56a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,104a12,12,0,1,1,12-12A12,12,0,0,1,128,184Z" />
|
|
19
|
+
</svg>
|
|
20
|
+
)}
|
|
16
21
|
{message}
|
|
17
22
|
</p>
|
|
18
23
|
);
|
|
@@ -31,6 +31,17 @@ Heading block with optional subtitle for introducing a content section.
|
|
|
31
31
|
### Centered
|
|
32
32
|
<Canvas of={Stories.Centered} />
|
|
33
33
|
|
|
34
|
+
### Gradient
|
|
35
|
+
Set `appearance="gradient"` for a gradient-filled title. The gradient spans the text, not the row. Where `background-clip: text` is unsupported the title keeps its normal colour; in forced-colors mode it uses `CanvasText`.
|
|
36
|
+
|
|
37
|
+
<Canvas of={Stories.Gradient} />
|
|
38
|
+
|
|
39
|
+
<Canvas of={Stories.GradientExtraSmall} />
|
|
40
|
+
|
|
41
|
+
<Canvas of={Stories.GradientSmall} />
|
|
42
|
+
|
|
43
|
+
<Canvas of={Stories.GradientLarge} />
|
|
44
|
+
|
|
34
45
|
## Props
|
|
35
46
|
|
|
36
47
|
<ArgTypes of={Stories} />
|
|
@@ -43,12 +54,14 @@ Heading block with optional subtitle for introducing a content section.
|
|
|
43
54
|
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
|
44
55
|
</thead>
|
|
45
56
|
<tbody>
|
|
46
|
-
<tr><td>`--sectionHeader_title-color`</td><td>`var(--bds-color_on-bg)`</td><td>Title text colour
|
|
47
|
-
<tr><td>`--sectionHeader_title-
|
|
57
|
+
<tr><td>`--sectionHeader_title-color`</td><td>`var(--bds-color_on-bg)`</td><td>Title text colour; no effect with `appearance="gradient"`, where the colour comes from `--sectionHeader_title-gradient`</td></tr>
|
|
58
|
+
<tr><td>`--sectionHeader_title-font-family`</td><td>`var(--bds-font_family--heading)`</td><td>Title font family</td></tr>
|
|
59
|
+
<tr><td>`--sectionHeader_title-gradient`</td><td>`linear-gradient(90deg, var(--bds-color_primary--strong), var(--bds-color_accent-3--strong))`</td><td>Title gradient, only with `appearance="gradient"`</td></tr>
|
|
60
|
+
<tr><td>`--sectionHeader_title-size`</td><td>`xs` body, `small` heading-3, `medium` heading-2, `large` heading-1</td><td>Title font-size (overrides the variant default)</td></tr>
|
|
48
61
|
<tr><td>`--sectionHeader_title-line-height`</td><td>varies by `size` prop</td><td>Title line-height</td></tr>
|
|
49
|
-
<tr><td>`--sectionHeader_title-weight`</td><td>`var(--bds-font_weight--
|
|
50
|
-
<tr><td>`--sectionHeader_subtitle-color`</td><td>`var(--bds-
|
|
51
|
-
<tr><td>`--sectionHeader_subtitle-size`</td><td
|
|
62
|
+
<tr><td>`--sectionHeader_title-weight`</td><td>`var(--bds-font_weight--heading)`</td><td>Title font-weight</td></tr>
|
|
63
|
+
<tr><td>`--sectionHeader_subtitle-color`</td><td>`var(--bds-color_on-bg--muted)`</td><td>Subtitle text colour</td></tr>
|
|
64
|
+
<tr><td>`--sectionHeader_subtitle-size`</td><td>`xs` body--s, `small`/`medium` body, `large` heading-3</td><td>Subtitle font-size</td></tr>
|
|
52
65
|
<tr><td>`--sectionHeader_subtitle-line-height`</td><td>`1.4` (`xs`/`small`), `1.3` (`medium`/`large`)</td><td>Subtitle line-height</td></tr>
|
|
53
66
|
<tr><td>`--sectionHeader_gap`</td><td>`var(--bds-space_m)`</td><td>Gap between title and subtitle</td></tr>
|
|
54
67
|
</tbody>
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
/* --sectionHeader-title-color (kebab delimiter) is deprecated — use --sectionHeader_title-color */
|
|
11
11
|
color: var(--sectionHeader_title-color, var(--sectionHeader-title-color, var(--bds-color_on-bg)));
|
|
12
12
|
margin: 0;
|
|
13
|
-
font-family: var(--bds-font_family--
|
|
13
|
+
font-family: var(--sectionHeader_title-font-family, var(--bds-font_family--heading));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
.subtitle {
|
|
17
|
-
color: var(--sectionHeader_subtitle-color, var(--bds-color_grey--strong));
|
|
17
|
+
color: var(--sectionHeader_subtitle-color, var(--bds-color_on-bg--muted, var(--bds-color_grey--strong)));
|
|
18
18
|
margin: 0;
|
|
19
19
|
font-family: var(--bds-font_family--body);
|
|
20
20
|
font-weight: var(--bds-font_weight--medium);
|
|
@@ -37,31 +37,31 @@
|
|
|
37
37
|
|
|
38
38
|
/* medium first — default size, so MCP extraction picks up its fallbacks */
|
|
39
39
|
.sectionHeader.--medium .title {
|
|
40
|
-
font-size: var(--sectionHeader_title-size, var(--bds-font_size--heading-
|
|
40
|
+
font-size: var(--sectionHeader_title-size, var(--bds-font_size--heading-2));
|
|
41
41
|
line-height: var(--sectionHeader_title-line-height, var(--bds-font_line-height--heading));
|
|
42
|
-
font-weight: var(--sectionHeader_title-weight, var(--bds-font_weight--bold));
|
|
42
|
+
font-weight: var(--sectionHeader_title-weight, var(--bds-font_weight--heading, var(--bds-font_weight--bold)));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
.sectionHeader.--medium .subtitle {
|
|
46
|
-
font-size: var(--sectionHeader_subtitle-size, var(--bds-font_size--
|
|
46
|
+
font-size: var(--sectionHeader_subtitle-size, var(--bds-font_size--body));
|
|
47
47
|
line-height: var(--sectionHeader_subtitle-line-height, 1.3);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
.sectionHeader.--xs .title {
|
|
51
|
-
font-size: var(--sectionHeader_title-size, var(--bds-font_size--
|
|
51
|
+
font-size: var(--sectionHeader_title-size, var(--bds-font_size--body));
|
|
52
52
|
line-height: var(--sectionHeader_title-line-height, var(--bds-font_line-height--heading));
|
|
53
|
-
font-weight: var(--sectionHeader_title-weight, var(--bds-font_weight--bold));
|
|
53
|
+
font-weight: var(--sectionHeader_title-weight, var(--bds-font_weight--heading, var(--bds-font_weight--bold)));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
.sectionHeader.--xs .subtitle {
|
|
57
|
-
font-size: var(--sectionHeader_subtitle-size, var(--bds-font_size--body));
|
|
57
|
+
font-size: var(--sectionHeader_subtitle-size, var(--bds-font_size--body--s));
|
|
58
58
|
line-height: var(--sectionHeader_subtitle-line-height, 1.4);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
.sectionHeader.--small .title {
|
|
62
|
-
font-size: var(--sectionHeader_title-size, var(--bds-font_size--heading-
|
|
62
|
+
font-size: var(--sectionHeader_title-size, var(--bds-font_size--heading-3));
|
|
63
63
|
line-height: var(--sectionHeader_title-line-height, var(--bds-font_line-height--heading));
|
|
64
|
-
font-weight: var(--sectionHeader_title-weight, var(--bds-font_weight--bold));
|
|
64
|
+
font-weight: var(--sectionHeader_title-weight, var(--bds-font_weight--heading, var(--bds-font_weight--bold)));
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
.sectionHeader.--small .subtitle {
|
|
@@ -70,15 +70,36 @@
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
.sectionHeader.--large .title {
|
|
73
|
-
font-size: var(--sectionHeader_title-size, var(--bds-font_size--
|
|
74
|
-
line-height: var(--sectionHeader_title-line-height, var(--bds-font_line-height--
|
|
75
|
-
font-weight: var(--sectionHeader_title-weight, var(--bds-font_weight--bold));
|
|
73
|
+
font-size: var(--sectionHeader_title-size, var(--bds-font_size--heading-1));
|
|
74
|
+
line-height: var(--sectionHeader_title-line-height, var(--bds-font_line-height--heading));
|
|
75
|
+
font-weight: var(--sectionHeader_title-weight, var(--bds-font_weight--heading, var(--bds-font_weight--bold)));
|
|
76
76
|
letter-spacing: var(--bds-font_tracking--tight);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
.sectionHeader.--large .subtitle {
|
|
80
|
-
font-size: var(--sectionHeader_subtitle-size, var(--bds-font_size--heading-
|
|
80
|
+
font-size: var(--sectionHeader_subtitle-size, var(--bds-font_size--heading-3));
|
|
81
81
|
line-height: var(--sectionHeader_subtitle-line-height, 1.3);
|
|
82
82
|
font-weight: var(--bds-font_weight--medium);
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
/* Gradient only where background-clip: text is supported; elsewhere the title keeps its normal colour */
|
|
86
|
+
@supports (background-clip: text) {
|
|
87
|
+
.sectionHeader.--gradient .title {
|
|
88
|
+
inline-size: fit-content;
|
|
89
|
+
max-inline-size: 100%;
|
|
90
|
+
background-image: var(--sectionHeader_title-gradient, linear-gradient(90deg, var(--bds-color_primary--strong, var(--bds-color_blue--strong)), var(--bds-color_accent-3--strong, var(--bds-color_blue--strong))));
|
|
91
|
+
background-clip: text;
|
|
92
|
+
color: transparent;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@media (forced-colors: active) {
|
|
97
|
+
.sectionHeader.--gradient .title {
|
|
98
|
+
background-image: none;
|
|
99
|
+
|
|
100
|
+
/* system colour keyword: forced-colors mode ignores tokens */
|
|
101
|
+
/* stylelint-disable-next-line scale-unlimited/declaration-strict-value */
|
|
102
|
+
color: CanvasText;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
84
105
|
}
|
|
@@ -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>
|
|
@@ -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
|
|
@@ -42,6 +45,32 @@
|
|
|
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,25 @@ 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
|
+
});
|
|
83
104
|
});
|
|
@@ -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
|
+
};
|