@boostdev/design-system-components 1.0.3 → 1.1.1
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 +51 -10
- package/dist/client.cjs +350 -174
- package/dist/client.css +751 -647
- package/dist/client.d.cts +62 -97
- package/dist/client.d.ts +62 -97
- package/dist/client.js +377 -211
- package/dist/index.cjs +350 -174
- package/dist/index.css +751 -647
- package/dist/index.d.cts +62 -97
- package/dist/index.d.ts +62 -97
- package/dist/index.js +377 -211
- package/dist/native/index.cjs +991 -0
- package/dist/native/index.d.cts +208 -0
- package/dist/native/index.d.ts +208 -0
- package/dist/native/index.js +968 -0
- package/dist/web-components.d.ts +304 -0
- package/dist/web-components.js +1978 -0
- package/package.json +27 -5
- package/src/components/interaction/Button/Button.module.css +11 -8
- package/src/components/interaction/Button/Button.native.mdx +72 -0
- package/src/components/interaction/Button/Button.native.spec.tsx +35 -0
- package/src/components/interaction/Button/Button.native.stories.tsx +42 -0
- package/src/components/interaction/Button/Button.native.tsx +95 -0
- package/src/components/interaction/Button/Button.stories.tsx +1 -1
- package/src/components/interaction/Button/Button.tsx +2 -2
- package/src/components/interaction/Command/Command.mdx +16 -0
- package/src/components/interaction/Command/Command.module.css +7 -7
- package/src/components/interaction/Command/Command.spec.tsx +37 -0
- package/src/components/interaction/Command/Command.stories.tsx +1 -1
- package/src/components/interaction/Command/Command.tsx +39 -4
- package/src/components/interaction/Dialog/Dialog.module.css +10 -13
- package/src/components/interaction/Dialog/Dialog.spec.tsx +53 -1
- package/src/components/interaction/Dialog/Dialog.stories.tsx +18 -1
- package/src/components/interaction/Dialog/Dialog.tsx +52 -18
- package/src/components/interaction/Drawer/Drawer.mdx +14 -0
- package/src/components/interaction/Drawer/Drawer.module.css +11 -11
- package/src/components/interaction/Drawer/Drawer.spec.tsx +48 -1
- package/src/components/interaction/Drawer/Drawer.stories.tsx +1 -1
- package/src/components/interaction/Drawer/Drawer.tsx +40 -6
- package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +6 -6
- package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +1 -1
- package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +2 -2
- package/src/components/interaction/Popover/Popover.mdx +17 -0
- package/src/components/interaction/Popover/Popover.module.css +132 -16
- package/src/components/interaction/Popover/Popover.spec.tsx +119 -20
- package/src/components/interaction/Popover/Popover.stories.tsx +1 -1
- package/src/components/interaction/Popover/Popover.tsx +69 -29
- package/src/components/interaction/Rating/Rating.stories.tsx +1 -1
- package/src/components/interaction/Rating/Rating.tsx +2 -2
- package/src/components/interaction/Toast/Toast.module.css +13 -8
- package/src/components/interaction/Toast/Toast.spec.tsx +21 -0
- package/src/components/interaction/Toast/Toast.stories.tsx +1 -1
- package/src/components/interaction/Toast/Toast.tsx +22 -2
- package/src/components/interaction/form/Checkbox/Checkbox.mdx +2 -2
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +9 -9
- package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +74 -0
- package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +42 -0
- package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +36 -0
- package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +80 -0
- package/src/components/interaction/form/Checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/interaction/form/Checkbox/Checkbox.tsx +2 -2
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +44 -0
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.stories.tsx +1 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +2 -2
- package/src/components/interaction/form/Combobox/Combobox.mdx +21 -0
- package/src/components/interaction/form/Combobox/Combobox.module.css +8 -9
- package/src/components/interaction/form/Combobox/Combobox.stories.tsx +1 -1
- package/src/components/interaction/form/Combobox/Combobox.tsx +2 -2
- package/src/components/interaction/form/FileInput/FileInput.mdx +14 -0
- package/src/components/interaction/form/FileInput/FileInput.module.css +5 -5
- package/src/components/interaction/form/FileInput/FileInput.stories.tsx +1 -1
- package/src/components/interaction/form/FileInput/FileInput.tsx +2 -2
- package/src/components/interaction/form/FormInput/FormInput.stories.tsx +1 -1
- package/src/components/interaction/form/FormInput/FormInput.tsx +2 -2
- package/src/components/interaction/form/NumberInput/NumberInput.mdx +16 -0
- package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
- package/src/components/interaction/form/NumberInput/NumberInput.stories.tsx +1 -1
- package/src/components/interaction/form/NumberInput/NumberInput.tsx +2 -2
- package/src/components/interaction/form/Radio/Radio.mdx +3 -3
- package/src/components/interaction/form/Radio/Radio.module.css +9 -9
- package/src/components/interaction/form/Radio/Radio.native.stories.tsx +46 -0
- package/src/components/interaction/form/Radio/Radio.native.tsx +79 -0
- package/src/components/interaction/form/Radio/Radio.stories.tsx +1 -1
- package/src/components/interaction/form/Radio/Radio.tsx +2 -2
- package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +45 -0
- package/src/components/interaction/form/RadioGroup/RadioGroup.stories.tsx +1 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +2 -2
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +5 -6
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +1 -1
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +2 -2
- package/src/components/interaction/form/Select/Select.module.css +3 -3
- package/src/components/interaction/form/Select/Select.stories.tsx +1 -1
- package/src/components/interaction/form/Select/Select.tsx +2 -2
- package/src/components/interaction/form/Slider/Slider.mdx +1 -1
- package/src/components/interaction/form/Slider/Slider.module.css +10 -10
- package/src/components/interaction/form/Slider/Slider.stories.tsx +1 -1
- package/src/components/interaction/form/Slider/Slider.tsx +2 -2
- package/src/components/interaction/form/Switch/Switch.mdx +4 -4
- package/src/components/interaction/form/Switch/Switch.module.css +11 -11
- package/src/components/interaction/form/Switch/Switch.native.spec.tsx +60 -0
- package/src/components/interaction/form/Switch/Switch.native.stories.tsx +35 -0
- package/src/components/interaction/form/Switch/Switch.native.tsx +59 -0
- package/src/components/interaction/form/Switch/Switch.stories.tsx +1 -1
- package/src/components/interaction/form/Switch/Switch.tsx +2 -2
- package/src/components/interaction/form/Textarea/Textarea.mdx +17 -0
- package/src/components/interaction/form/Textarea/Textarea.module.css +1 -1
- package/src/components/interaction/form/Textarea/Textarea.stories.tsx +1 -1
- package/src/components/interaction/form/Textarea/Textarea.tsx +2 -2
- package/src/components/interaction/form/atoms/InputContainer.tsx +2 -1
- package/src/components/interaction/form/atoms/Label.native.stories.tsx +18 -0
- package/src/components/interaction/form/atoms/Label.native.tsx +29 -0
- package/src/components/interaction/form/atoms/Label.tsx +5 -3
- package/src/components/interaction/form/atoms/Message.native.spec.tsx +30 -0
- package/src/components/interaction/form/atoms/Message.native.stories.tsx +22 -0
- package/src/components/interaction/form/atoms/Message.native.tsx +34 -0
- package/src/components/interaction/form/atoms/Message.tsx +5 -3
- package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +50 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +34 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +1 -1
- package/src/components/layout/ButtonGroup/ButtonGroup.tsx +2 -2
- package/src/components/layout/Card/Card.native.stories.tsx +53 -0
- package/src/components/layout/Card/Card.native.tsx +89 -0
- package/src/components/layout/Card/Card.stories.tsx +1 -1
- package/src/components/layout/Card/Card.tsx +2 -2
- package/src/components/layout/IconWrapper/IconWrapper.mdx +2 -2
- package/src/components/layout/IconWrapper/IconWrapper.module.css +2 -2
- package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +39 -0
- package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +41 -0
- package/src/components/layout/IconWrapper/IconWrapper.native.tsx +20 -0
- package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +1 -1
- package/src/components/layout/IconWrapper/IconWrapper.tsx +2 -2
- package/src/components/layout/SectionHeader/SectionHeader.mdx +13 -0
- package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +38 -0
- package/src/components/layout/SectionHeader/SectionHeader.native.tsx +79 -0
- package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +1 -1
- package/src/components/layout/SectionHeader/SectionHeader.tsx +2 -2
- package/src/components/ui/Accordion/Accordion.mdx +14 -0
- package/src/components/ui/Accordion/Accordion.module.css +5 -5
- package/src/components/ui/Accordion/Accordion.stories.tsx +1 -1
- package/src/components/ui/Accordion/Accordion.tsx +2 -2
- package/src/components/ui/Alert/Alert.native.mdx +62 -0
- package/src/components/ui/Alert/Alert.native.stories.tsx +43 -0
- package/src/components/ui/Alert/Alert.native.tsx +94 -0
- package/src/components/ui/Alert/Alert.stories.tsx +1 -1
- package/src/components/ui/Alert/Alert.tsx +2 -2
- package/src/components/ui/Avatar/Avatar.native.mdx +50 -0
- package/src/components/ui/Avatar/Avatar.native.spec.tsx +47 -0
- package/src/components/ui/Avatar/Avatar.native.stories.tsx +34 -0
- package/src/components/ui/Avatar/Avatar.native.tsx +85 -0
- package/src/components/ui/Avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/Avatar/Avatar.tsx +2 -2
- package/src/components/ui/Badge/Badge.mdx +2 -2
- package/src/components/ui/Badge/Badge.native.mdx +54 -0
- package/src/components/ui/Badge/Badge.native.stories.tsx +36 -0
- package/src/components/ui/Badge/Badge.native.tsx +50 -0
- package/src/components/ui/Badge/Badge.stories.tsx +1 -1
- package/src/components/ui/Badge/Badge.tsx +2 -2
- package/src/components/ui/Breadcrumb/Breadcrumb.stories.tsx +1 -1
- package/src/components/ui/Breadcrumb/Breadcrumb.tsx +2 -2
- package/src/components/ui/Calendar/Calendar.mdx +16 -0
- package/src/components/ui/Calendar/Calendar.module.css +7 -7
- package/src/components/ui/Calendar/Calendar.stories.tsx +1 -1
- package/src/components/ui/Calendar/Calendar.tsx +2 -2
- package/src/components/ui/Carousel/Carousel.module.css +5 -5
- package/src/components/ui/Carousel/Carousel.stories.tsx +1 -1
- package/src/components/ui/Carousel/Carousel.tsx +2 -2
- package/src/components/ui/Collapsible/Collapsible.module.css +4 -4
- package/src/components/ui/Collapsible/Collapsible.stories.tsx +1 -1
- package/src/components/ui/Collapsible/Collapsible.tsx +2 -2
- package/src/components/ui/DescriptionList/DescriptionList.stories.tsx +1 -1
- package/src/components/ui/DescriptionList/DescriptionList.tsx +2 -2
- package/src/components/ui/Link/Link.mdx +14 -0
- package/src/components/ui/Link/Link.module.css +2 -2
- package/src/components/ui/Link/Link.stories.tsx +1 -1
- package/src/components/ui/Link/Link.tsx +2 -2
- package/src/components/ui/Loading/Loading.module.css +7 -7
- package/src/components/ui/Loading/Loading.native.spec.tsx +24 -0
- package/src/components/ui/Loading/Loading.native.stories.tsx +33 -0
- package/src/components/ui/Loading/Loading.native.tsx +29 -0
- package/src/components/ui/Loading/Loading.stories.tsx +1 -1
- package/src/components/ui/Loading/Loading.tsx +2 -2
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +3 -3
- package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +39 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +76 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +1 -1
- package/src/components/ui/NotificationBanner/NotificationBanner.tsx +2 -2
- package/src/components/ui/Pagination/Pagination.module.css +7 -7
- package/src/components/ui/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/ui/Pagination/Pagination.tsx +2 -2
- package/src/components/ui/Progress/Progress.mdx +1 -1
- package/src/components/ui/Progress/Progress.module.css +6 -6
- package/src/components/ui/Progress/Progress.native.stories.tsx +34 -0
- package/src/components/ui/Progress/Progress.native.tsx +84 -0
- package/src/components/ui/Progress/Progress.stories.tsx +1 -1
- package/src/components/ui/Progress/Progress.tsx +2 -2
- package/src/components/ui/ProgressCircle/ProgressCircle.module.css +1 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.stories.tsx +1 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.tsx +2 -2
- package/src/components/ui/Separator/Separator.mdx +14 -0
- package/src/components/ui/Separator/Separator.module.css +3 -3
- package/src/components/ui/Separator/Separator.native.stories.tsx +42 -0
- package/src/components/ui/Separator/Separator.native.tsx +32 -0
- package/src/components/ui/Separator/Separator.stories.tsx +1 -1
- package/src/components/ui/Separator/Separator.tsx +2 -2
- package/src/components/ui/Skeleton/Skeleton.module.css +1 -1
- package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +33 -0
- package/src/components/ui/Skeleton/Skeleton.native.tsx +41 -0
- package/src/components/ui/Skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/Skeleton/Skeleton.tsx +2 -3
- package/src/components/ui/SkipLink/SkipLink.stories.tsx +1 -1
- package/src/components/ui/SkipLink/SkipLink.tsx +5 -3
- package/src/components/ui/Table/Table.mdx +14 -0
- package/src/components/ui/Table/Table.module.css +9 -9
- package/src/components/ui/Table/Table.stories.tsx +1 -1
- package/src/components/ui/Table/Table.tsx +2 -2
- package/src/components/ui/Tabs/Tabs.module.css +3 -3
- package/src/components/ui/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/Tabs/Tabs.tsx +2 -2
- package/src/components/ui/Tooltip/Tooltip.mdx +14 -0
- package/src/components/ui/Tooltip/Tooltip.module.css +8 -8
- package/src/components/ui/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/ui/Tooltip/Tooltip.tsx +2 -2
- package/src/components/ui/Typography/Typography.mdx +13 -0
- package/src/components/ui/Typography/Typography.native.mdx +56 -0
- package/src/components/ui/Typography/Typography.native.stories.tsx +38 -0
- package/src/components/ui/Typography/Typography.native.tsx +65 -0
- package/src/components/ui/Typography/Typography.stories.tsx +1 -1
- package/src/components/ui/Typography/Typography.tsx +2 -2
- package/src/css/bdc.css +8 -0
- package/src/index.ts +1 -0
- package/src/native/ThemeContext.tsx +28 -0
- package/src/native/tokens.ts +13 -0
- package/src/native.ts +39 -0
- package/src/polyfill-invoker-commands.ts +68 -0
- package/src/react-augment.d.ts +32 -0
- package/src/stories/DesignSystem/DarkMode.mdx +130 -0
- package/src/stories/ReactNative.mdx +121 -0
- package/src/types.ts +2 -0
- package/src/typings.d.ts +3 -0
- package/src/web-components/globals.ts +61 -0
- package/src/web-components/index.ts +12 -0
- package/src/web-components/interaction/BdsButton.mdx +106 -0
- package/src/web-components/interaction/BdsButton.stories.tsx +60 -0
- package/src/web-components/interaction/BdsPopover.mdx +120 -0
- package/src/web-components/interaction/BdsPopover.stories.tsx +70 -0
- package/src/web-components/interaction/BdsToastProvider.mdx +94 -0
- package/src/web-components/interaction/BdsToastProvider.stories.tsx +73 -0
- package/src/web-components/interaction/bds-button.spec.ts +95 -0
- package/src/web-components/interaction/bds-button.ts +293 -0
- package/src/web-components/interaction/bds-popover.spec.ts +174 -0
- package/src/web-components/interaction/bds-popover.ts +229 -0
- package/src/web-components/interaction/bds-toast-provider.spec.ts +122 -0
- package/src/web-components/interaction/bds-toast-provider.ts +211 -0
- package/src/web-components/test/helpers.ts +22 -0
- package/src/web-components/ui/BdsAlert.mdx +90 -0
- package/src/web-components/ui/BdsAlert.stories.tsx +60 -0
- package/src/web-components/ui/BdsBadge.mdx +74 -0
- package/src/web-components/ui/BdsBadge.stories.tsx +37 -0
- package/src/web-components/ui/bds-alert.spec.ts +109 -0
- package/src/web-components/ui/bds-alert.ts +209 -0
- package/src/web-components/ui/bds-badge.spec.ts +51 -0
- package/src/web-components/ui/bds-badge.ts +88 -0
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
@layer component {
|
|
2
2
|
.wrapper {
|
|
3
|
-
position: relative;
|
|
4
3
|
display: inline-flex;
|
|
4
|
+
|
|
5
|
+
/* No position: relative — the panel is position: fixed so it doesn't need
|
|
6
|
+
the wrapper as a containing block. */
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
.panel {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
/* Fixed positioning: containing block is the viewport.
|
|
11
|
+
- CSS anchor() values resolve in viewport space → polyfill applies them correctly.
|
|
12
|
+
- Ancestor overflow / stacking contexts cannot clip the panel. */
|
|
13
|
+
position: fixed;
|
|
14
|
+
|
|
15
|
+
/* Reset UA [popover] defaults (inset: 0, margin: auto, border, padding). */
|
|
16
|
+
inset: unset;
|
|
17
|
+
margin: 0;
|
|
18
|
+
overflow-block: auto;
|
|
19
|
+
z-index: var(--popover_z-index, 100);
|
|
20
|
+
min-inline-size: 12rem;
|
|
21
|
+
max-inline-size: var(--popover_max-width, 20rem);
|
|
22
|
+
max-block-size: var(--popover_max-height, 80dvh);
|
|
11
23
|
padding: var(--bds-space_m);
|
|
12
24
|
border: var(--popover_border, none);
|
|
13
25
|
--bdc_color: currentcolor;
|
|
@@ -22,23 +34,127 @@
|
|
|
22
34
|
font-size: var(--bds-font_size--body);
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
/* Primary placements via CSS anchor positioning.
|
|
38
|
+
anchor() resolves relative to the element tagged with the matching anchor-name.
|
|
39
|
+
@position-try fallbacks are tried in order when the primary placement overflows. */
|
|
40
|
+
|
|
41
|
+
.panel[data-placement='bottom'] {
|
|
42
|
+
top: anchor(bottom);
|
|
43
|
+
left: anchor(left);
|
|
44
|
+
margin-block-start: var(--bds-space_xs, 8px);
|
|
45
|
+
position-try-fallbacks: --popover-top, --popover-right, --popover-left;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.panel[data-placement='top'] {
|
|
49
|
+
bottom: anchor(top);
|
|
50
|
+
left: anchor(left);
|
|
51
|
+
margin-block-end: var(--bds-space_xs, 8px);
|
|
52
|
+
position-try-fallbacks: --popover-bottom, --popover-right, --popover-left;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.panel[data-placement='right'] {
|
|
56
|
+
top: anchor(top);
|
|
57
|
+
left: anchor(right);
|
|
58
|
+
margin-inline-start: var(--bds-space_xs, 8px);
|
|
59
|
+
position-try-fallbacks: --popover-left, --popover-bottom, --popover-top;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.panel[data-placement='left'] {
|
|
63
|
+
top: anchor(top);
|
|
64
|
+
right: anchor(left);
|
|
65
|
+
margin-inline-end: var(--bds-space_xs, 8px);
|
|
66
|
+
position-try-fallbacks: --popover-right, --popover-bottom, --popover-top;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Fallback placements — used by the browser (or polyfill) when the primary
|
|
70
|
+
placement would overflow the viewport. Each block resets all inset/margin
|
|
71
|
+
properties so nothing bleeds in from the primary placement. */
|
|
72
|
+
|
|
73
|
+
@position-try --popover-bottom {
|
|
74
|
+
top: anchor(bottom);
|
|
75
|
+
right: unset;
|
|
76
|
+
bottom: unset;
|
|
77
|
+
left: anchor(left);
|
|
78
|
+
margin: 0;
|
|
79
|
+
margin-block-start: var(--bds-space_xs, 8px);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@position-try --popover-top {
|
|
83
|
+
top: unset;
|
|
84
|
+
right: unset;
|
|
85
|
+
bottom: anchor(top);
|
|
86
|
+
left: anchor(left);
|
|
87
|
+
margin: 0;
|
|
88
|
+
margin-block-end: var(--bds-space_xs, 8px);
|
|
28
89
|
}
|
|
29
90
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
91
|
+
@position-try --popover-right {
|
|
92
|
+
top: anchor(top);
|
|
93
|
+
right: unset;
|
|
94
|
+
bottom: unset;
|
|
95
|
+
left: anchor(right);
|
|
96
|
+
margin: 0;
|
|
97
|
+
margin-inline-start: var(--bds-space_xs, 8px);
|
|
33
98
|
}
|
|
34
99
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
100
|
+
@position-try --popover-left {
|
|
101
|
+
top: anchor(top);
|
|
102
|
+
right: anchor(left);
|
|
103
|
+
bottom: unset;
|
|
104
|
+
left: unset;
|
|
105
|
+
margin: 0;
|
|
106
|
+
margin-inline-end: var(--bds-space_xs, 8px);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Full-width fallback blocks for narrow screens.
|
|
110
|
+
Defined at layer level (not inside @media) because @position-try cannot
|
|
111
|
+
be nested inside @media. These preserve left:8px/right:8px so the
|
|
112
|
+
panel stays edge-to-edge even when a @position-try fallback fires. */
|
|
113
|
+
|
|
114
|
+
@position-try --popover-bottom-fullwidth {
|
|
115
|
+
top: anchor(bottom);
|
|
116
|
+
right: var(--bds-space_xs, 8px);
|
|
117
|
+
bottom: unset;
|
|
118
|
+
left: var(--bds-space_xs, 8px);
|
|
119
|
+
margin: 0;
|
|
120
|
+
margin-block-start: var(--bds-space_xs, 8px);
|
|
38
121
|
}
|
|
39
122
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
123
|
+
@position-try --popover-top-fullwidth {
|
|
124
|
+
top: unset;
|
|
125
|
+
right: var(--bds-space_xs, 8px);
|
|
126
|
+
bottom: anchor(top);
|
|
127
|
+
left: var(--bds-space_xs, 8px);
|
|
128
|
+
margin: 0;
|
|
129
|
+
margin-block-end: var(--bds-space_xs, 8px);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* Narrow screens (≤420px): span full viewport width and only flip vertically.
|
|
133
|
+
max-width: none overrides the base 20rem cap; left/right edge constraints
|
|
134
|
+
handle the width instead. The panel still touches the anchor on the
|
|
135
|
+
vertical axis (top/bottom via anchor() is not overridden here). */
|
|
136
|
+
@media (width <= 420px) {
|
|
137
|
+
.panel[data-placement='bottom'],
|
|
138
|
+
.panel[data-placement='top'],
|
|
139
|
+
.panel[data-placement='left'],
|
|
140
|
+
.panel[data-placement='right'] {
|
|
141
|
+
left: var(--bds-space_xs, 8px);
|
|
142
|
+
right: var(--bds-space_xs, 8px);
|
|
143
|
+
inline-size: 100%;
|
|
144
|
+
max-inline-size: calc(100svw - 2 * var(--bds-space_xs, 8px));
|
|
145
|
+
position-try-fallbacks: --popover-top-fullwidth, --popover-bottom-fullwidth;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Wide + short screens (e.g. landscape mobile, split-screen): when the
|
|
150
|
+
viewport has more horizontal room than vertical, prefer side placements. */
|
|
151
|
+
@media (aspect-ratio >= 2/1) {
|
|
152
|
+
.panel[data-placement='bottom'] {
|
|
153
|
+
position-try-fallbacks: --popover-top, --popover-right, --popover-left;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.panel[data-placement='top'] {
|
|
157
|
+
position-try-fallbacks: --popover-bottom, --popover-right, --popover-left;
|
|
158
|
+
}
|
|
43
159
|
}
|
|
44
160
|
}
|
|
@@ -2,6 +2,47 @@ import { render, screen } from '@testing-library/react';
|
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { Popover } from './Popover';
|
|
4
4
|
|
|
5
|
+
// jsdom doesn't implement CSS.supports — stub so the anchor polyfill guard doesn't throw.
|
|
6
|
+
beforeAll(() => {
|
|
7
|
+
Object.defineProperty(globalThis, 'CSS', {
|
|
8
|
+
value: { supports: () => true },
|
|
9
|
+
configurable: true,
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// jsdom doesn't implement the Popover API — mock showPopover/hidePopover/togglePopover
|
|
14
|
+
// and dispatch the `toggle` event so our state-sync listener fires.
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
17
|
+
const event = new Event('toggle');
|
|
18
|
+
Object.defineProperty(event, 'newState', { value: 'open', configurable: true });
|
|
19
|
+
this.dispatchEvent(event);
|
|
20
|
+
});
|
|
21
|
+
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
|
|
22
|
+
const event = new Event('toggle');
|
|
23
|
+
Object.defineProperty(event, 'newState', { value: 'closed', configurable: true });
|
|
24
|
+
this.dispatchEvent(event);
|
|
25
|
+
});
|
|
26
|
+
HTMLElement.prototype.togglePopover = vi.fn(function (this: HTMLElement) {
|
|
27
|
+
const isCurrentlyOpen = this.getAttribute('data-popover-open') === 'true';
|
|
28
|
+
if (isCurrentlyOpen) {
|
|
29
|
+
this.setAttribute('data-popover-open', 'false');
|
|
30
|
+
const event = new Event('toggle');
|
|
31
|
+
Object.defineProperty(event, 'newState', { value: 'closed', configurable: true });
|
|
32
|
+
this.dispatchEvent(event);
|
|
33
|
+
} else {
|
|
34
|
+
this.setAttribute('data-popover-open', 'true');
|
|
35
|
+
const event = new Event('toggle');
|
|
36
|
+
Object.defineProperty(event, 'newState', { value: 'open', configurable: true });
|
|
37
|
+
this.dispatchEvent(event);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function getPanel() {
|
|
43
|
+
return document.querySelector<HTMLElement>('[popover]')!;
|
|
44
|
+
}
|
|
45
|
+
|
|
5
46
|
describe('Popover', () => {
|
|
6
47
|
it('renders the trigger', () => {
|
|
7
48
|
render(
|
|
@@ -12,13 +53,23 @@ describe('Popover', () => {
|
|
|
12
53
|
expect(screen.getByRole('button', { name: 'Open' })).toBeInTheDocument();
|
|
13
54
|
});
|
|
14
55
|
|
|
15
|
-
it('
|
|
56
|
+
it('renders the panel as a [popover] element', () => {
|
|
16
57
|
render(
|
|
17
58
|
<Popover content={<p>Panel content</p>}>
|
|
18
59
|
<button type="button">Open</button>
|
|
19
60
|
</Popover>
|
|
20
61
|
);
|
|
21
|
-
expect(
|
|
62
|
+
expect(getPanel()).toBeInTheDocument();
|
|
63
|
+
expect(getPanel()).toHaveAttribute('popover', 'auto');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('panel is not open by default', () => {
|
|
67
|
+
render(
|
|
68
|
+
<Popover content={<p>Panel content</p>}>
|
|
69
|
+
<button type="button">Open</button>
|
|
70
|
+
</Popover>
|
|
71
|
+
);
|
|
72
|
+
expect(screen.getByRole('button').getAttribute('aria-expanded')).toBe('false');
|
|
22
73
|
});
|
|
23
74
|
|
|
24
75
|
it('shows the panel when trigger is clicked', async () => {
|
|
@@ -29,7 +80,20 @@ describe('Popover', () => {
|
|
|
29
80
|
</Popover>
|
|
30
81
|
);
|
|
31
82
|
await user.click(screen.getByRole('button', { name: 'Open' }));
|
|
32
|
-
expect(
|
|
83
|
+
expect(HTMLElement.prototype.togglePopover).toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('sets aria-expanded true after opening', async () => {
|
|
87
|
+
const user = userEvent.setup();
|
|
88
|
+
render(
|
|
89
|
+
<Popover content={<p>Panel</p>}>
|
|
90
|
+
<button type="button">Open</button>
|
|
91
|
+
</Popover>
|
|
92
|
+
);
|
|
93
|
+
const trigger = screen.getByRole('button');
|
|
94
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
95
|
+
await user.click(trigger);
|
|
96
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
33
97
|
});
|
|
34
98
|
|
|
35
99
|
it('closes the panel when trigger is clicked again', async () => {
|
|
@@ -41,32 +105,68 @@ describe('Popover', () => {
|
|
|
41
105
|
);
|
|
42
106
|
await user.click(screen.getByRole('button', { name: 'Toggle' }));
|
|
43
107
|
await user.click(screen.getByRole('button', { name: 'Toggle' }));
|
|
44
|
-
expect(screen.
|
|
108
|
+
expect(screen.getByRole('button', { name: 'Toggle' })).toHaveAttribute('aria-expanded', 'false');
|
|
45
109
|
});
|
|
46
110
|
|
|
47
|
-
it('
|
|
48
|
-
const user = userEvent.setup();
|
|
111
|
+
it('trigger uses commandfor/command="toggle-popover"', () => {
|
|
49
112
|
render(
|
|
50
|
-
<Popover content={<p>Panel
|
|
113
|
+
<Popover content={<p>Panel</p>}>
|
|
51
114
|
<button type="button">Open</button>
|
|
52
115
|
</Popover>
|
|
53
116
|
);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
expect(
|
|
117
|
+
const trigger = screen.getByRole('button');
|
|
118
|
+
expect(trigger).toHaveAttribute('commandfor');
|
|
119
|
+
expect(trigger).toHaveAttribute('command', 'toggle-popover');
|
|
120
|
+
expect(trigger.getAttribute('commandfor')).toBe(getPanel().id);
|
|
57
121
|
});
|
|
58
122
|
|
|
59
|
-
it('sets
|
|
60
|
-
const user = userEvent.setup();
|
|
123
|
+
it('sets anchor-name on the trigger and position-anchor on the panel', () => {
|
|
61
124
|
render(
|
|
62
125
|
<Popover content={<p>Panel</p>}>
|
|
63
126
|
<button type="button">Open</button>
|
|
64
127
|
</Popover>
|
|
65
128
|
);
|
|
66
129
|
const trigger = screen.getByRole('button');
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
130
|
+
const panel = getPanel();
|
|
131
|
+
const anchorName = trigger.style.getPropertyValue('anchor-name');
|
|
132
|
+
const positionAnchor = panel.style.getPropertyValue('position-anchor');
|
|
133
|
+
expect(anchorName).toMatch(/^--popover-/);
|
|
134
|
+
expect(positionAnchor).toBe(anchorName);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('each instance gets a unique anchor-name', () => {
|
|
138
|
+
render(
|
|
139
|
+
<>
|
|
140
|
+
<Popover content={<p>A</p>}>
|
|
141
|
+
<button type="button">A</button>
|
|
142
|
+
</Popover>
|
|
143
|
+
<Popover content={<p>B</p>}>
|
|
144
|
+
<button type="button">B</button>
|
|
145
|
+
</Popover>
|
|
146
|
+
</>
|
|
147
|
+
);
|
|
148
|
+
const [btnA, btnB] = screen.getAllByRole('button');
|
|
149
|
+
expect(btnA.style.getPropertyValue('anchor-name')).not.toBe(
|
|
150
|
+
btnB.style.getPropertyValue('anchor-name')
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('sets data-placement from the placement prop', () => {
|
|
155
|
+
render(
|
|
156
|
+
<Popover content={<p>Panel</p>} placement="top">
|
|
157
|
+
<button type="button">Open</button>
|
|
158
|
+
</Popover>
|
|
159
|
+
);
|
|
160
|
+
expect(getPanel()).toHaveAttribute('data-placement', 'top');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('defaults data-placement to "bottom"', () => {
|
|
164
|
+
render(
|
|
165
|
+
<Popover content={<p>Panel</p>}>
|
|
166
|
+
<button type="button">Open</button>
|
|
167
|
+
</Popover>
|
|
168
|
+
);
|
|
169
|
+
expect(getPanel()).toHaveAttribute('data-placement', 'bottom');
|
|
70
170
|
});
|
|
71
171
|
|
|
72
172
|
it('renders role="region" with aria-label when aria-label prop is provided', async () => {
|
|
@@ -77,17 +177,16 @@ describe('Popover', () => {
|
|
|
77
177
|
</Popover>
|
|
78
178
|
);
|
|
79
179
|
await user.click(screen.getByRole('button', { name: 'Open' }));
|
|
80
|
-
expect(
|
|
180
|
+
expect(getPanel()).toHaveAttribute('role', 'region');
|
|
181
|
+
expect(getPanel()).toHaveAttribute('aria-label', 'Filter options');
|
|
81
182
|
});
|
|
82
183
|
|
|
83
|
-
it('does not render role="region" when no aria-label is provided',
|
|
84
|
-
const user = userEvent.setup();
|
|
184
|
+
it('does not render role="region" when no aria-label is provided', () => {
|
|
85
185
|
render(
|
|
86
186
|
<Popover content={<p>Panel</p>}>
|
|
87
187
|
<button type="button">Open</button>
|
|
88
188
|
</Popover>
|
|
89
189
|
);
|
|
90
|
-
|
|
91
|
-
expect(screen.queryByRole('region')).not.toBeInTheDocument();
|
|
190
|
+
expect(getPanel()).not.toHaveAttribute('role');
|
|
92
191
|
});
|
|
93
192
|
});
|
|
@@ -3,7 +3,7 @@ import { Popover } from './Popover';
|
|
|
3
3
|
import { Button } from '../Button/Button';
|
|
4
4
|
|
|
5
5
|
const meta = {
|
|
6
|
-
title: 'Interaction/Popover',
|
|
6
|
+
title: 'React/Interaction/Popover',
|
|
7
7
|
component: Popover,
|
|
8
8
|
argTypes: {
|
|
9
9
|
placement: { control: 'radio', options: ['top', 'bottom', 'left', 'right'] },
|
|
@@ -8,14 +8,27 @@ import {
|
|
|
8
8
|
useRef,
|
|
9
9
|
useState,
|
|
10
10
|
} from 'react';
|
|
11
|
+
import { installInvokerCommandsPolyfill } from '../../../polyfill-invoker-commands';
|
|
12
|
+
|
|
13
|
+
// Install once at module load — no-op if native support is present.
|
|
14
|
+
installInvokerCommandsPolyfill();
|
|
15
|
+
|
|
11
16
|
import css from './Popover.module.css';
|
|
12
17
|
import { cn } from '@boostdev/design-system-foundation';
|
|
18
|
+
import type { WithClassName } from '../../../types';
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
// Load the CSS Anchor Positioning polyfill once, only in browsers that need it.
|
|
21
|
+
if (
|
|
22
|
+
typeof window !== 'undefined' &&
|
|
23
|
+
!(typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('anchor-name: --a'))
|
|
24
|
+
) {
|
|
25
|
+
import('@oddbird/css-anchor-positioning').catch(() => {});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface PopoverProps extends WithClassName {
|
|
15
29
|
children: ReactElement;
|
|
16
30
|
content: ReactNode;
|
|
17
31
|
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
18
|
-
className?: string;
|
|
19
32
|
'aria-label'?: string;
|
|
20
33
|
}
|
|
21
34
|
|
|
@@ -28,54 +41,81 @@ export function Popover({
|
|
|
28
41
|
}: Readonly<PopoverProps>) {
|
|
29
42
|
const [isOpen, setIsOpen] = useState(false);
|
|
30
43
|
const containerRef = useRef<HTMLSpanElement>(null);
|
|
31
|
-
const
|
|
44
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
45
|
+
const rawId = useId();
|
|
46
|
+
// CSS anchor-name must be a valid custom-ident: strip non-word characters.
|
|
47
|
+
const anchorName = `--popover-${rawId.replace(/\W/g, '') || 'a'}`;
|
|
48
|
+
const panelId = rawId;
|
|
32
49
|
|
|
50
|
+
// Sync isOpen state via the popover toggle event (fires on both native and
|
|
51
|
+
// polyfilled open/close, including light-dismiss and Escape).
|
|
33
52
|
useEffect(() => {
|
|
34
|
-
|
|
53
|
+
const panel = panelRef.current;
|
|
54
|
+
if (!panel) return;
|
|
55
|
+
const handleToggle = (e: Event) => {
|
|
56
|
+
setIsOpen((e as ToggleEvent).newState === 'open');
|
|
57
|
+
};
|
|
58
|
+
panel.addEventListener('toggle', handleToggle);
|
|
59
|
+
return () => panel.removeEventListener('toggle', handleToggle);
|
|
60
|
+
}, []);
|
|
35
61
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
62
|
+
// Close the popover when the anchor scrolls out of the viewport.
|
|
63
|
+
// Light-dismiss (outside click) and Escape are handled natively by popover="auto".
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!isOpen) return;
|
|
66
|
+
const handleScroll = () => {
|
|
67
|
+
const rect = containerRef.current?.getBoundingClientRect();
|
|
68
|
+
if (!rect) return;
|
|
69
|
+
const { innerWidth: vw, innerHeight: vh } = window;
|
|
70
|
+
if (rect.bottom < 0 || rect.top > vh || rect.right < 0 || rect.left > vw) {
|
|
71
|
+
panelRef.current?.hidePopover();
|
|
39
72
|
}
|
|
40
73
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
document.addEventListener('pointerdown', handlePointerDown);
|
|
46
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
74
|
+
window.addEventListener('scroll', handleScroll, { capture: true, passive: true });
|
|
75
|
+
window.addEventListener('resize', handleScroll, { passive: true });
|
|
47
76
|
return () => {
|
|
48
|
-
|
|
49
|
-
|
|
77
|
+
window.removeEventListener('scroll', handleScroll, { capture: true });
|
|
78
|
+
window.removeEventListener('resize', handleScroll);
|
|
50
79
|
};
|
|
51
80
|
}, [isOpen]);
|
|
52
81
|
|
|
82
|
+
// Inject invoker command attrs and accessibility props onto the trigger.
|
|
83
|
+
// commandfor/command="toggle-popover" replaces the onClick toggle handler —
|
|
84
|
+
// the browser (or polyfill) calls togglePopover() on the panel, which fires
|
|
85
|
+
// the toggle event above to sync state.
|
|
53
86
|
const trigger = isValidElement(children)
|
|
54
87
|
? cloneElement(children as ReactElement<Record<string, unknown>>, {
|
|
55
88
|
'aria-expanded': isOpen,
|
|
56
89
|
'aria-controls': panelId,
|
|
57
90
|
'aria-haspopup': true,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
91
|
+
commandfor: panelId,
|
|
92
|
+
command: 'toggle-popover',
|
|
93
|
+
style: {
|
|
94
|
+
...((children.props as Record<string, unknown>).style as object | undefined),
|
|
95
|
+
anchorName,
|
|
62
96
|
},
|
|
97
|
+
// Forward any existing onClick on the child (for custom side-effects).
|
|
98
|
+
// Do NOT add our own toggle here — commandfor/command handles it to
|
|
99
|
+
// avoid a double-toggle when both onClick and invoker fire.
|
|
100
|
+
onClick: (children.props as Record<string, unknown>).onClick as (() => void) | undefined,
|
|
63
101
|
})
|
|
64
102
|
: children;
|
|
65
103
|
|
|
66
104
|
return (
|
|
67
105
|
<span ref={containerRef} className={cn(css.wrapper, className)}>
|
|
68
106
|
{trigger}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
107
|
+
<div
|
|
108
|
+
ref={panelRef}
|
|
109
|
+
id={panelId}
|
|
110
|
+
popover="auto"
|
|
111
|
+
data-placement={placement}
|
|
112
|
+
role={ariaLabel ? 'region' : undefined}
|
|
113
|
+
aria-label={ariaLabel}
|
|
114
|
+
className={css.panel}
|
|
115
|
+
style={{ positionAnchor: anchorName }}
|
|
116
|
+
>
|
|
117
|
+
{content}
|
|
118
|
+
</div>
|
|
79
119
|
</span>
|
|
80
120
|
);
|
|
81
121
|
}
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { Rating } from './Rating';
|
|
3
3
|
|
|
4
4
|
const meta = {
|
|
5
|
-
title: 'Interaction/Rating',
|
|
5
|
+
title: 'React/Interaction/Rating',
|
|
6
6
|
component: Rating,
|
|
7
7
|
argTypes: {
|
|
8
8
|
value: { control: { type: 'range', min: 0, max: 5, step: 1 } },
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import css from './Rating.module.css';
|
|
2
2
|
import { cn } from '@boostdev/design-system-foundation';
|
|
3
|
+
import type { WithClassName } from '../../../types';
|
|
3
4
|
|
|
4
|
-
interface RatingProps {
|
|
5
|
+
interface RatingProps extends WithClassName {
|
|
5
6
|
value: number;
|
|
6
7
|
max?: number;
|
|
7
|
-
className?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function Rating({ value, max = 5, className }: RatingProps) {
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
@layer component {
|
|
2
2
|
.toastContainer {
|
|
3
3
|
position: fixed;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
inset: unset; /* override UA popover default (inset: 0) */
|
|
5
|
+
inset-block-end: var(--bds-space_xl);
|
|
6
|
+
inset-inline-end: var(--bds-space_xl);
|
|
6
7
|
display: flex;
|
|
7
8
|
flex-direction: column;
|
|
8
9
|
gap: var(--bds-space_m);
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
/* z-index removed — top layer renders above all fixed/sticky elements */
|
|
12
|
+
border: none; /* override UA popover default */
|
|
13
|
+
padding: 0; /* override UA popover default */
|
|
14
|
+
background: transparent; /* override UA popover default */
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
.toast {
|
|
@@ -18,7 +23,7 @@
|
|
|
18
23
|
display: flex;
|
|
19
24
|
align-items: center;
|
|
20
25
|
gap: var(--bds-space_m);
|
|
21
|
-
min-
|
|
26
|
+
min-inline-size: 300px;
|
|
22
27
|
animation: slideIn var(--bds-animation_duration--fast) var(--bds-animation_easing);
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -51,8 +56,8 @@
|
|
|
51
56
|
align-items: center;
|
|
52
57
|
justify-content: center;
|
|
53
58
|
flex-shrink: 0;
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
inline-size: 1.25rem;
|
|
60
|
+
block-size: 1.25rem;
|
|
56
61
|
padding: 0;
|
|
57
62
|
background: none;
|
|
58
63
|
border: none;
|
|
@@ -61,8 +66,8 @@
|
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
.closeButton svg {
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
inline-size: 1rem;
|
|
70
|
+
block-size: 1rem;
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
|
|
@@ -32,6 +32,27 @@ describe('Toast', () => {
|
|
|
32
32
|
vi.useRealTimers();
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
it('renders the toast container as a manual popover', () => {
|
|
36
|
+
render(
|
|
37
|
+
<ToastProvider>
|
|
38
|
+
<div />
|
|
39
|
+
</ToastProvider>
|
|
40
|
+
);
|
|
41
|
+
const container = document.querySelector('[popover]');
|
|
42
|
+
expect(container).toHaveAttribute('popover', 'manual');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders the toast container as a labelled notifications region', () => {
|
|
46
|
+
render(
|
|
47
|
+
<ToastProvider>
|
|
48
|
+
<div />
|
|
49
|
+
</ToastProvider>
|
|
50
|
+
);
|
|
51
|
+
const container = document.querySelector('[popover="manual"]');
|
|
52
|
+
expect(container).toHaveAttribute('role', 'region');
|
|
53
|
+
expect(container).toHaveAttribute('aria-label', 'Notifications');
|
|
54
|
+
});
|
|
55
|
+
|
|
35
56
|
it('throws when useToast is used outside ToastProvider', () => {
|
|
36
57
|
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
37
58
|
const Bad = () => { useToast(); return null; };
|