@boostdev/design-system-components 2.8.0 → 2.11.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 +54 -46
- package/README.md +34 -20
- package/dist/client.cjs +1317 -779
- package/dist/client.css +1029 -812
- package/dist/client.d.cts +88 -26
- package/dist/client.d.ts +88 -26
- package/dist/client.js +1278 -732
- package/dist/index.cjs +1317 -779
- package/dist/index.css +1029 -812
- package/dist/index.d.cts +88 -26
- package/dist/index.d.ts +88 -26
- package/dist/index.js +1278 -732
- package/dist/web-components/chunk-2OLVQUBU.js +193 -0
- package/dist/web-components/{chunk-OF6FFGOT.js → chunk-3AY2YKCM.js} +77 -21
- package/dist/web-components/chunk-3WAV6MDN.js +212 -0
- package/dist/web-components/{chunk-UUKSDCYS.js → chunk-47AT5KZD.js} +87 -15
- package/dist/web-components/chunk-4A2YYTYN.js +93 -0
- package/dist/web-components/{chunk-Q4T5BJT6.js → chunk-62NK3ZSC.js} +18 -3
- package/dist/web-components/chunk-66M6ETWD.js +77 -0
- package/dist/web-components/{chunk-NCPQ7AF3.js → chunk-6G22I2CK.js} +18 -3
- package/dist/web-components/{chunk-QDST57JC.js → chunk-7ZOJ7N4E.js} +14 -4
- package/dist/web-components/{chunk-QLT6MTZF.js → chunk-AIBOHNDU.js} +10 -0
- package/dist/web-components/{chunk-X3NQMBAQ.js → chunk-AP4RDUL2.js} +14 -3
- package/dist/web-components/{chunk-QWPACK5L.js → chunk-BPYKZVDK.js} +11 -1
- package/dist/web-components/{chunk-YLVTEY23.js → chunk-BTCMJQKB.js} +53 -21
- package/dist/web-components/chunk-CUKQ2L5B.js +245 -0
- package/dist/web-components/chunk-DIZDCL7Z.js +14 -0
- package/dist/web-components/{chunk-PC3IOQAH.js → chunk-DV5T43ZC.js} +27 -7
- package/dist/web-components/{chunk-2QA62WCQ.js → chunk-EF7IVYTY.js} +10 -0
- package/dist/web-components/{chunk-L3XHOMN6.js → chunk-F3ZDNHZZ.js} +26 -1
- package/dist/web-components/chunk-FEDABOR5.js +248 -0
- package/dist/web-components/{chunk-6I2DBFQ7.js → chunk-HGFCJXOQ.js} +36 -5
- package/dist/web-components/{chunk-SOQQ7RVJ.js → chunk-IPGHUCJT.js} +23 -8
- package/dist/web-components/chunk-ITD2MV5K.js +392 -0
- package/dist/web-components/{chunk-ARMKC4BQ.js → chunk-JH6LQKLK.js} +77 -5
- package/dist/web-components/chunk-JMCXAAFK.js +120 -0
- package/dist/web-components/{chunk-KUI55GFB.js → chunk-JUPCZVVW.js} +50 -18
- package/dist/web-components/chunk-JV5QT2OU.js +146 -0
- package/dist/web-components/{chunk-3GFWLSLS.js → chunk-KA3VPKMY.js} +29 -6
- package/dist/web-components/{chunk-CR5PGUHV.js → chunk-KJGRFDFI.js} +26 -11
- package/dist/web-components/{chunk-PY62FTRW.js → chunk-KZPRFY5P.js} +68 -33
- package/dist/web-components/{chunk-X46KL5BK.js → chunk-LDIPCTRN.js} +18 -1
- package/dist/web-components/chunk-M3ECULRH.js +174 -0
- package/dist/web-components/chunk-NBN2ENWJ.js +90 -0
- package/dist/web-components/chunk-NIN3JO6Q.js +189 -0
- package/dist/web-components/{chunk-2FGATTGT.js → chunk-NTQ4TCUT.js} +76 -55
- package/dist/web-components/chunk-OY74D4X6.js +302 -0
- package/dist/web-components/{chunk-3S42DZ7W.js → chunk-P24JYC5V.js} +7 -0
- package/dist/web-components/{chunk-BKIHQYJR.js → chunk-PXPPKCLT.js} +17 -2
- package/dist/web-components/{chunk-TQ7IQ4GZ.js → chunk-QEF2IRID.js} +71 -29
- package/dist/web-components/chunk-QZAIRJ75.js +111 -0
- package/dist/web-components/{chunk-6I3CFC26.js → chunk-RSZ3BWBK.js} +20 -5
- package/dist/web-components/chunk-RVF7W653.js +101 -0
- package/dist/web-components/chunk-SEO2F5KJ.js +234 -0
- package/dist/web-components/{chunk-VMMVWLXL.js → chunk-SFZ6Q4BP.js} +10 -0
- package/dist/web-components/{chunk-LY62SAVN.js → chunk-SYNLFKD3.js} +12 -2
- package/dist/web-components/chunk-TMWXRHGF.js +192 -0
- package/dist/web-components/{chunk-65JXQOBA.js → chunk-U3YZE6R6.js} +51 -7
- package/dist/web-components/chunk-UO7ROSHD.js +424 -0
- package/dist/web-components/chunk-VANAKB6D.js +152 -0
- package/dist/web-components/{chunk-PXZWYO6Z.js → chunk-VED2DFJ6.js} +108 -29
- package/dist/web-components/chunk-W6H5ID2U.js +161 -0
- package/dist/web-components/{chunk-FSZRP43R.js → chunk-WFVKU4GX.js} +21 -10
- package/dist/web-components/{chunk-OQTRTMAB.js → chunk-WIBAN7KP.js} +10 -0
- package/dist/web-components/chunk-XI7KRAH7.js +54 -0
- package/dist/web-components/{chunk-ORC56MDY.js → chunk-XIZWAWMJ.js} +45 -6
- package/dist/web-components/{chunk-FXURM6D5.js → chunk-YFAXGRPR.js} +10 -0
- package/dist/web-components/globals.js +1 -1
- package/dist/web-components/index.d.ts +77 -707
- package/dist/web-components/index.js +339 -2066
- package/dist/web-components/interaction/bds-accordion-item.js +1 -1
- package/dist/web-components/interaction/bds-accordion.js +1 -1
- package/dist/web-components/interaction/bds-button.d.ts +10 -2
- package/dist/web-components/interaction/bds-button.js +1 -1
- package/dist/web-components/interaction/bds-collapsible.d.ts +7 -2
- package/dist/web-components/interaction/bds-collapsible.js +1 -1
- package/dist/web-components/interaction/bds-dialog.d.ts +11 -3
- package/dist/web-components/interaction/bds-dialog.js +1 -1
- package/dist/web-components/interaction/bds-drawer.d.ts +7 -1
- package/dist/web-components/interaction/bds-drawer.js +1 -1
- package/dist/web-components/interaction/bds-dropdown-menu-item.d.ts +51 -0
- package/dist/web-components/interaction/bds-dropdown-menu-item.js +7 -0
- package/dist/web-components/interaction/bds-dropdown-menu.d.ts +70 -0
- package/dist/web-components/interaction/bds-dropdown-menu.js +7 -0
- package/dist/web-components/interaction/bds-popover.d.ts +14 -3
- package/dist/web-components/interaction/bds-popover.js +1 -1
- package/dist/web-components/interaction/bds-skip-link.js +1 -1
- package/dist/web-components/interaction/bds-tab-panel.js +1 -1
- package/dist/web-components/interaction/bds-tab.js +1 -1
- package/dist/web-components/interaction/bds-tabs.d.ts +1 -0
- package/dist/web-components/interaction/bds-tabs.js +1 -1
- package/dist/web-components/interaction/bds-toast-provider.js +1 -1
- package/dist/web-components/interaction/bds-tooltip.d.ts +19 -3
- package/dist/web-components/interaction/bds-tooltip.js +1 -1
- package/dist/web-components/interaction/form/bds-checkbox-group.d.ts +83 -0
- package/dist/web-components/interaction/form/bds-checkbox-group.js +7 -0
- package/dist/web-components/interaction/form/bds-checkbox.d.ts +6 -0
- package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
- package/dist/web-components/interaction/form/bds-combobox.d.ts +18 -0
- package/dist/web-components/interaction/form/bds-combobox.js +1 -1
- package/dist/web-components/interaction/form/bds-field-group.d.ts +89 -0
- package/dist/web-components/interaction/form/bds-field-group.js +7 -0
- package/dist/web-components/interaction/form/bds-file-input.d.ts +1 -1
- package/dist/web-components/interaction/form/bds-file-input.js +1 -1
- package/dist/web-components/interaction/form/bds-form-input.d.ts +92 -0
- package/dist/web-components/interaction/form/bds-form-input.js +7 -0
- package/dist/web-components/interaction/form/bds-number-input.d.ts +18 -1
- package/dist/web-components/interaction/form/bds-number-input.js +1 -1
- package/dist/web-components/interaction/form/bds-radio-group.d.ts +81 -0
- package/dist/web-components/interaction/form/bds-radio-group.js +7 -0
- package/dist/web-components/interaction/form/bds-radio.d.ts +34 -6
- package/dist/web-components/interaction/form/bds-radio.js +1 -1
- package/dist/web-components/interaction/form/bds-segmented-control.d.ts +4 -4
- package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
- package/dist/web-components/interaction/form/bds-select.d.ts +22 -1
- package/dist/web-components/interaction/form/bds-select.js +1 -1
- package/dist/web-components/interaction/form/bds-slider.d.ts +42 -10
- package/dist/web-components/interaction/form/bds-slider.js +1 -1
- package/dist/web-components/interaction/form/bds-switch.d.ts +20 -0
- package/dist/web-components/interaction/form/bds-switch.js +1 -1
- package/dist/web-components/interaction/form/bds-textarea.d.ts +23 -3
- package/dist/web-components/interaction/form/bds-textarea.js +1 -1
- package/dist/web-components/layout/bds-grid-item.d.ts +63 -0
- package/dist/web-components/layout/bds-grid-item.js +7 -0
- package/dist/web-components/layout/bds-grid.d.ts +108 -0
- package/dist/web-components/layout/bds-grid.js +7 -0
- package/dist/web-components/ui/bds-alert.d.ts +16 -2
- package/dist/web-components/ui/bds-alert.js +1 -1
- package/dist/web-components/ui/bds-avatar.d.ts +2 -1
- package/dist/web-components/ui/bds-avatar.js +1 -1
- package/dist/web-components/ui/bds-badge.js +1 -1
- package/dist/web-components/ui/bds-breadcrumb.d.ts +3 -1
- package/dist/web-components/ui/bds-breadcrumb.js +1 -1
- package/dist/web-components/ui/bds-button-container.d.ts +52 -0
- package/dist/web-components/ui/bds-button-container.js +7 -0
- package/dist/web-components/ui/bds-button-group.d.ts +21 -0
- package/dist/web-components/ui/bds-button-group.js +8 -0
- package/dist/web-components/ui/bds-calendar.d.ts +121 -0
- package/dist/web-components/ui/bds-calendar.js +7 -0
- package/dist/web-components/ui/bds-card.d.ts +12 -3
- package/dist/web-components/ui/bds-card.js +1 -1
- package/dist/web-components/ui/bds-carousel.d.ts +98 -0
- package/dist/web-components/ui/bds-carousel.js +7 -0
- package/dist/web-components/ui/bds-description-list.js +1 -1
- package/dist/web-components/ui/bds-icon-wrapper.d.ts +0 -9
- package/dist/web-components/ui/bds-icon-wrapper.js +1 -1
- package/dist/web-components/ui/bds-link.js +1 -1
- package/dist/web-components/ui/bds-loading.js +1 -1
- package/dist/web-components/ui/bds-notification-banner.js +1 -1
- package/dist/web-components/ui/bds-pagination.d.ts +47 -0
- package/dist/web-components/ui/bds-pagination.js +7 -0
- package/dist/web-components/ui/bds-progress-circle.d.ts +3 -1
- package/dist/web-components/ui/bds-progress-circle.js +1 -1
- package/dist/web-components/ui/bds-progress.d.ts +3 -1
- package/dist/web-components/ui/bds-progress.js +1 -1
- package/dist/web-components/ui/bds-rating.js +1 -1
- package/dist/web-components/ui/bds-section-header.d.ts +18 -4
- 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-skeleton.js +1 -1
- package/dist/web-components/ui/bds-table.d.ts +52 -0
- package/dist/web-components/ui/bds-table.js +7 -0
- package/dist/web-components/ui/bds-typography.d.ts +1 -0
- package/dist/web-components/ui/bds-typography.js +1 -1
- package/package.json +60 -3
- package/src/components/interaction/Button/Button.module.css +23 -14
- package/src/components/interaction/Button/Button.spec.tsx +20 -0
- package/src/components/interaction/Button/Button.tsx +2 -2
- package/src/components/interaction/Command/Command.mdx +5 -2
- package/src/components/interaction/Command/Command.module.css +23 -7
- package/src/components/interaction/Command/Command.spec.tsx +35 -0
- package/src/components/interaction/Command/Command.tsx +15 -4
- package/src/components/interaction/Command/index.ts +1 -1
- package/src/components/interaction/Dialog/Dialog.mdx +4 -4
- package/src/components/interaction/Dialog/Dialog.module.css +20 -0
- package/src/components/interaction/Dialog/Dialog.spec.tsx +20 -0
- package/src/components/interaction/Dialog/Dialog.tsx +8 -3
- package/src/components/interaction/Dialog/index.ts +1 -0
- package/src/components/interaction/Drawer/Drawer.mdx +5 -2
- package/src/components/interaction/Drawer/Drawer.module.css +34 -3
- package/src/components/interaction/Drawer/Drawer.spec.tsx +67 -1
- package/src/components/interaction/Drawer/Drawer.tsx +30 -12
- package/src/components/interaction/Drawer/index.ts +1 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +2 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +34 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +15 -5
- package/src/components/interaction/DropdownMenu/index.ts +1 -0
- package/src/components/interaction/Popover/Popover.mdx +6 -2
- package/src/components/interaction/Popover/Popover.module.css +26 -18
- package/src/components/interaction/Popover/Popover.spec.tsx +38 -0
- package/src/components/interaction/Popover/Popover.tsx +19 -5
- package/src/components/interaction/Popover/index.ts +1 -0
- package/src/components/interaction/Rating/Rating.mdx +1 -1
- package/src/components/interaction/Rating/Rating.module.css +2 -2
- package/src/components/interaction/Rating/Rating.tsx +1 -1
- package/src/components/interaction/Rating/index.ts +1 -0
- package/src/components/interaction/Toast/Toast.mdx +3 -0
- package/src/components/interaction/Toast/Toast.module.css +35 -5
- package/src/components/interaction/Toast/Toast.spec.tsx +69 -1
- package/src/components/interaction/Toast/Toast.tsx +15 -9
- package/src/components/interaction/form/Checkbox/Checkbox.mdx +3 -0
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +13 -6
- package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +6 -0
- package/src/components/interaction/form/Checkbox/Checkbox.tsx +1 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.mdx +16 -1
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.module.css +24 -8
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.spec.tsx +32 -2
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.tsx +14 -2
- package/src/components/interaction/form/Combobox/Combobox.mdx +11 -4
- package/src/components/interaction/form/Combobox/Combobox.module.css +6 -6
- package/src/components/interaction/form/Combobox/Combobox.spec.tsx +47 -0
- package/src/components/interaction/form/Combobox/Combobox.tsx +10 -5
- package/src/components/interaction/form/FieldGroup/FieldGroup.mdx +9 -10
- package/src/components/interaction/form/FieldGroup/FieldGroup.module.css +57 -23
- package/src/components/interaction/form/FieldGroup/FieldGroup.spec.tsx +129 -7
- package/src/components/interaction/form/FieldGroup/FieldGroup.tsx +72 -12
- package/src/components/interaction/form/FileInput/FileInput.mdx +9 -1
- package/src/components/interaction/form/FileInput/FileInput.module.css +8 -8
- package/src/components/interaction/form/FileInput/FileInput.spec.tsx +89 -1
- package/src/components/interaction/form/FileInput/FileInput.tsx +6 -2
- package/src/components/interaction/form/FormInput/FormInput.mdx +9 -5
- package/src/components/interaction/form/FormInput/FormInput.module.css +17 -8
- package/src/components/interaction/form/FormInput/FormInput.spec.tsx +20 -0
- package/src/components/interaction/form/FormInput/FormInput.tsx +7 -2
- package/src/components/interaction/form/NumberInput/NumberInput.mdx +5 -1
- package/src/components/interaction/form/NumberInput/NumberInput.module.css +6 -6
- package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +51 -0
- package/src/components/interaction/form/NumberInput/NumberInput.tsx +36 -8
- package/src/components/interaction/form/Radio/Radio.mdx +8 -1
- package/src/components/interaction/form/Radio/Radio.module.css +20 -7
- package/src/components/interaction/form/Radio/Radio.spec.tsx +15 -0
- package/src/components/interaction/form/Radio/Radio.tsx +10 -4
- package/src/components/interaction/form/RadioGroup/RadioGroup.mdx +16 -1
- package/src/components/interaction/form/RadioGroup/RadioGroup.module.css +24 -8
- package/src/components/interaction/form/RadioGroup/RadioGroup.spec.tsx +32 -2
- package/src/components/interaction/form/RadioGroup/RadioGroup.tsx +14 -2
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +9 -3
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +25 -12
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +86 -5
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +44 -4
- package/src/components/interaction/form/Select/Select.mdx +10 -4
- package/src/components/interaction/form/Select/Select.module.css +4 -4
- package/src/components/interaction/form/Select/Select.spec.tsx +34 -0
- package/src/components/interaction/form/Select/Select.tsx +21 -9
- package/src/components/interaction/form/Slider/Slider.mdx +4 -1
- package/src/components/interaction/form/Slider/Slider.module.css +32 -8
- package/src/components/interaction/form/Slider/Slider.spec.tsx +29 -0
- package/src/components/interaction/form/Slider/Slider.tsx +9 -7
- package/src/components/interaction/form/Switch/Switch.mdx +2 -2
- package/src/components/interaction/form/Switch/Switch.module.css +12 -13
- package/src/components/interaction/form/Switch/Switch.spec.tsx +20 -0
- package/src/components/interaction/form/Switch/Switch.tsx +2 -1
- package/src/components/interaction/form/Textarea/Textarea.mdx +5 -0
- package/src/components/interaction/form/Textarea/Textarea.module.css +5 -5
- package/src/components/interaction/form/Textarea/Textarea.spec.tsx +13 -0
- package/src/components/interaction/form/Textarea/Textarea.tsx +3 -1
- package/src/components/interaction/form/atoms/Message.module.css +1 -1
- package/src/components/interaction/form/atoms/Message.spec.tsx +29 -0
- package/src/components/interaction/form/atoms/Message.tsx +1 -1
- package/src/components/layout/ButtonGroup/ButtonGroup.mdx +11 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -5
- package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +14 -0
- package/src/components/layout/Card/Card.mdx +6 -1
- package/src/components/layout/Card/Card.module.css +26 -5
- package/src/components/layout/Card/Card.spec.tsx +7 -0
- package/src/components/layout/Card/Card.tsx +4 -3
- package/src/components/layout/Grid/Grid.spec.tsx +44 -0
- package/src/components/layout/Grid/Grid.tsx +5 -5
- package/src/components/layout/Grid/GridItem.tsx +6 -6
- package/src/components/layout/Grid/masonry.ts +31 -5
- package/src/components/layout/Grid/useIsomorphicLayoutEffect.ts +11 -0
- package/src/components/layout/IconWrapper/IconWrapper.mdx +8 -2
- package/src/components/layout/IconWrapper/IconWrapper.module.css +9 -6
- package/src/components/layout/SectionHeader/SectionHeader.mdx +4 -1
- package/src/components/layout/SectionHeader/SectionHeader.module.css +10 -9
- package/src/components/ui/Accordion/Accordion.mdx +4 -1
- package/src/components/ui/Accordion/Accordion.module.css +2 -1
- package/src/components/ui/Alert/Alert.mdx +21 -4
- package/src/components/ui/Alert/Alert.module.css +9 -4
- package/src/components/ui/Alert/Alert.spec.tsx +20 -0
- package/src/components/ui/Avatar/Avatar.mdx +8 -3
- package/src/components/ui/Avatar/Avatar.module.css +13 -8
- package/src/components/ui/Badge/Badge.mdx +3 -1
- package/src/components/ui/Breadcrumb/Breadcrumb.mdx +3 -0
- package/src/components/ui/Breadcrumb/Breadcrumb.module.css +4 -0
- package/src/components/ui/Breadcrumb/Breadcrumb.spec.tsx +12 -0
- package/src/components/ui/Breadcrumb/Breadcrumb.tsx +11 -3
- package/src/components/ui/Calendar/Calendar.mdx +6 -0
- package/src/components/ui/Calendar/Calendar.module.css +68 -19
- package/src/components/ui/Calendar/Calendar.spec.tsx +139 -24
- package/src/components/ui/Calendar/Calendar.stories.tsx +14 -0
- package/src/components/ui/Calendar/Calendar.tsx +143 -154
- package/src/components/ui/Calendar/CalendarRange.mdx +39 -0
- package/src/components/ui/Calendar/CalendarRange.spec.tsx +119 -0
- package/src/components/ui/Calendar/CalendarRange.stories.tsx +51 -0
- package/src/components/ui/Calendar/CalendarRange.tsx +213 -0
- package/src/components/ui/Calendar/MonthGrid.tsx +141 -0
- package/src/components/ui/Calendar/calendar-core.spec.ts +176 -0
- package/src/components/ui/Calendar/calendar-core.ts +164 -0
- package/src/components/ui/Calendar/index.ts +1 -0
- package/src/components/ui/Carousel/Carousel.mdx +8 -3
- package/src/components/ui/Carousel/Carousel.module.css +7 -5
- package/src/components/ui/Carousel/Carousel.spec.tsx +67 -0
- package/src/components/ui/Carousel/Carousel.tsx +19 -7
- package/src/components/ui/Collapsible/Collapsible.mdx +17 -6
- package/src/components/ui/Collapsible/Collapsible.module.css +7 -2
- package/src/components/ui/Collapsible/Collapsible.spec.tsx +40 -0
- package/src/components/ui/Collapsible/Collapsible.tsx +19 -2
- package/src/components/ui/DescriptionList/DescriptionList.mdx +2 -0
- package/src/components/ui/Link/Link.mdx +4 -2
- package/src/components/ui/Loading/Loading.mdx +6 -3
- package/src/components/ui/Loading/Loading.module.css +7 -7
- package/src/components/ui/Loading/Loading.spec.tsx +12 -0
- package/src/components/ui/Loading/Loading.tsx +8 -3
- package/src/components/ui/NotificationBanner/NotificationBanner.mdx +4 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +5 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +20 -0
- package/src/components/ui/Pagination/Pagination.mdx +29 -7
- package/src/components/ui/Pagination/Pagination.module.css +15 -14
- package/src/components/ui/Progress/Progress.mdx +6 -2
- package/src/components/ui/Progress/Progress.module.css +1 -1
- package/src/components/ui/Progress/Progress.spec.tsx +17 -0
- package/src/components/ui/Progress/Progress.tsx +2 -2
- package/src/components/ui/ProgressCircle/ProgressCircle.mdx +8 -4
- package/src/components/ui/ProgressCircle/ProgressCircle.module.css +2 -2
- package/src/components/ui/ProgressCircle/ProgressCircle.spec.tsx +11 -1
- package/src/components/ui/ProgressCircle/ProgressCircle.tsx +1 -1
- package/src/components/ui/Separator/Separator.mdx +7 -0
- package/src/components/ui/Separator/Separator.module.css +1 -0
- package/src/components/ui/Skeleton/Skeleton.mdx +17 -0
- package/src/components/ui/Skeleton/Skeleton.module.css +12 -5
- package/src/components/ui/SkipLink/SkipLink.mdx +19 -0
- package/src/components/ui/SkipLink/SkipLink.module.css +8 -7
- package/src/components/ui/SkipLink/SkipLink.spec.tsx +28 -0
- package/src/components/ui/Table/Table.mdx +14 -0
- package/src/components/ui/Table/Table.module.css +13 -8
- package/src/components/ui/Table/Table.spec.tsx +56 -0
- package/src/components/ui/Table/Table.tsx +44 -6
- package/src/components/ui/Tabs/Tabs.mdx +16 -0
- package/src/components/ui/Tabs/Tabs.module.css +3 -3
- package/src/components/ui/Tabs/Tabs.spec.tsx +20 -0
- package/src/components/ui/Tabs/Tabs.tsx +5 -1
- package/src/components/ui/Tooltip/Tooltip.mdx +9 -0
- package/src/components/ui/Tooltip/Tooltip.module.css +32 -12
- package/src/components/ui/Tooltip/Tooltip.spec.tsx +56 -0
- package/src/components/ui/Tooltip/Tooltip.tsx +26 -7
- package/src/components/ui/Typography/Typography.mdx +7 -0
- package/src/components/ui/Typography/Typography.module.css +5 -5
- package/src/index.ts +7 -3
- package/src/stories/DesignSystem/Borders.mdx +14 -9
- package/src/stories/DesignSystem/Colors.mdx +43 -41
- package/src/stories/DesignSystem/Overview.mdx +4 -3
- package/src/stories/Introduction.mdx +5 -5
- package/src/web-components/globals.ts +7 -0
- package/src/web-components/index.ts +2 -0
- package/src/web-components/interaction/BdsAccordion.mdx +10 -1
- package/src/web-components/interaction/BdsButton.mdx +15 -4
- package/src/web-components/interaction/BdsButton.stories.tsx +1 -1
- package/src/web-components/interaction/BdsCollapsible.mdx +3 -3
- package/src/web-components/interaction/BdsDialog.mdx +12 -7
- package/src/web-components/interaction/BdsDialog.stories.tsx +2 -2
- package/src/web-components/interaction/BdsDrawer.mdx +9 -6
- package/src/web-components/interaction/BdsDropdownMenu.mdx +20 -1
- package/src/web-components/interaction/BdsPopover.mdx +7 -3
- package/src/web-components/interaction/BdsSkipLink.mdx +14 -1
- package/src/web-components/interaction/BdsTabs.mdx +18 -1
- package/src/web-components/interaction/BdsToastProvider.mdx +21 -3
- package/src/web-components/interaction/BdsTooltip.mdx +4 -4
- package/src/web-components/interaction/bds-accordion-item.ts +10 -0
- package/src/web-components/interaction/bds-accordion.spec.ts +41 -0
- package/src/web-components/interaction/bds-accordion.ts +28 -2
- package/src/web-components/interaction/bds-button.spec.ts +81 -0
- package/src/web-components/interaction/bds-button.ts +55 -8
- package/src/web-components/interaction/bds-collapsible.spec.ts +93 -2
- package/src/web-components/interaction/bds-collapsible.ts +49 -7
- package/src/web-components/interaction/bds-dialog.spec.ts +49 -0
- package/src/web-components/interaction/bds-dialog.ts +68 -11
- package/src/web-components/interaction/bds-drawer.spec.ts +93 -4
- package/src/web-components/interaction/bds-drawer.ts +54 -21
- package/src/web-components/interaction/bds-dropdown-menu-item.ts +12 -1
- package/src/web-components/interaction/bds-dropdown-menu.spec.ts +63 -0
- package/src/web-components/interaction/bds-dropdown-menu.ts +45 -7
- package/src/web-components/interaction/bds-popover.spec.ts +80 -6
- package/src/web-components/interaction/bds-popover.ts +83 -32
- package/src/web-components/interaction/bds-skip-link.spec.ts +18 -0
- package/src/web-components/interaction/bds-skip-link.ts +29 -8
- package/src/web-components/interaction/bds-tab-panel.ts +10 -0
- package/src/web-components/interaction/bds-tab.spec.ts +25 -0
- package/src/web-components/interaction/bds-tab.ts +20 -5
- package/src/web-components/interaction/bds-tabs.spec.ts +92 -5
- package/src/web-components/interaction/bds-tabs.ts +42 -1
- package/src/web-components/interaction/bds-toast-provider.spec.ts +43 -0
- package/src/web-components/interaction/bds-toast-provider.ts +50 -21
- package/src/web-components/interaction/bds-tooltip.spec.ts +71 -0
- package/src/web-components/interaction/bds-tooltip.ts +100 -5
- package/src/web-components/interaction/form/BdsCheckbox.mdx +20 -1
- package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +8 -0
- package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +4 -3
- package/src/web-components/interaction/form/BdsCombobox.mdx +39 -2
- package/src/web-components/interaction/form/BdsCombobox.stories.tsx +31 -0
- package/src/web-components/interaction/form/BdsFieldGroup.mdx +8 -3
- package/src/web-components/interaction/form/BdsFieldGroup.stories.tsx +1 -1
- package/src/web-components/interaction/form/BdsFileInput.mdx +26 -5
- package/src/web-components/interaction/form/BdsFormInput.mdx +16 -1
- package/src/web-components/interaction/form/BdsNumberInput.mdx +34 -2
- package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +27 -3
- package/src/web-components/interaction/form/BdsRadio.mdx +39 -12
- package/src/web-components/interaction/form/BdsRadio.stories.tsx +26 -0
- package/src/web-components/interaction/form/BdsRadioGroup.mdx +9 -4
- package/src/web-components/interaction/form/BdsSegmentedControl.mdx +37 -11
- package/src/web-components/interaction/form/BdsSelect.mdx +22 -4
- package/src/web-components/interaction/form/BdsSelect.stories.tsx +33 -0
- package/src/web-components/interaction/form/BdsSlider.mdx +36 -19
- package/src/web-components/interaction/form/BdsSlider.stories.tsx +24 -0
- package/src/web-components/interaction/form/BdsSwitch.mdx +38 -11
- package/src/web-components/interaction/form/BdsSwitch.stories.tsx +16 -0
- package/src/web-components/interaction/form/BdsTextarea.mdx +42 -16
- package/src/web-components/interaction/form/BdsTextarea.stories.tsx +21 -0
- package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +99 -0
- package/src/web-components/interaction/form/bds-checkbox-group.ts +95 -9
- package/src/web-components/interaction/form/bds-checkbox.spec.ts +55 -0
- package/src/web-components/interaction/form/bds-checkbox.ts +30 -5
- package/src/web-components/interaction/form/bds-combobox.spec.ts +79 -0
- package/src/web-components/interaction/form/bds-combobox.ts +101 -16
- package/src/web-components/interaction/form/bds-field-group.spec.ts +158 -0
- package/src/web-components/interaction/form/bds-field-group.ts +167 -23
- package/src/web-components/interaction/form/bds-file-input.spec.ts +73 -5
- package/src/web-components/interaction/form/bds-file-input.ts +71 -34
- package/src/web-components/interaction/form/bds-form-input.spec.ts +22 -0
- package/src/web-components/interaction/form/bds-form-input.ts +27 -16
- package/src/web-components/interaction/form/bds-number-input.spec.ts +49 -0
- package/src/web-components/interaction/form/bds-number-input.ts +118 -29
- package/src/web-components/interaction/form/bds-radio-group.spec.ts +57 -0
- package/src/web-components/interaction/form/bds-radio-group.ts +60 -12
- package/src/web-components/interaction/form/bds-radio.spec.ts +123 -0
- package/src/web-components/interaction/form/bds-radio.ts +135 -22
- package/src/web-components/interaction/form/bds-segmented-control.spec.ts +55 -0
- package/src/web-components/interaction/form/bds-segmented-control.ts +81 -59
- package/src/web-components/interaction/form/bds-select.spec.ts +57 -0
- package/src/web-components/interaction/form/bds-select.ts +95 -7
- package/src/web-components/interaction/form/bds-slider.spec.ts +83 -0
- package/src/web-components/interaction/form/bds-slider.ts +154 -26
- package/src/web-components/interaction/form/bds-switch.spec.ts +68 -0
- package/src/web-components/interaction/form/bds-switch.ts +87 -20
- package/src/web-components/interaction/form/bds-textarea.spec.ts +71 -0
- package/src/web-components/interaction/form/bds-textarea.ts +108 -7
- package/src/web-components/layout/BdsGrid.mdx +6 -2
- package/src/web-components/layout/BdsGrid.stories.tsx +4 -0
- package/src/web-components/layout/bds-grid-item.spec.ts +21 -0
- package/src/web-components/layout/bds-grid-item.ts +17 -3
- package/src/web-components/layout/bds-grid.spec.ts +98 -0
- package/src/web-components/layout/bds-grid.ts +139 -19
- package/src/web-components/ui/BdsAlert.mdx +6 -5
- package/src/web-components/ui/BdsAlert.stories.tsx +6 -6
- package/src/web-components/ui/BdsAvatar.mdx +4 -2
- package/src/web-components/ui/BdsBreadcrumb.mdx +3 -2
- package/src/web-components/ui/BdsButtonContainer.mdx +64 -0
- package/src/web-components/ui/BdsButtonContainer.stories.tsx +84 -0
- package/src/web-components/ui/BdsCalendar.mdx +29 -4
- package/src/web-components/ui/BdsCalendar.stories.tsx +16 -18
- package/src/web-components/ui/BdsCalendarRange.mdx +71 -0
- package/src/web-components/ui/BdsCalendarRange.stories.tsx +31 -0
- package/src/web-components/ui/BdsCard.mdx +7 -3
- package/src/web-components/ui/BdsCard.stories.tsx +17 -1
- package/src/web-components/ui/BdsCarousel.mdx +40 -12
- package/src/web-components/ui/BdsDescriptionList.mdx +6 -1
- package/src/web-components/ui/BdsIconWrapper.mdx +3 -2
- package/src/web-components/ui/BdsLink.mdx +14 -2
- package/src/web-components/ui/BdsNotificationBanner.mdx +18 -0
- package/src/web-components/ui/BdsPagination.mdx +40 -1
- package/src/web-components/ui/BdsProgress.mdx +8 -2
- package/src/web-components/ui/BdsProgressCircle.mdx +5 -2
- package/src/web-components/ui/BdsRating.mdx +7 -0
- package/src/web-components/ui/BdsSectionHeader.mdx +22 -3
- package/src/web-components/ui/BdsSectionHeader.stories.tsx +15 -13
- package/src/web-components/ui/BdsTable.mdx +23 -2
- package/src/web-components/ui/BdsTable.stories.tsx +8 -2
- package/src/web-components/ui/BdsTypography.mdx +17 -1
- package/src/web-components/ui/bds-alert.spec.ts +17 -2
- package/src/web-components/ui/bds-alert.ts +31 -5
- package/src/web-components/ui/bds-avatar.spec.ts +29 -0
- package/src/web-components/ui/bds-avatar.ts +29 -11
- package/src/web-components/ui/bds-badge.spec.ts +5 -0
- package/src/web-components/ui/bds-badge.ts +11 -1
- package/src/web-components/ui/bds-breadcrumb.spec.ts +30 -0
- package/src/web-components/ui/bds-breadcrumb.ts +26 -4
- package/src/web-components/ui/bds-button-container.spec.ts +39 -0
- package/src/web-components/ui/bds-button-container.ts +123 -0
- package/src/web-components/ui/bds-button-group.spec.ts +14 -20
- package/src/web-components/ui/bds-button-group.ts +13 -64
- package/src/web-components/ui/bds-calendar-range.spec.ts +123 -0
- package/src/web-components/ui/bds-calendar-range.ts +307 -0
- package/src/web-components/ui/bds-calendar.spec.ts +176 -0
- package/src/web-components/ui/bds-calendar.ts +186 -344
- package/src/web-components/ui/bds-card.spec.ts +32 -0
- package/src/web-components/ui/bds-card.ts +34 -9
- package/src/web-components/ui/bds-carousel.spec.ts +147 -0
- package/src/web-components/ui/bds-carousel.ts +109 -32
- package/src/web-components/ui/bds-description-list.spec.ts +15 -0
- package/src/web-components/ui/bds-description-list.ts +37 -3
- package/src/web-components/ui/bds-icon-wrapper.spec.ts +12 -0
- package/src/web-components/ui/bds-icon-wrapper.ts +15 -14
- package/src/web-components/ui/bds-link.spec.ts +17 -0
- package/src/web-components/ui/bds-link.ts +26 -11
- package/src/web-components/ui/bds-loading.ts +10 -0
- package/src/web-components/ui/bds-notification-banner.spec.ts +13 -0
- package/src/web-components/ui/bds-notification-banner.ts +18 -1
- package/src/web-components/ui/bds-pagination.spec.ts +44 -0
- package/src/web-components/ui/bds-pagination.ts +39 -19
- package/src/web-components/ui/bds-progress-circle.spec.ts +21 -0
- package/src/web-components/ui/bds-progress-circle.ts +17 -5
- package/src/web-components/ui/bds-progress.spec.ts +21 -0
- package/src/web-components/ui/bds-progress.ts +17 -4
- package/src/web-components/ui/bds-rating.spec.ts +6 -0
- package/src/web-components/ui/bds-rating.ts +12 -2
- package/src/web-components/ui/bds-section-header.spec.ts +39 -8
- package/src/web-components/ui/bds-section-header.ts +47 -13
- package/src/web-components/ui/bds-separator.ts +10 -0
- package/src/web-components/ui/bds-skeleton.ts +10 -0
- package/src/web-components/ui/bds-table.spec.ts +63 -0
- package/src/web-components/ui/bds-table.ts +88 -28
- package/src/web-components/ui/bds-typography.spec.ts +47 -0
- package/src/web-components/ui/bds-typography.ts +28 -10
- package/src/web-components/ui/calendar-styles.ts +94 -0
- package/dist/web-components/chunk-7YURXHTS.js +0 -147
- package/dist/web-components/chunk-A3RLT3AS.js +0 -49
- package/dist/web-components/chunk-AGKJTMJI.js +0 -56
- package/dist/web-components/chunk-BQLZ7I4G.js +0 -56
- package/dist/web-components/chunk-DZOEPVFT.js +0 -107
- package/dist/web-components/chunk-JYYRJS2D.js +0 -118
- package/dist/web-components/chunk-Y4MAXTMA.js +0 -80
- package/dist/web-components/chunk-Y7V7LM6R.js +0 -98
- package/src/web-components/ui/BdsButtonGroup.mdx +0 -69
- package/src/web-components/ui/BdsButtonGroup.stories.tsx +0 -66
|
@@ -4,11 +4,12 @@ import { installInvokerCommandsPolyfill } from '../../../polyfill-invoker-comman
|
|
|
4
4
|
// Install once at module load — no-op if native support is present.
|
|
5
5
|
installInvokerCommandsPolyfill();
|
|
6
6
|
|
|
7
|
+
const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
7
8
|
import css from './Drawer.module.css';
|
|
8
9
|
import { cn } from '@boostdev/design-system-foundation';
|
|
9
10
|
import type { WithClassName } from '../../../types';
|
|
10
11
|
|
|
11
|
-
interface DrawerProps extends WithClassName, Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
|
|
12
|
+
export interface DrawerProps extends WithClassName, Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
|
|
12
13
|
isOpen: boolean;
|
|
13
14
|
onOpen?: () => void;
|
|
14
15
|
onClose: () => void;
|
|
@@ -29,6 +30,8 @@ export function Drawer({
|
|
|
29
30
|
}: Readonly<DrawerProps>) {
|
|
30
31
|
const generatedId = useId();
|
|
31
32
|
const id = idProp ?? generatedId;
|
|
33
|
+
const titleId = useId();
|
|
34
|
+
const hasCustomLabel = 'aria-label' in rest || 'aria-labelledby' in rest;
|
|
32
35
|
|
|
33
36
|
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
34
37
|
const triggerRef = useRef<Element | null>(null);
|
|
@@ -70,15 +73,12 @@ export function Drawer({
|
|
|
70
73
|
return () => dialog.removeEventListener('command', handleCommand);
|
|
71
74
|
}, [onOpen, onClose]);
|
|
72
75
|
|
|
73
|
-
// Close on backdrop click
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
e.clientX < rect.left || e.clientX > rect.right ||
|
|
80
|
-
e.clientY < rect.top || e.clientY > rect.bottom;
|
|
81
|
-
if (outside) onClose();
|
|
76
|
+
// Close on backdrop click. The dialog element's own box is only the visible
|
|
77
|
+
// panel — clicking the ::backdrop dispatches with e.target === the dialog
|
|
78
|
+
// itself, so this correctly ignores clicks (including keyboard-activated
|
|
79
|
+
// synthetic clicks at 0,0) on controls inside the panel.
|
|
80
|
+
const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => {
|
|
81
|
+
if (e.target === dialogRef.current) onClose();
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
// Close on Escape (dialog handles it natively, but we sync state)
|
|
@@ -87,6 +87,22 @@ export function Drawer({
|
|
|
87
87
|
onClose();
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
// Trap Tab focus inside the drawer while open, matching Dialog's behaviour.
|
|
91
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLDialogElement>) => {
|
|
92
|
+
if (e.key !== 'Tab') return;
|
|
93
|
+
const dialog = dialogRef.current;
|
|
94
|
+
if (!dialog) return;
|
|
95
|
+
const focusable = Array.from(dialog.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
|
96
|
+
if (focusable.length === 0) return;
|
|
97
|
+
const first = focusable[0];
|
|
98
|
+
const last = focusable[focusable.length - 1];
|
|
99
|
+
if (e.shiftKey) {
|
|
100
|
+
if (document.activeElement === first) { e.preventDefault(); last.focus(); }
|
|
101
|
+
} else {
|
|
102
|
+
if (document.activeElement === last) { e.preventDefault(); first.focus(); }
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
90
106
|
return (
|
|
91
107
|
<dialog
|
|
92
108
|
{...rest}
|
|
@@ -94,11 +110,13 @@ export function Drawer({
|
|
|
94
110
|
id={id}
|
|
95
111
|
className={cn(css.drawer, css[`--side_${side}`], className)}
|
|
96
112
|
aria-modal="true"
|
|
97
|
-
|
|
113
|
+
aria-labelledby={hasCustomLabel ? rest['aria-labelledby'] : (title ? titleId : undefined)}
|
|
114
|
+
onClick={handleBackdropClick}
|
|
98
115
|
onCancel={handleCancel}
|
|
116
|
+
onKeyDown={handleKeyDown}
|
|
99
117
|
>
|
|
100
118
|
<div className={css.header}>
|
|
101
|
-
{!!title && title}
|
|
119
|
+
{!!title && <div id={titleId}>{title}</div>}
|
|
102
120
|
<button
|
|
103
121
|
type="button"
|
|
104
122
|
className={css.closeButton}
|
|
@@ -57,3 +57,5 @@ Floating menu with a list of selectable actions, triggered by a button. Supports
|
|
|
57
57
|
- Implements ARIA menu pattern: `role="menu"`, `role="menuitem"`
|
|
58
58
|
- Arrow keys navigate items; `Enter`/`Space` activate; `Escape` closes
|
|
59
59
|
- Disabled items have `aria-disabled="true"` and are skipped during keyboard navigation
|
|
60
|
+
- Opening the menu focuses the first enabled item (disabled items are skipped as the initial target)
|
|
61
|
+
- Closing the menu via `Escape` or item activation returns focus to the trigger; closing via `Tab` lets focus move naturally to the next element
|
|
@@ -71,4 +71,38 @@ describe('DropdownMenu', () => {
|
|
|
71
71
|
await user.keyboard('{ArrowDown}');
|
|
72
72
|
expect(screen.getByRole('menuitem', { name: 'Duplicate' })).toHaveFocus();
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
it('restores focus to the trigger when Escape is pressed', async () => {
|
|
76
|
+
const user = userEvent.setup();
|
|
77
|
+
render(<DropdownMenu trigger={<button type="button">Actions</button>} items={items} />);
|
|
78
|
+
await user.click(screen.getByRole('button', { name: 'Actions' }));
|
|
79
|
+
await user.keyboard('{Escape}');
|
|
80
|
+
expect(screen.getByRole('button', { name: 'Actions' })).toHaveFocus();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('restores focus to the trigger when a menu item is activated', async () => {
|
|
84
|
+
const user = userEvent.setup();
|
|
85
|
+
render(
|
|
86
|
+
<DropdownMenu
|
|
87
|
+
trigger={<button type="button">Actions</button>}
|
|
88
|
+
items={[{ id: 'edit', label: 'Edit', onClick: vi.fn() }]}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
await user.click(screen.getByRole('button', { name: 'Actions' }));
|
|
92
|
+
await user.click(screen.getByRole('menuitem', { name: 'Edit' }));
|
|
93
|
+
expect(screen.getByRole('button', { name: 'Actions' })).toHaveFocus();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('focuses the first enabled item when the first item is disabled', async () => {
|
|
97
|
+
const user = userEvent.setup();
|
|
98
|
+
const itemsWithDisabledFirst = [
|
|
99
|
+
{ id: 'delete', label: 'Delete', onClick: vi.fn(), disabled: true },
|
|
100
|
+
{ id: 'edit', label: 'Edit', onClick: vi.fn() },
|
|
101
|
+
];
|
|
102
|
+
render(
|
|
103
|
+
<DropdownMenu trigger={<button type="button">Actions</button>} items={itemsWithDisabledFirst} />
|
|
104
|
+
);
|
|
105
|
+
await user.click(screen.getByRole('button', { name: 'Actions' }));
|
|
106
|
+
expect(screen.getByRole('menuitem', { name: 'Edit' })).toHaveFocus();
|
|
107
|
+
});
|
|
74
108
|
});
|
|
@@ -22,7 +22,7 @@ export interface DropdownMenuItem {
|
|
|
22
22
|
separator?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
interface DropdownMenuProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
|
|
25
|
+
export interface DropdownMenuProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
|
|
26
26
|
trigger: ReactElement;
|
|
27
27
|
items: DropdownMenuItem[];
|
|
28
28
|
placement?: 'bottom-start' | 'bottom-end';
|
|
@@ -39,15 +39,25 @@ export function DropdownMenu({
|
|
|
39
39
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
40
40
|
const menuId = useId();
|
|
41
41
|
const itemRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
42
|
+
const triggerRef = useRef<Element | null>(null);
|
|
42
43
|
const open = () => {
|
|
44
|
+
triggerRef.current = document.activeElement;
|
|
43
45
|
setIsOpen(true);
|
|
44
46
|
};
|
|
45
47
|
|
|
46
|
-
const close = () =>
|
|
48
|
+
const close = (restoreFocus = false) => {
|
|
49
|
+
setIsOpen(false);
|
|
50
|
+
if (restoreFocus) {
|
|
51
|
+
(triggerRef.current as HTMLElement | null)?.focus();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
47
54
|
|
|
48
55
|
useEffect(() => {
|
|
49
56
|
if (isOpen) {
|
|
50
|
-
|
|
57
|
+
const firstEnabledIndex = items.findIndex(item => !item.disabled);
|
|
58
|
+
if (firstEnabledIndex !== -1) {
|
|
59
|
+
itemRefs.current[firstEnabledIndex]?.focus();
|
|
60
|
+
}
|
|
51
61
|
}
|
|
52
62
|
}, [isOpen]);
|
|
53
63
|
|
|
@@ -58,7 +68,7 @@ export function DropdownMenu({
|
|
|
58
68
|
if (!containerRef.current?.contains(e.target as Node)) close();
|
|
59
69
|
};
|
|
60
70
|
const handleKeyDown = (e: globalThis.KeyboardEvent) => {
|
|
61
|
-
if (e.key === 'Escape') close();
|
|
71
|
+
if (e.key === 'Escape') close(true);
|
|
62
72
|
};
|
|
63
73
|
|
|
64
74
|
document.addEventListener('pointerdown', handlePointerDown);
|
|
@@ -124,7 +134,7 @@ export function DropdownMenu({
|
|
|
124
134
|
role="menuitem"
|
|
125
135
|
disabled={item.disabled}
|
|
126
136
|
className={css.item}
|
|
127
|
-
onClick={() => { item.onClick?.(); close(); }}
|
|
137
|
+
onClick={() => { item.onClick?.(); close(true); }}
|
|
128
138
|
onKeyDown={e => handleItemKeyDown(e, index)}
|
|
129
139
|
>
|
|
130
140
|
{item.icon && <span className={css.icon} aria-hidden="true">{item.icon}</span>}
|
|
@@ -40,12 +40,16 @@ Floating panel anchored to a trigger element. Unlike `Tooltip`, popovers can con
|
|
|
40
40
|
<tr><td>`--popover_shadow`</td><td>`var(--bds-shadow_m)`</td><td>Panel box shadow</td></tr>
|
|
41
41
|
<tr><td>`--popover_border`</td><td>`none`</td><td>Panel border</td></tr>
|
|
42
42
|
<tr><td>`--popover_radius`</td><td>`var(--bdc-outline_radius)`</td><td>Panel corner radius</td></tr>
|
|
43
|
+
<tr><td>`--popover_z-index`</td><td>`var(--bds-z-index_dropdown)`</td><td>Panel stacking order</td></tr>
|
|
44
|
+
<tr><td>`--popover_max-width`</td><td>`20rem`</td><td>Panel maximum inline size</td></tr>
|
|
45
|
+
<tr><td>`--popover_max-height`</td><td>`80dvh`</td><td>Panel maximum block size</td></tr>
|
|
43
46
|
</tbody>
|
|
44
47
|
</table>
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
## Accessibility
|
|
48
51
|
|
|
49
|
-
- Trigger element gets `aria-
|
|
50
|
-
- Popover panel is linked via `aria-controls`
|
|
52
|
+
- Trigger element gets `aria-expanded` toggled on open/close (no `aria-haspopup` — the panel is a disclosure region, not a menu)
|
|
53
|
+
- Popover panel is linked to the trigger via `aria-controls`
|
|
51
54
|
- Focus moves into the panel on open; `Escape` closes and returns focus to trigger
|
|
55
|
+
- The trigger must render a native `<button>` for the built-in open/close behaviour (invoker commands are spec-limited to `<button>`). Non-`<button>` triggers still work — an `onClick` fallback calls `togglePopover()` directly — but should render an accessible, keyboard-operable control themselves.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
inset: unset;
|
|
17
17
|
margin: 0;
|
|
18
18
|
overflow-block: auto;
|
|
19
|
-
z-index: var(--popover_z-index,
|
|
19
|
+
z-index: var(--popover_z-index, var(--bds-z-index_dropdown));
|
|
20
20
|
min-inline-size: 12rem;
|
|
21
21
|
max-inline-size: var(--popover_max-width, 20rem);
|
|
22
22
|
max-block-size: var(--popover_max-height, 80dvh);
|
|
@@ -36,31 +36,40 @@
|
|
|
36
36
|
anchor() resolves relative to the element tagged with the matching anchor-name.
|
|
37
37
|
@position-try fallbacks are tried in order when the primary placement overflows. */
|
|
38
38
|
|
|
39
|
+
/* Physical top/left/right/bottom (not logical inset-*) are intentional here:
|
|
40
|
+
`placement` is a physical direction chosen by the consumer ('top' |
|
|
41
|
+
'bottom' | 'left' | 'right'), not a text-direction-relative one. Anchor
|
|
42
|
+
positioning's logical inset-inline-start/end flip meaning under
|
|
43
|
+
[dir='rtl'], which would silently reinterpret e.g. placement="right" as
|
|
44
|
+
"left" in RTL documents — changing consumer-visible behaviour, not just
|
|
45
|
+
CSS convention. Kept physical, consistent with the @position-try blocks
|
|
46
|
+
below (already an accepted logical-properties exception). */
|
|
47
|
+
|
|
39
48
|
.panel[data-placement='bottom'] {
|
|
40
49
|
top: anchor(bottom);
|
|
41
50
|
left: anchor(left);
|
|
42
|
-
margin-block-start: var(--bds-space_xs
|
|
51
|
+
margin-block-start: var(--bds-space_xs);
|
|
43
52
|
position-try-fallbacks: --popover-top, --popover-right, --popover-left;
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
.panel[data-placement='top'] {
|
|
47
56
|
bottom: anchor(top);
|
|
48
57
|
left: anchor(left);
|
|
49
|
-
margin-block-end: var(--bds-space_xs
|
|
58
|
+
margin-block-end: var(--bds-space_xs);
|
|
50
59
|
position-try-fallbacks: --popover-bottom, --popover-right, --popover-left;
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
.panel[data-placement='right'] {
|
|
54
63
|
top: anchor(top);
|
|
55
64
|
left: anchor(right);
|
|
56
|
-
margin-inline-start: var(--bds-space_xs
|
|
65
|
+
margin-inline-start: var(--bds-space_xs);
|
|
57
66
|
position-try-fallbacks: --popover-left, --popover-bottom, --popover-top;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
.panel[data-placement='left'] {
|
|
61
70
|
top: anchor(top);
|
|
62
71
|
right: anchor(left);
|
|
63
|
-
margin-inline-end: var(--bds-space_xs
|
|
72
|
+
margin-inline-end: var(--bds-space_xs);
|
|
64
73
|
position-try-fallbacks: --popover-right, --popover-bottom, --popover-top;
|
|
65
74
|
}
|
|
66
75
|
|
|
@@ -74,7 +83,7 @@
|
|
|
74
83
|
bottom: unset;
|
|
75
84
|
left: anchor(left);
|
|
76
85
|
margin: 0;
|
|
77
|
-
margin-block-start: var(--bds-space_xs
|
|
86
|
+
margin-block-start: var(--bds-space_xs);
|
|
78
87
|
}
|
|
79
88
|
|
|
80
89
|
@position-try --popover-top {
|
|
@@ -83,7 +92,7 @@
|
|
|
83
92
|
bottom: anchor(top);
|
|
84
93
|
left: anchor(left);
|
|
85
94
|
margin: 0;
|
|
86
|
-
margin-block-end: var(--bds-space_xs
|
|
95
|
+
margin-block-end: var(--bds-space_xs);
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
@position-try --popover-right {
|
|
@@ -92,7 +101,7 @@
|
|
|
92
101
|
bottom: unset;
|
|
93
102
|
left: anchor(right);
|
|
94
103
|
margin: 0;
|
|
95
|
-
margin-inline-start: var(--bds-space_xs
|
|
104
|
+
margin-inline-start: var(--bds-space_xs);
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
@position-try --popover-left {
|
|
@@ -101,7 +110,7 @@
|
|
|
101
110
|
bottom: unset;
|
|
102
111
|
left: unset;
|
|
103
112
|
margin: 0;
|
|
104
|
-
margin-inline-end: var(--bds-space_xs
|
|
113
|
+
margin-inline-end: var(--bds-space_xs);
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
/* Full-width fallback blocks for narrow screens.
|
|
@@ -111,20 +120,20 @@
|
|
|
111
120
|
|
|
112
121
|
@position-try --popover-bottom-fullwidth {
|
|
113
122
|
top: anchor(bottom);
|
|
114
|
-
right: var(--bds-space_xs
|
|
123
|
+
right: var(--bds-space_xs);
|
|
115
124
|
bottom: unset;
|
|
116
|
-
left: var(--bds-space_xs
|
|
125
|
+
left: var(--bds-space_xs);
|
|
117
126
|
margin: 0;
|
|
118
|
-
margin-block-start: var(--bds-space_xs
|
|
127
|
+
margin-block-start: var(--bds-space_xs);
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
@position-try --popover-top-fullwidth {
|
|
122
131
|
top: unset;
|
|
123
|
-
right: var(--bds-space_xs
|
|
132
|
+
right: var(--bds-space_xs);
|
|
124
133
|
bottom: anchor(top);
|
|
125
|
-
left: var(--bds-space_xs
|
|
134
|
+
left: var(--bds-space_xs);
|
|
126
135
|
margin: 0;
|
|
127
|
-
margin-block-end: var(--bds-space_xs
|
|
136
|
+
margin-block-end: var(--bds-space_xs);
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
/* Narrow screens (≤420px): span full viewport width and only flip vertically.
|
|
@@ -136,10 +145,9 @@
|
|
|
136
145
|
.panel[data-placement='top'],
|
|
137
146
|
.panel[data-placement='left'],
|
|
138
147
|
.panel[data-placement='right'] {
|
|
139
|
-
|
|
140
|
-
right: var(--bds-space_xs, 8px);
|
|
148
|
+
inset-inline: var(--bds-space_xs);
|
|
141
149
|
inline-size: 100%;
|
|
142
|
-
max-inline-size: calc(100svw - 2 * var(--bds-space_xs
|
|
150
|
+
max-inline-size: calc(100svw - 2 * var(--bds-space_xs));
|
|
143
151
|
position-try-fallbacks: --popover-top-fullwidth, --popover-bottom-fullwidth;
|
|
144
152
|
}
|
|
145
153
|
}
|
|
@@ -189,4 +189,42 @@ describe('Popover', () => {
|
|
|
189
189
|
);
|
|
190
190
|
expect(getPanel()).not.toHaveAttribute('role');
|
|
191
191
|
});
|
|
192
|
+
|
|
193
|
+
it('does not set aria-haspopup on the trigger', () => {
|
|
194
|
+
render(
|
|
195
|
+
<Popover content={<p>Panel content</p>}>
|
|
196
|
+
<button type="button">Open</button>
|
|
197
|
+
</Popover>
|
|
198
|
+
);
|
|
199
|
+
expect(screen.getByRole('button', { name: 'Open' })).not.toHaveAttribute('aria-haspopup');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('falls back to togglePopover on click when the trigger does not render a <button>', async () => {
|
|
203
|
+
const user = userEvent.setup();
|
|
204
|
+
render(
|
|
205
|
+
<Popover content={<p>Panel content</p>}>
|
|
206
|
+
<span role="button" tabIndex={0}>
|
|
207
|
+
Open
|
|
208
|
+
</span>
|
|
209
|
+
</Popover>
|
|
210
|
+
);
|
|
211
|
+
const trigger = screen.getByRole('button', { name: 'Open' });
|
|
212
|
+
await user.click(trigger);
|
|
213
|
+
expect(HTMLElement.prototype.togglePopover).toHaveBeenCalled();
|
|
214
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('still calls the consumer onClick handler for a non-<button> trigger', async () => {
|
|
218
|
+
const user = userEvent.setup();
|
|
219
|
+
const onClick = vi.fn();
|
|
220
|
+
render(
|
|
221
|
+
<Popover content={<p>Panel content</p>}>
|
|
222
|
+
<span role="button" tabIndex={0} onClick={onClick}>
|
|
223
|
+
Open
|
|
224
|
+
</span>
|
|
225
|
+
</Popover>
|
|
226
|
+
);
|
|
227
|
+
await user.click(screen.getByRole('button', { name: 'Open' }));
|
|
228
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
229
|
+
});
|
|
192
230
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HTMLAttributes,
|
|
3
|
+
MouseEvent as ReactMouseEvent,
|
|
3
4
|
ReactElement,
|
|
4
5
|
ReactNode,
|
|
5
6
|
cloneElement,
|
|
@@ -26,7 +27,7 @@ if (
|
|
|
26
27
|
import('@oddbird/css-anchor-positioning').catch(() => {});
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
interface PopoverProps extends WithClassName, Omit<HTMLAttributes<HTMLSpanElement>, 'content'> {
|
|
30
|
+
export interface PopoverProps extends WithClassName, Omit<HTMLAttributes<HTMLSpanElement>, 'content'> {
|
|
30
31
|
children: ReactElement;
|
|
31
32
|
content: ReactNode;
|
|
32
33
|
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
@@ -88,7 +89,6 @@ export function Popover({
|
|
|
88
89
|
? cloneElement(children as ReactElement<Record<string, unknown>>, {
|
|
89
90
|
'aria-expanded': isOpen,
|
|
90
91
|
'aria-controls': panelId,
|
|
91
|
-
'aria-haspopup': true,
|
|
92
92
|
commandfor: panelId,
|
|
93
93
|
command: 'toggle-popover',
|
|
94
94
|
style: {
|
|
@@ -96,9 +96,23 @@ export function Popover({
|
|
|
96
96
|
anchorName,
|
|
97
97
|
},
|
|
98
98
|
// Forward any existing onClick on the child (for custom side-effects).
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
99
|
+
// commandfor/command handles the toggle when the trigger renders a
|
|
100
|
+
// native <button> — don't also toggle there (avoids a double-toggle).
|
|
101
|
+
// Invoker commands are spec-limited to <button>, so when the actual
|
|
102
|
+
// rendered element isn't one (e.g. a plain <a> or a custom non-button
|
|
103
|
+
// Link), fall back to an explicit togglePopover() call instead of
|
|
104
|
+
// silently going dead. Checked on the DOM node (event.currentTarget),
|
|
105
|
+
// not the React element type, so wrapper components that forward
|
|
106
|
+
// props onto an inner <button> (e.g. this library's Button) still
|
|
107
|
+
// take the native path.
|
|
108
|
+
onClick: (event: ReactMouseEvent<HTMLElement>) => {
|
|
109
|
+
((children.props as Record<string, unknown>).onClick as
|
|
110
|
+
| ((e: ReactMouseEvent<HTMLElement>) => void)
|
|
111
|
+
| undefined)?.(event);
|
|
112
|
+
if (!(event.currentTarget instanceof HTMLButtonElement)) {
|
|
113
|
+
panelRef.current?.togglePopover();
|
|
114
|
+
}
|
|
115
|
+
},
|
|
102
116
|
})
|
|
103
117
|
: children;
|
|
104
118
|
|
|
@@ -48,5 +48,5 @@ Read-only star rating display. Use to show an existing score; for user input, co
|
|
|
48
48
|
|
|
49
49
|
## Accessibility
|
|
50
50
|
|
|
51
|
-
- Renders as a `<
|
|
51
|
+
- Renders as a `<div>` with `role="img"` and `aria-label="X out of Y stars"`
|
|
52
52
|
- Stars are `aria-hidden` — the label carries the full meaning
|
|
@@ -3,7 +3,7 @@ import css from './Rating.module.css';
|
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
4
|
import type { WithClassName } from '../../../types';
|
|
5
5
|
|
|
6
|
-
interface RatingProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
export interface RatingProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
|
|
7
7
|
value: number;
|
|
8
8
|
max?: number;
|
|
9
9
|
}
|
|
@@ -43,6 +43,7 @@ const { showToast } = useToast();
|
|
|
43
43
|
showToast('Changes saved', 'success');
|
|
44
44
|
showToast('Something went wrong', 'error');
|
|
45
45
|
showToast('New message received', 'info');
|
|
46
|
+
showToast('Double-check this before continuing', 'warning');
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
> **Next.js RSC note:** The root entry (`@boostdev/design-system-components`) is safe to import in Server Components for server-safe components like `Typography` or `Badge`. Toast itself is client-only and must be used inside a `'use client'` boundary.
|
|
@@ -78,6 +79,8 @@ The override surface is **layered**. Setting a public token on the toast (or any
|
|
|
78
79
|
<tbody>
|
|
79
80
|
<tr><td>`--toast_color`</td><td>per variant</td><td>Toast background. Set to theme every variant at once.</td></tr>
|
|
80
81
|
<tr><td>`--toast_on-color`</td><td>per variant</td><td>Toast text colour.</td></tr>
|
|
82
|
+
<tr><td>`--toast_radius`</td><td>`var(--bds-border_radius--s)`</td><td>Toast corner radius.</td></tr>
|
|
83
|
+
<tr><td>`--toast_shadow`</td><td>`var(--bds-shadow_s)`</td><td>Toast elevation shadow.</td></tr>
|
|
81
84
|
</tbody>
|
|
82
85
|
</table>
|
|
83
86
|
|
|
@@ -21,15 +21,26 @@
|
|
|
21
21
|
over per-variant defaults. Variants write to the inner --toast_*--variant slot. */
|
|
22
22
|
background-color: var(--toast_color, var(--toast_color--variant, var(--bds-color_bg)));
|
|
23
23
|
color: var(--toast_on-color, var(--toast_on-color--variant, var(--bds-color_on-bg)));
|
|
24
|
-
border-radius: var(--bds-border_radius--s);
|
|
25
|
-
box-shadow: var(--bds-shadow_s);
|
|
24
|
+
border-radius: var(--toast_radius, var(--bds-border_radius--s));
|
|
25
|
+
box-shadow: var(--toast_shadow, var(--bds-shadow_s));
|
|
26
26
|
display: flex;
|
|
27
27
|
align-items: center;
|
|
28
28
|
gap: var(--bds-space_m);
|
|
29
|
-
min-inline-size: 300px;
|
|
29
|
+
min-inline-size: min(300px, calc(100svw - 2 * var(--bds-space_xl)));
|
|
30
30
|
animation: slideIn var(--bds-animation_duration--fast) var(--bds-animation_easing);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/*
|
|
34
|
+
* translateX() is a physical transform (documented exception — see AGENTS.md).
|
|
35
|
+
* The container is anchored on the logical inline-end edge (inset-inline-end),
|
|
36
|
+
* which is the physical *left* edge in RTL, so the off-screen slide-in offset
|
|
37
|
+
* must flip too or the toast slides in from the wrong side. --toast_slide-offset
|
|
38
|
+
* is an internal implementation slot, not part of the public token surface.
|
|
39
|
+
*/
|
|
40
|
+
.toast:dir(rtl) {
|
|
41
|
+
--toast_slide-offset: -100%;
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
.toast.--variant_success {
|
|
34
45
|
--toast_color--variant: var(--toast_color--success, var(--bds-color_success));
|
|
35
46
|
--toast_on-color--variant: var(--toast_on-color--success, var(--bds-color_on-success));
|
|
@@ -55,6 +66,7 @@
|
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
.closeButton {
|
|
69
|
+
position: relative;
|
|
58
70
|
display: flex;
|
|
59
71
|
align-items: center;
|
|
60
72
|
justify-content: center;
|
|
@@ -65,7 +77,19 @@
|
|
|
65
77
|
background: none;
|
|
66
78
|
border: none;
|
|
67
79
|
cursor: pointer;
|
|
68
|
-
color:
|
|
80
|
+
color: currentcolor;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Expands the hit area to >=44px (WCAG 2.5.5) without growing the visible icon. */
|
|
84
|
+
.closeButton::after {
|
|
85
|
+
content: '';
|
|
86
|
+
position: absolute;
|
|
87
|
+
inset: -0.75rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.closeButton:focus-visible {
|
|
91
|
+
outline: var(--bds-outline_default);
|
|
92
|
+
outline-offset: var(--bds-outline_offset);
|
|
69
93
|
}
|
|
70
94
|
|
|
71
95
|
.closeButton svg {
|
|
@@ -76,7 +100,7 @@
|
|
|
76
100
|
|
|
77
101
|
@keyframes slideIn {
|
|
78
102
|
from {
|
|
79
|
-
transform: translateX(100%);
|
|
103
|
+
transform: translateX(var(--toast_slide-offset, 100%));
|
|
80
104
|
opacity: 0;
|
|
81
105
|
}
|
|
82
106
|
|
|
@@ -85,3 +109,9 @@
|
|
|
85
109
|
opacity: 1;
|
|
86
110
|
}
|
|
87
111
|
}
|
|
112
|
+
|
|
113
|
+
@media (prefers-reduced-motion: reduce) {
|
|
114
|
+
.toast {
|
|
115
|
+
animation: none;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -2,7 +2,13 @@ import { render, screen, act, fireEvent } from '@testing-library/react';
|
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { ToastProvider, useToast } from './Toast';
|
|
4
4
|
|
|
5
|
-
const ToastTrigger = ({
|
|
5
|
+
const ToastTrigger = ({
|
|
6
|
+
message = 'Test toast',
|
|
7
|
+
variant = 'info' as const,
|
|
8
|
+
}: {
|
|
9
|
+
message?: string;
|
|
10
|
+
variant?: 'success' | 'error' | 'info' | 'warning';
|
|
11
|
+
}) => {
|
|
6
12
|
const { showToast } = useToast();
|
|
7
13
|
return <button onClick={() => showToast(message, variant)}>Show Toast</button>;
|
|
8
14
|
};
|
|
@@ -32,6 +38,68 @@ describe('Toast', () => {
|
|
|
32
38
|
vi.useRealTimers();
|
|
33
39
|
});
|
|
34
40
|
|
|
41
|
+
it('auto-dismisses each toast on its own 5s schedule even when another toast arrives mid-stream', () => {
|
|
42
|
+
vi.useFakeTimers();
|
|
43
|
+
const TwoTriggers = () => {
|
|
44
|
+
const { showToast } = useToast();
|
|
45
|
+
return (
|
|
46
|
+
<>
|
|
47
|
+
<button onClick={() => showToast('First toast', 'info')}>First</button>
|
|
48
|
+
<button onClick={() => showToast('Second toast', 'info')}>Second</button>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
render(
|
|
53
|
+
<ToastProvider>
|
|
54
|
+
<TwoTriggers />
|
|
55
|
+
</ToastProvider>,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
act(() => { fireEvent.click(screen.getByText('First')); });
|
|
59
|
+
act(() => { vi.advanceTimersByTime(3000); });
|
|
60
|
+
// Second toast arrives before the first's timer fires — under the prior bug
|
|
61
|
+
// this reset every in-flight toast's timer instead of just starting its own.
|
|
62
|
+
act(() => { fireEvent.click(screen.getByText('Second')); });
|
|
63
|
+
act(() => { vi.advanceTimersByTime(2100); }); // t=5100 since "First" mounted
|
|
64
|
+
expect(screen.queryByText('First toast')).not.toBeInTheDocument();
|
|
65
|
+
expect(screen.getByText('Second toast')).toBeInTheDocument();
|
|
66
|
+
vi.useRealTimers();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('does not crash when another toast is queued while the popover is already open', async () => {
|
|
70
|
+
// jsdom has no Popover API — mock a spec-accurate showPopover/hidePopover that
|
|
71
|
+
// throws InvalidStateError when called in a redundant state, matching real browsers.
|
|
72
|
+
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
73
|
+
if (this.hasAttribute('data-popover-open')) {
|
|
74
|
+
throw new DOMException('Invalid state', 'InvalidStateError');
|
|
75
|
+
}
|
|
76
|
+
this.setAttribute('data-popover-open', 'true');
|
|
77
|
+
});
|
|
78
|
+
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
|
|
79
|
+
if (!this.hasAttribute('data-popover-open')) {
|
|
80
|
+
throw new DOMException('Invalid state', 'InvalidStateError');
|
|
81
|
+
}
|
|
82
|
+
this.removeAttribute('data-popover-open');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const user = userEvent.setup();
|
|
86
|
+
setup({ message: 'Queued toast' });
|
|
87
|
+
const button = screen.getByRole('button');
|
|
88
|
+
await user.click(button);
|
|
89
|
+
await user.click(button);
|
|
90
|
+
await user.click(button);
|
|
91
|
+
|
|
92
|
+
expect(screen.getAllByText('Queued toast')).toHaveLength(3);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('accepts the warning variant', async () => {
|
|
96
|
+
const user = userEvent.setup();
|
|
97
|
+
setup({ message: 'Careful now', variant: 'warning' });
|
|
98
|
+
await user.click(screen.getByRole('button'));
|
|
99
|
+
const toastEl = screen.getByText('Careful now').closest('[role="status"]');
|
|
100
|
+
expect(toastEl?.className).toMatch(/--variant_warning/);
|
|
101
|
+
});
|
|
102
|
+
|
|
35
103
|
it('renders the toast container as a manual popover', () => {
|
|
36
104
|
render(
|
|
37
105
|
<ToastProvider>
|