@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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
export type PopoverPlacement = 'bottom' | 'top' | 'left' | 'right';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `<bds-popover>` — framework-agnostic Popover custom element.
|
|
7
|
+
*
|
|
8
|
+
* Uses CSS Anchor Positioning within the shadow DOM so the panel stays
|
|
9
|
+
* anchored to the trigger and automatically repositions when it would
|
|
10
|
+
* overflow the viewport (`@position-try` fallbacks).
|
|
11
|
+
*
|
|
12
|
+
* Attributes:
|
|
13
|
+
* placement — "bottom" (default) | "top" | "left" | "right"
|
|
14
|
+
* open — boolean; reflects whether the panel is visible
|
|
15
|
+
*
|
|
16
|
+
* Slots:
|
|
17
|
+
* trigger — the element that opens/closes the panel on click
|
|
18
|
+
* (default) — panel content
|
|
19
|
+
*
|
|
20
|
+
* Events:
|
|
21
|
+
* bds-open — fired (bubbles, composed) when the panel opens
|
|
22
|
+
* bds-close — fired (bubbles, composed) when the panel closes
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* <bds-popover placement="bottom">
|
|
26
|
+
* <button slot="trigger">Open</button>
|
|
27
|
+
* <p>Panel content here</p>
|
|
28
|
+
* </bds-popover>
|
|
29
|
+
*/
|
|
30
|
+
export class BdsPopover extends LitElement {
|
|
31
|
+
static styles = css`
|
|
32
|
+
:host {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
position: relative;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* The wrapper IS the CSS anchor — anchor-name resolves via the custom
|
|
38
|
+
property injected on :host in connectedCallback(), so each instance
|
|
39
|
+
gets its own unique name without colliding with siblings. */
|
|
40
|
+
.wrapper {
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
anchor-name: var(--bds-popover-anchor);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.panel {
|
|
46
|
+
display: none;
|
|
47
|
+
position: absolute;
|
|
48
|
+
position-anchor: var(--bds-popover-anchor);
|
|
49
|
+
margin: 0;
|
|
50
|
+
overflow-y: auto;
|
|
51
|
+
z-index: var(--popover_z-index, 100);
|
|
52
|
+
min-width: 12rem;
|
|
53
|
+
padding: var(--bds-space_m);
|
|
54
|
+
border-radius: var(--bds-border_radius--s);
|
|
55
|
+
background-color: var(--popover_color, var(--bds-color_bg));
|
|
56
|
+
box-shadow: var(--popover_shadow, var(--bds-shadow_m));
|
|
57
|
+
color: var(--popover_on-color, var(--bds-color_on-bg));
|
|
58
|
+
font-size: var(--bds-font_size--body);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.panel.open {
|
|
62
|
+
display: block;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Primary placements */
|
|
66
|
+
|
|
67
|
+
.panel[data-placement='bottom'] {
|
|
68
|
+
top: anchor(bottom);
|
|
69
|
+
left: anchor(left);
|
|
70
|
+
margin-top: var(--bds-space_xs, 8px);
|
|
71
|
+
position-try-fallbacks: --bds-popover-top, --bds-popover-right, --bds-popover-left;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.panel[data-placement='top'] {
|
|
75
|
+
bottom: anchor(top);
|
|
76
|
+
left: anchor(left);
|
|
77
|
+
margin-bottom: var(--bds-space_xs, 8px);
|
|
78
|
+
position-try-fallbacks: --bds-popover-bottom, --bds-popover-right, --bds-popover-left;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.panel[data-placement='right'] {
|
|
82
|
+
top: anchor(top);
|
|
83
|
+
left: anchor(right);
|
|
84
|
+
margin-left: var(--bds-space_xs, 8px);
|
|
85
|
+
position-try-fallbacks: --bds-popover-left, --bds-popover-bottom, --bds-popover-top;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.panel[data-placement='left'] {
|
|
89
|
+
top: anchor(top);
|
|
90
|
+
right: anchor(left);
|
|
91
|
+
margin-right: var(--bds-space_xs, 8px);
|
|
92
|
+
position-try-fallbacks: --bds-popover-right, --bds-popover-bottom, --bds-popover-top;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Fallback placements */
|
|
96
|
+
|
|
97
|
+
@position-try --bds-popover-bottom {
|
|
98
|
+
top: anchor(bottom);
|
|
99
|
+
right: unset;
|
|
100
|
+
bottom: unset;
|
|
101
|
+
left: anchor(left);
|
|
102
|
+
margin: 0;
|
|
103
|
+
margin-top: var(--bds-space_xs, 8px);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@position-try --bds-popover-top {
|
|
107
|
+
top: unset;
|
|
108
|
+
right: unset;
|
|
109
|
+
bottom: anchor(top);
|
|
110
|
+
left: anchor(left);
|
|
111
|
+
margin: 0;
|
|
112
|
+
margin-bottom: var(--bds-space_xs, 8px);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@position-try --bds-popover-right {
|
|
116
|
+
top: anchor(top);
|
|
117
|
+
right: unset;
|
|
118
|
+
bottom: unset;
|
|
119
|
+
left: anchor(right);
|
|
120
|
+
margin: 0;
|
|
121
|
+
margin-left: var(--bds-space_xs, 8px);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@position-try --bds-popover-left {
|
|
125
|
+
top: anchor(top);
|
|
126
|
+
right: anchor(left);
|
|
127
|
+
bottom: unset;
|
|
128
|
+
left: unset;
|
|
129
|
+
margin: 0;
|
|
130
|
+
margin-right: var(--bds-space_xs, 8px);
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
static properties = {
|
|
135
|
+
placement: { type: String, reflect: true },
|
|
136
|
+
open: { type: Boolean, reflect: true },
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
declare placement: PopoverPlacement;
|
|
140
|
+
declare open: boolean;
|
|
141
|
+
|
|
142
|
+
private _anchorName: string;
|
|
143
|
+
|
|
144
|
+
constructor() {
|
|
145
|
+
super();
|
|
146
|
+
this.placement = 'bottom';
|
|
147
|
+
this.open = false;
|
|
148
|
+
const uid = Math.random().toString(36).slice(2, 8);
|
|
149
|
+
this._anchorName = `--bds-popover-${uid}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
connectedCallback() {
|
|
153
|
+
super.connectedCallback();
|
|
154
|
+
// Inject the unique anchor-name as a CSS custom property so both
|
|
155
|
+
// `.wrapper` (anchor-name) and `.panel` (position-anchor) reference
|
|
156
|
+
// the same value without hard-coding it in the shared static styles.
|
|
157
|
+
this.style.setProperty('--bds-popover-anchor', this._anchorName);
|
|
158
|
+
document.addEventListener('keydown', this._onKeydown);
|
|
159
|
+
document.addEventListener('mousedown', this._onOutsideClick);
|
|
160
|
+
// Invoker Commands API — host is in light DOM so commandfor can reference its id.
|
|
161
|
+
this.addEventListener('command', this._onCommand);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
disconnectedCallback() {
|
|
165
|
+
super.disconnectedCallback();
|
|
166
|
+
document.removeEventListener('keydown', this._onKeydown);
|
|
167
|
+
document.removeEventListener('mousedown', this._onOutsideClick);
|
|
168
|
+
this.removeEventListener('command', this._onCommand);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private _onKeydown = (e: KeyboardEvent) => {
|
|
172
|
+
if (this.open && e.key === 'Escape') this._close();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
private _onCommand = (e: Event) => {
|
|
176
|
+
const command = (e as CommandEvent).command;
|
|
177
|
+
if (command === '--toggle') this._toggle();
|
|
178
|
+
else if (command === '--open') this._open();
|
|
179
|
+
else if (command === '--close') this._close();
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
private _onOutsideClick = (e: MouseEvent) => {
|
|
183
|
+
if (this.open && !this.contains(e.target as Node)) this._close();
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
private _toggle() {
|
|
187
|
+
if (this.open) {
|
|
188
|
+
this._close();
|
|
189
|
+
} else {
|
|
190
|
+
this._open();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private _open() {
|
|
195
|
+
if (this.open) return;
|
|
196
|
+
this.open = true;
|
|
197
|
+
this.dispatchEvent(new CustomEvent('bds-open', { bubbles: true, composed: true }));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private _close() {
|
|
201
|
+
if (!this.open) return;
|
|
202
|
+
this.open = false;
|
|
203
|
+
this.dispatchEvent(new CustomEvent('bds-close', { bubbles: true, composed: true }));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
render() {
|
|
207
|
+
return html`
|
|
208
|
+
<span class="wrapper">
|
|
209
|
+
<slot name="trigger" @click=${this._toggle}></slot>
|
|
210
|
+
</span>
|
|
211
|
+
<div
|
|
212
|
+
class="panel ${this.open ? 'open' : ''}"
|
|
213
|
+
data-placement=${this.placement}
|
|
214
|
+
role="region"
|
|
215
|
+
aria-label="Popover"
|
|
216
|
+
>
|
|
217
|
+
<slot></slot>
|
|
218
|
+
</div>
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
customElements.define('bds-popover', BdsPopover);
|
|
224
|
+
|
|
225
|
+
declare global {
|
|
226
|
+
interface HTMLElementTagNameMap {
|
|
227
|
+
'bds-popover': BdsPopover;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsToastProvider } from './bds-toast-provider';
|
|
3
|
+
|
|
4
|
+
describe('bds-toast-provider', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-toast-provider')).toBe(BdsToastProvider);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('renders a popover="manual" container in shadow DOM', async () => {
|
|
10
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>');
|
|
11
|
+
const container = el.shadowRoot!.querySelector('[popover]');
|
|
12
|
+
expect(container).not.toBeNull();
|
|
13
|
+
expect(container!.getAttribute('popover')).toBe('manual');
|
|
14
|
+
cleanup(el);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('container has role="region" and aria-label="Notifications"', async () => {
|
|
18
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>');
|
|
19
|
+
const container = el.shadowRoot!.querySelector('[popover="manual"]');
|
|
20
|
+
expect(container!.getAttribute('role')).toBe('region');
|
|
21
|
+
expect(container!.getAttribute('aria-label')).toBe('Notifications');
|
|
22
|
+
cleanup(el);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('shows no toasts initially', async () => {
|
|
26
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>');
|
|
27
|
+
const toasts = el.shadowRoot!.querySelectorAll('.toast');
|
|
28
|
+
expect(toasts.length).toBe(0);
|
|
29
|
+
cleanup(el);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('showToast adds a toast to the shadow DOM', async () => {
|
|
33
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
34
|
+
el.showToast('Hello toast', 'info');
|
|
35
|
+
await el.updateComplete;
|
|
36
|
+
const toasts = el.shadowRoot!.querySelectorAll('.toast');
|
|
37
|
+
expect(toasts.length).toBe(1);
|
|
38
|
+
expect(toasts[0].querySelector('.message')!.textContent).toBe('Hello toast');
|
|
39
|
+
cleanup(el);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('applies the correct variant class', async () => {
|
|
43
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
44
|
+
el.showToast('Success!', 'success');
|
|
45
|
+
await el.updateComplete;
|
|
46
|
+
const toast = el.shadowRoot!.querySelector('.toast');
|
|
47
|
+
expect(toast!.classList.contains('--variant_success')).toBe(true);
|
|
48
|
+
cleanup(el);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('dismiss button click removes the toast', async () => {
|
|
52
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
53
|
+
el.showToast('Dismiss me', 'info');
|
|
54
|
+
await el.updateComplete;
|
|
55
|
+
const btn = el.shadowRoot!.querySelector<HTMLButtonElement>('.close')!;
|
|
56
|
+
btn.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
|
|
57
|
+
await el.updateComplete;
|
|
58
|
+
expect(el.shadowRoot!.querySelectorAll('.toast').length).toBe(0);
|
|
59
|
+
cleanup(el);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('auto-removes the toast after 5 seconds', async () => {
|
|
63
|
+
vi.useFakeTimers();
|
|
64
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
65
|
+
el.showToast('Timed out', 'warning');
|
|
66
|
+
await el.updateComplete;
|
|
67
|
+
expect(el.shadowRoot!.querySelectorAll('.toast').length).toBe(1);
|
|
68
|
+
vi.advanceTimersByTime(5100);
|
|
69
|
+
await el.updateComplete;
|
|
70
|
+
expect(el.shadowRoot!.querySelectorAll('.toast').length).toBe(0);
|
|
71
|
+
vi.useRealTimers();
|
|
72
|
+
cleanup(el);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('static BdsToastProvider.show() adds a toast via the first provider', async () => {
|
|
76
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
77
|
+
BdsToastProvider.show('Static show', 'error');
|
|
78
|
+
await el.updateComplete;
|
|
79
|
+
const toast = el.shadowRoot!.querySelector('.toast');
|
|
80
|
+
expect(toast).not.toBeNull();
|
|
81
|
+
expect(toast!.querySelector('.message')!.textContent).toBe('Static show');
|
|
82
|
+
cleanup(el);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('bds-show-toast document event adds a toast', async () => {
|
|
86
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
87
|
+
document.dispatchEvent(
|
|
88
|
+
new CustomEvent('bds-show-toast', { detail: { message: 'Event toast', variant: 'success' } }),
|
|
89
|
+
);
|
|
90
|
+
await el.updateComplete;
|
|
91
|
+
const toast = el.shadowRoot!.querySelector('.toast');
|
|
92
|
+
expect(toast).not.toBeNull();
|
|
93
|
+
expect(toast!.querySelector('.message')!.textContent).toBe('Event toast');
|
|
94
|
+
cleanup(el);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('showToast defaults variant to info when not specified', async () => {
|
|
98
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
99
|
+
el.showToast('No variant');
|
|
100
|
+
await el.updateComplete;
|
|
101
|
+
const toast = el.shadowRoot!.querySelector('.toast');
|
|
102
|
+
expect(toast!.classList.contains('--variant_info')).toBe(true);
|
|
103
|
+
cleanup(el);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('bds-show-toast event with no message is a no-op', async () => {
|
|
107
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
108
|
+
document.dispatchEvent(new CustomEvent('bds-show-toast', { detail: {} }));
|
|
109
|
+
await el.updateComplete;
|
|
110
|
+
expect(el.shadowRoot!.querySelectorAll('.toast').length).toBe(0);
|
|
111
|
+
cleanup(el);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('multiple toasts stack in the container', async () => {
|
|
115
|
+
const el = await fixture('<bds-toast-provider></bds-toast-provider>') as BdsToastProvider;
|
|
116
|
+
el.showToast('First', 'info');
|
|
117
|
+
el.showToast('Second', 'success');
|
|
118
|
+
await el.updateComplete;
|
|
119
|
+
expect(el.shadowRoot!.querySelectorAll('.toast').length).toBe(2);
|
|
120
|
+
cleanup(el);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
export type ToastVariant = 'success' | 'error' | 'info' | 'warning';
|
|
4
|
+
|
|
5
|
+
interface ToastEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
message: string;
|
|
8
|
+
variant: ToastVariant;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `<bds-toast-provider>` — framework-agnostic Toast provider custom element.
|
|
13
|
+
*
|
|
14
|
+
* Wraps page content and manages a `popover="manual"` toast container that
|
|
15
|
+
* renders in the browser top layer — above all fixed/sticky elements without
|
|
16
|
+
* needing a z-index.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* // Instance method (direct reference)
|
|
20
|
+
* provider.showToast('Saved!', 'success');
|
|
21
|
+
*
|
|
22
|
+
* // Static convenience (finds the first provider in the DOM)
|
|
23
|
+
* BdsToastProvider.show('Something went wrong', 'error');
|
|
24
|
+
*
|
|
25
|
+
* // Event-driven (no import required)
|
|
26
|
+
* document.dispatchEvent(new CustomEvent('bds-show-toast', {
|
|
27
|
+
* detail: { message: 'Hello', variant: 'info' }
|
|
28
|
+
* }));
|
|
29
|
+
*
|
|
30
|
+
* Slots:
|
|
31
|
+
* (default) — page content; the host uses display:contents so layout
|
|
32
|
+
* is unaffected
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* <bds-toast-provider>
|
|
36
|
+
* <main>…</main>
|
|
37
|
+
* </bds-toast-provider>
|
|
38
|
+
*/
|
|
39
|
+
export class BdsToastProvider extends LitElement {
|
|
40
|
+
static styles = css`
|
|
41
|
+
:host {
|
|
42
|
+
display: contents;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.container {
|
|
46
|
+
/* Reset UA popover defaults */
|
|
47
|
+
position: fixed;
|
|
48
|
+
inset: unset;
|
|
49
|
+
bottom: var(--bds-space_xl, 2rem);
|
|
50
|
+
right: var(--bds-space_xl, 2rem);
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: var(--bds-space_m, 1rem);
|
|
54
|
+
border: none;
|
|
55
|
+
padding: 0;
|
|
56
|
+
background: transparent;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.toast {
|
|
60
|
+
padding: var(--bds-space_m) var(--bds-space_l);
|
|
61
|
+
background-color: var(--toast_color, var(--bds-color_bg));
|
|
62
|
+
color: var(--toast_on-color, var(--bds-color_on-bg));
|
|
63
|
+
border-radius: var(--bds-border_radius--s);
|
|
64
|
+
box-shadow: var(--bds-shadow_s);
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: var(--bds-space_m);
|
|
68
|
+
min-width: 300px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.toast.--variant_success {
|
|
72
|
+
--toast_color: var(--bds-color_success);
|
|
73
|
+
--toast_on-color: var(--bds-color_on-success);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.toast.--variant_warning {
|
|
77
|
+
--toast_color: var(--bds-color_warning);
|
|
78
|
+
--toast_on-color: var(--bds-color_on-warning);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.toast.--variant_info {
|
|
82
|
+
--toast_color: var(--bds-color_bg--subtle);
|
|
83
|
+
--toast_on-color: var(--bds-color_on-bg--subtle);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.toast.--variant_error {
|
|
87
|
+
--toast_color: var(--bds-color_error);
|
|
88
|
+
--toast_on-color: var(--bds-color_on-error);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.message {
|
|
92
|
+
flex: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.close {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
width: 1.25rem;
|
|
101
|
+
height: 1.25rem;
|
|
102
|
+
padding: 0;
|
|
103
|
+
background: none;
|
|
104
|
+
border: none;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
color: var(--toast_on-color);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.close svg {
|
|
110
|
+
width: 1rem;
|
|
111
|
+
height: 1rem;
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
static properties = {
|
|
116
|
+
_toasts: { state: true },
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
declare private _toasts: ToastEntry[];
|
|
120
|
+
|
|
121
|
+
constructor() {
|
|
122
|
+
super();
|
|
123
|
+
this._toasts = [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
connectedCallback() {
|
|
127
|
+
super.connectedCallback();
|
|
128
|
+
document.addEventListener('bds-show-toast', this._onShowToastEvent);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
disconnectedCallback() {
|
|
132
|
+
super.disconnectedCallback();
|
|
133
|
+
document.removeEventListener('bds-show-toast', this._onShowToastEvent);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Sync the popover's top-layer visibility with the toast queue —
|
|
137
|
+
// mirrors Toast.tsx's useEffect([toasts.length]).
|
|
138
|
+
updated() {
|
|
139
|
+
const container = this.shadowRoot?.querySelector<HTMLElement & {
|
|
140
|
+
showPopover?: () => void;
|
|
141
|
+
hidePopover?: () => void;
|
|
142
|
+
}>('[popover]');
|
|
143
|
+
if (!container) return;
|
|
144
|
+
if (this._toasts.length > 0) {
|
|
145
|
+
container.showPopover?.();
|
|
146
|
+
} else {
|
|
147
|
+
try { container.hidePopover?.(); } catch { /* already hidden on initial mount */ }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
showToast(message: string, variant: ToastVariant = 'info') {
|
|
152
|
+
const id = Math.random().toString(36).slice(2, 9);
|
|
153
|
+
this._toasts = [...this._toasts, { id, message, variant }];
|
|
154
|
+
setTimeout(() => this._removeToast(id), 5000);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private _removeToast(id: string) {
|
|
158
|
+
this._toasts = this._toasts.filter(t => t.id !== id);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private _onShowToastEvent = (e: Event) => {
|
|
162
|
+
const { message, variant } = (e as CustomEvent<{ message: string; variant?: ToastVariant }>).detail ?? {};
|
|
163
|
+
if (message) this.showToast(message, variant);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
static show(message: string, variant?: ToastVariant) {
|
|
167
|
+
const provider = document.querySelector<BdsToastProvider>('bds-toast-provider');
|
|
168
|
+
provider?.showToast(message, variant ?? 'info');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
render() {
|
|
172
|
+
return html`
|
|
173
|
+
<slot></slot>
|
|
174
|
+
<div
|
|
175
|
+
popover="manual"
|
|
176
|
+
role="region"
|
|
177
|
+
aria-label="Notifications"
|
|
178
|
+
class="container"
|
|
179
|
+
>
|
|
180
|
+
${this._toasts.map(t => html`
|
|
181
|
+
<div
|
|
182
|
+
class="toast --variant_${t.variant}"
|
|
183
|
+
role="status"
|
|
184
|
+
aria-live="polite"
|
|
185
|
+
aria-atomic="true"
|
|
186
|
+
>
|
|
187
|
+
<span class="message">${t.message}</span>
|
|
188
|
+
<button
|
|
189
|
+
type="button"
|
|
190
|
+
class="close"
|
|
191
|
+
@click=${() => this._removeToast(t.id)}
|
|
192
|
+
aria-label="Dismiss"
|
|
193
|
+
>
|
|
194
|
+
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
195
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12" />
|
|
196
|
+
</svg>
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
199
|
+
`)}
|
|
200
|
+
</div>
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
customElements.define('bds-toast-provider', BdsToastProvider);
|
|
206
|
+
|
|
207
|
+
declare global {
|
|
208
|
+
interface HTMLElementTagNameMap {
|
|
209
|
+
'bds-toast-provider': BdsToastProvider;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Render an HTML string, upgrade the custom element, and await first paint. */
|
|
2
|
+
export async function fixture(html: string): Promise<HTMLElement> {
|
|
3
|
+
const template = document.createElement('template');
|
|
4
|
+
template.innerHTML = html;
|
|
5
|
+
const el = template.content.firstElementChild as HTMLElement;
|
|
6
|
+
document.body.appendChild(el);
|
|
7
|
+
await (el as unknown as { updateComplete: Promise<void> }).updateComplete;
|
|
8
|
+
return el;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Remove an element from the document after a test. */
|
|
12
|
+
export function cleanup(el: HTMLElement) {
|
|
13
|
+
el.remove();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Create a synthetic Invoker Commands API `command` event for testing. */
|
|
17
|
+
export function makeCommandEvent(command: string): Event {
|
|
18
|
+
const event = new Event('command', { bubbles: false, cancelable: true });
|
|
19
|
+
Object.defineProperty(event, 'command', { value: command, configurable: true });
|
|
20
|
+
Object.defineProperty(event, 'source', { value: null, configurable: true });
|
|
21
|
+
return event;
|
|
22
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
+
import * as Stories from './BdsAlert.stories';
|
|
3
|
+
|
|
4
|
+
<Meta of={Stories} />
|
|
5
|
+
|
|
6
|
+
# <bds-alert>
|
|
7
|
+
|
|
8
|
+
Framework-agnostic alert custom element for inline status messages. Supports four semantic variants, an optional title, and a dismissible mode.
|
|
9
|
+
|
|
10
|
+
> **Status: experimental** — API may change before stable release.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import '@boostdev/components/web-components';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
### All variants
|
|
21
|
+
<Canvas of={Stories.AllVariants} />
|
|
22
|
+
|
|
23
|
+
### Info
|
|
24
|
+
<Canvas of={Stories.Info} />
|
|
25
|
+
|
|
26
|
+
### Success
|
|
27
|
+
<Canvas of={Stories.Success} />
|
|
28
|
+
|
|
29
|
+
### Warning
|
|
30
|
+
<Canvas of={Stories.Warning} />
|
|
31
|
+
|
|
32
|
+
### Error
|
|
33
|
+
<Canvas of={Stories.Error} />
|
|
34
|
+
|
|
35
|
+
### With title
|
|
36
|
+
<Canvas of={Stories.WithTitle} />
|
|
37
|
+
|
|
38
|
+
### Dismissible
|
|
39
|
+
<Canvas of={Stories.Dismissible} />
|
|
40
|
+
|
|
41
|
+
## Props
|
|
42
|
+
|
|
43
|
+
<ArgTypes of={Stories} />
|
|
44
|
+
|
|
45
|
+
## Attributes
|
|
46
|
+
|
|
47
|
+
| Attribute | Type | Default | Description |
|
|
48
|
+
|-----------|------|---------|-------------|
|
|
49
|
+
| `variant` | `"info" \| "success" \| "warning" \| "error"` | `"info"` | Semantic colour and icon |
|
|
50
|
+
| `title` | string | — | Optional heading displayed above body content |
|
|
51
|
+
| `dismissible` | boolean | `false` | Shows a dismiss (×) button |
|
|
52
|
+
|
|
53
|
+
## Slots
|
|
54
|
+
|
|
55
|
+
| Slot | Description |
|
|
56
|
+
|------|-------------|
|
|
57
|
+
| `(default)` | Alert body text or content |
|
|
58
|
+
| `icon` | Custom icon, placed left of the content area |
|
|
59
|
+
|
|
60
|
+
## Events
|
|
61
|
+
|
|
62
|
+
| Event | Detail | Bubbles | Composed | Description |
|
|
63
|
+
|-------|--------|---------|----------|-------------|
|
|
64
|
+
| `bds-dismiss` | — | Yes | Yes | Fired when the dismiss button is clicked |
|
|
65
|
+
|
|
66
|
+
## Usage in plain HTML
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<bds-alert variant="success" title="Saved">
|
|
70
|
+
Your changes have been saved successfully.
|
|
71
|
+
</bds-alert>
|
|
72
|
+
|
|
73
|
+
<bds-alert variant="error" dismissible>
|
|
74
|
+
Something went wrong. Please try again.
|
|
75
|
+
</bds-alert>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Listen for dismiss:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
document.querySelector('bds-alert').addEventListener('bds-dismiss', () => {
|
|
82
|
+
// remove or hide the element
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Accessibility
|
|
87
|
+
|
|
88
|
+
- `error` and `warning` use `role="alert"` + `aria-live="assertive"` — content is announced immediately
|
|
89
|
+
- `info` and `success` use `role="status"` + `aria-live="polite"` — content is announced at the next opportunity
|
|
90
|
+
- The dismiss button has an accessible label; do not rely solely on its icon
|