@boostdev/design-system-components 1.1.0 → 1.1.2
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 +46 -5
- package/dist/client.cjs +206 -113
- package/dist/client.css +664 -624
- package/dist/client.d.cts +9 -3
- package/dist/client.d.ts +9 -3
- package/dist/client.js +241 -149
- package/dist/index.cjs +206 -113
- package/dist/index.css +664 -624
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +241 -149
- package/dist/web-components/chunk-2QA62WCQ.js +48 -0
- package/dist/web-components/chunk-4BX6AWXX.js +146 -0
- package/dist/web-components/chunk-57TRNCWQ.js +93 -0
- package/dist/web-components/chunk-5IPHEONG.js +139 -0
- package/dist/web-components/chunk-5R6IFELV.js +63 -0
- package/dist/web-components/chunk-5TFWOPLH.js +49 -0
- package/dist/web-components/chunk-6I3CFC26.js +89 -0
- package/dist/web-components/chunk-6T3RKUKG.js +65 -0
- package/dist/web-components/chunk-7FMR53BW.js +127 -0
- package/dist/web-components/chunk-A67LCT7C.js +134 -0
- package/dist/web-components/chunk-AGKJTMJI.js +56 -0
- package/dist/web-components/chunk-BQLZ7I4G.js +56 -0
- package/dist/web-components/chunk-CAPI25CU.js +53 -0
- package/dist/web-components/chunk-CJ5ZOO42.js +229 -0
- package/dist/web-components/chunk-CR5PGUHV.js +109 -0
- package/dist/web-components/chunk-ECW3HHWA.js +185 -0
- package/dist/web-components/chunk-HGBNDR22.js +98 -0
- package/dist/web-components/chunk-IK3PTFZ4.js +133 -0
- package/dist/web-components/chunk-IQGLIQSO.js +188 -0
- package/dist/web-components/chunk-ITHLKR7Q.js +105 -0
- package/dist/web-components/chunk-K5CE6HSB.js +169 -0
- package/dist/web-components/chunk-L3XHOMN6.js +111 -0
- package/dist/{web-components.js → web-components/chunk-LGEZYFUU.js} +62 -834
- package/dist/web-components/chunk-LSC4N56Q.js +168 -0
- package/dist/web-components/chunk-LY62SAVN.js +64 -0
- package/dist/web-components/chunk-O4UFI2RX.js +244 -0
- package/dist/web-components/chunk-O7I63SQX.js +124 -0
- package/dist/web-components/chunk-OHUTLSIQ.js +69 -0
- package/dist/web-components/chunk-OQTRTMAB.js +27 -0
- package/dist/web-components/chunk-OTX4COSV.js +187 -0
- package/dist/web-components/chunk-PY62FTRW.js +179 -0
- package/dist/web-components/chunk-Q4T5BJT6.js +94 -0
- package/dist/web-components/chunk-QDRH46TF.js +281 -0
- package/dist/web-components/chunk-QDST57JC.js +119 -0
- package/dist/web-components/chunk-QPOKHLMQ.js +45 -0
- package/dist/web-components/chunk-SOQQ7RVJ.js +88 -0
- package/dist/web-components/chunk-UFFCKVZE.js +149 -0
- package/dist/web-components/chunk-VM3YHGQ2.js +152 -0
- package/dist/web-components/chunk-VMMVWLXL.js +53 -0
- package/dist/web-components/chunk-VSS5ITNE.js +109 -0
- package/dist/web-components/chunk-XIENXR7H.js +118 -0
- package/dist/web-components/chunk-Y4MAXTMA.js +80 -0
- package/dist/web-components/chunk-YLVTEY23.js +165 -0
- package/dist/web-components/globals.d.ts +2 -0
- package/dist/web-components/globals.js +1 -0
- package/dist/web-components/index.d.ts +41 -0
- package/dist/web-components/index.js +165 -0
- package/dist/web-components/interaction/bds-accordion-item.d.ts +54 -0
- package/dist/web-components/interaction/bds-accordion-item.js +7 -0
- package/dist/web-components/interaction/bds-accordion.d.ts +50 -0
- package/dist/web-components/interaction/bds-accordion.js +7 -0
- package/dist/web-components/interaction/bds-button.d.ts +100 -0
- package/dist/web-components/interaction/bds-button.js +7 -0
- package/dist/web-components/interaction/bds-collapsible.d.ts +52 -0
- package/dist/web-components/interaction/bds-collapsible.js +7 -0
- package/dist/web-components/interaction/bds-dialog.d.ts +56 -0
- package/dist/web-components/interaction/bds-dialog.js +7 -0
- package/dist/web-components/interaction/bds-drawer.d.ts +64 -0
- package/dist/web-components/interaction/bds-drawer.js +7 -0
- package/dist/web-components/interaction/bds-popover.d.ts +62 -0
- package/dist/web-components/interaction/bds-popover.js +7 -0
- package/dist/web-components/interaction/bds-skip-link.d.ts +36 -0
- package/dist/web-components/interaction/bds-skip-link.js +7 -0
- package/dist/web-components/interaction/bds-tab-panel.d.ts +26 -0
- package/dist/web-components/interaction/bds-tab-panel.js +7 -0
- package/dist/web-components/interaction/bds-tab.d.ts +45 -0
- package/dist/web-components/interaction/bds-tab.js +7 -0
- package/dist/web-components/interaction/bds-tabs.d.ts +46 -0
- package/dist/web-components/interaction/bds-tabs.js +7 -0
- package/dist/web-components/interaction/bds-toast-provider.d.ts +57 -0
- package/dist/web-components/interaction/bds-toast-provider.js +7 -0
- package/dist/web-components/interaction/bds-tooltip.d.ts +50 -0
- package/dist/web-components/interaction/bds-tooltip.js +7 -0
- package/dist/web-components/interaction/form/bds-checkbox.d.ts +70 -0
- package/dist/web-components/interaction/form/bds-checkbox.js +7 -0
- package/dist/web-components/interaction/form/bds-combobox.d.ts +84 -0
- package/dist/web-components/interaction/form/bds-combobox.js +7 -0
- package/dist/web-components/interaction/form/bds-file-input.d.ts +62 -0
- package/dist/web-components/interaction/form/bds-file-input.js +7 -0
- package/dist/web-components/interaction/form/bds-number-input.d.ts +68 -0
- package/dist/web-components/interaction/form/bds-number-input.js +7 -0
- package/dist/web-components/interaction/form/bds-radio.d.ts +68 -0
- package/dist/web-components/interaction/form/bds-radio.js +7 -0
- package/dist/web-components/interaction/form/bds-segmented-control.d.ts +71 -0
- package/dist/web-components/interaction/form/bds-segmented-control.js +7 -0
- package/dist/web-components/interaction/form/bds-select.d.ts +79 -0
- package/dist/web-components/interaction/form/bds-select.js +7 -0
- package/dist/web-components/interaction/form/bds-slider.d.ts +70 -0
- package/dist/web-components/interaction/form/bds-slider.js +7 -0
- package/dist/web-components/interaction/form/bds-switch.d.ts +67 -0
- package/dist/web-components/interaction/form/bds-switch.js +7 -0
- package/dist/web-components/interaction/form/bds-textarea.d.ts +70 -0
- package/dist/web-components/interaction/form/bds-textarea.js +7 -0
- package/dist/web-components/ui/bds-alert.d.ts +66 -0
- package/dist/web-components/ui/bds-alert.js +7 -0
- package/dist/web-components/ui/bds-avatar.d.ts +50 -0
- package/dist/web-components/ui/bds-avatar.js +7 -0
- package/dist/web-components/ui/bds-badge.d.ts +35 -0
- package/dist/web-components/ui/bds-badge.js +7 -0
- package/dist/web-components/ui/bds-breadcrumb.d.ts +35 -0
- package/dist/web-components/ui/bds-breadcrumb.js +7 -0
- package/dist/web-components/ui/bds-card.d.ts +72 -0
- package/dist/web-components/ui/bds-card.js +7 -0
- package/dist/web-components/ui/bds-description-list.d.ts +42 -0
- package/dist/web-components/ui/bds-description-list.js +7 -0
- package/dist/web-components/ui/bds-icon-wrapper.d.ts +39 -0
- package/dist/web-components/ui/bds-icon-wrapper.js +7 -0
- package/dist/web-components/ui/bds-link.d.ts +59 -0
- package/dist/web-components/ui/bds-link.js +7 -0
- package/dist/web-components/ui/bds-loading.d.ts +32 -0
- package/dist/web-components/ui/bds-loading.js +7 -0
- package/dist/web-components/ui/bds-notification-banner.d.ts +61 -0
- package/dist/web-components/ui/bds-notification-banner.js +7 -0
- package/dist/web-components/ui/bds-progress-circle.d.ts +53 -0
- package/dist/web-components/ui/bds-progress-circle.js +7 -0
- package/dist/web-components/ui/bds-progress.d.ts +54 -0
- package/dist/web-components/ui/bds-progress.js +7 -0
- package/dist/web-components/ui/bds-rating.d.ts +36 -0
- package/dist/web-components/ui/bds-rating.js +7 -0
- package/dist/web-components/ui/bds-section-header.d.ts +53 -0
- package/dist/web-components/ui/bds-section-header.js +8 -0
- package/dist/web-components/ui/bds-separator.d.ts +32 -0
- package/dist/web-components/ui/bds-separator.js +7 -0
- package/dist/web-components/ui/bds-skeleton.d.ts +22 -0
- package/dist/web-components/ui/bds-skeleton.js +7 -0
- package/dist/web-components/ui/bds-typography.d.ts +44 -0
- package/dist/web-components/ui/bds-typography.js +8 -0
- package/package.json +169 -4
- package/src/components/interaction/Button/Button.module.css +1 -1
- package/src/components/interaction/Button/Button.native.mdx +72 -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.tsx +37 -2
- 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 +17 -0
- package/src/components/interaction/Dialog/Dialog.tsx +50 -16
- 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.tsx +38 -4
- package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +6 -6
- package/src/components/interaction/Popover/Popover.module.css +74 -15
- package/src/components/interaction/Popover/Popover.spec.tsx +94 -59
- package/src/components/interaction/Popover/Popover.tsx +44 -60
- package/src/components/interaction/Toast/Toast.module.css +7 -7
- package/src/components/interaction/form/Checkbox/Checkbox.module.css +6 -6
- package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +74 -0
- package/src/components/interaction/form/Combobox/Combobox.module.css +7 -8
- package/src/components/interaction/form/FileInput/FileInput.module.css +4 -4
- package/src/components/interaction/form/NumberInput/NumberInput.module.css +1 -1
- package/src/components/interaction/form/Radio/Radio.module.css +6 -6
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +5 -6
- package/src/components/interaction/form/Select/Select.module.css +3 -3
- package/src/components/interaction/form/Slider/Slider.module.css +6 -6
- package/src/components/interaction/form/Switch/Switch.module.css +8 -8
- package/src/components/interaction/form/Textarea/Textarea.module.css +1 -1
- package/src/components/layout/IconWrapper/IconWrapper.module.css +2 -2
- package/src/components/ui/Accordion/Accordion.module.css +5 -5
- package/src/components/ui/Alert/Alert.native.mdx +62 -0
- package/src/components/ui/Avatar/Avatar.native.mdx +50 -0
- package/src/components/ui/Badge/Badge.native.mdx +54 -0
- package/src/components/ui/Calendar/Calendar.module.css +7 -7
- package/src/components/ui/Carousel/Carousel.module.css +5 -5
- package/src/components/ui/Collapsible/Collapsible.module.css +4 -4
- package/src/components/ui/Link/Link.module.css +2 -2
- package/src/components/ui/Loading/Loading.module.css +7 -7
- package/src/components/ui/NotificationBanner/NotificationBanner.module.css +3 -3
- package/src/components/ui/Pagination/Pagination.module.css +6 -6
- package/src/components/ui/Progress/Progress.module.css +5 -5
- package/src/components/ui/Separator/Separator.module.css +3 -3
- package/src/components/ui/Skeleton/Skeleton.module.css +1 -1
- package/src/components/ui/Table/Table.module.css +9 -9
- package/src/components/ui/Tabs/Tabs.module.css +3 -3
- package/src/components/ui/Tooltip/Tooltip.module.css +8 -8
- package/src/components/ui/Typography/Typography.native.mdx +56 -0
- package/src/polyfill-invoker-commands.ts +68 -0
- package/src/react-augment.d.ts +19 -0
- package/src/stories/ReactNative.mdx +121 -0
- package/src/web-components/index.ts +47 -0
- package/src/web-components/interaction/BdsButton.mdx +106 -0
- package/src/web-components/interaction/BdsPopover.mdx +120 -0
- package/src/web-components/interaction/BdsToastProvider.mdx +94 -0
- package/src/web-components/interaction/bds-accordion-item.spec.ts +75 -0
- package/src/web-components/interaction/bds-accordion-item.ts +181 -0
- package/src/web-components/interaction/bds-accordion.spec.ts +73 -0
- package/src/web-components/interaction/bds-accordion.ts +99 -0
- package/src/web-components/interaction/bds-collapsible.spec.ts +56 -0
- package/src/web-components/interaction/bds-collapsible.ts +170 -0
- package/src/web-components/interaction/bds-dialog.spec.ts +119 -0
- package/src/web-components/interaction/bds-dialog.ts +224 -0
- package/src/web-components/interaction/bds-drawer.spec.ts +113 -0
- package/src/web-components/interaction/bds-drawer.ts +266 -0
- package/src/web-components/interaction/bds-popover.spec.ts +49 -1
- package/src/web-components/interaction/bds-popover.ts +12 -0
- package/src/web-components/interaction/bds-skip-link.spec.ts +42 -0
- package/src/web-components/interaction/bds-skip-link.ts +74 -0
- package/src/web-components/interaction/bds-tab-panel.spec.ts +32 -0
- package/src/web-components/interaction/bds-tab-panel.ts +38 -0
- package/src/web-components/interaction/bds-tab.spec.ts +71 -0
- package/src/web-components/interaction/bds-tab.ts +110 -0
- package/src/web-components/interaction/bds-tabs.spec.ts +120 -0
- package/src/web-components/interaction/bds-tabs.ts +152 -0
- package/src/web-components/interaction/bds-tooltip.spec.ts +49 -0
- package/src/web-components/interaction/bds-tooltip.ts +127 -0
- package/src/web-components/interaction/form/bds-checkbox.spec.ts +63 -0
- package/src/web-components/interaction/form/bds-checkbox.ts +168 -0
- package/src/web-components/interaction/form/bds-combobox.spec.ts +117 -0
- package/src/web-components/interaction/form/bds-combobox.ts +336 -0
- package/src/web-components/interaction/form/bds-file-input.spec.ts +58 -0
- package/src/web-components/interaction/form/bds-file-input.ts +209 -0
- package/src/web-components/interaction/form/bds-number-input.spec.ts +87 -0
- package/src/web-components/interaction/form/bds-number-input.ts +221 -0
- package/src/web-components/interaction/form/bds-radio.spec.ts +70 -0
- package/src/web-components/interaction/form/bds-radio.ts +157 -0
- package/src/web-components/interaction/form/bds-segmented-control.spec.ts +74 -0
- package/src/web-components/interaction/form/bds-segmented-control.ts +211 -0
- package/src/web-components/interaction/form/bds-select.spec.ts +74 -0
- package/src/web-components/interaction/form/bds-select.ts +190 -0
- package/src/web-components/interaction/form/bds-slider.spec.ts +41 -0
- package/src/web-components/interaction/form/bds-slider.ts +162 -0
- package/src/web-components/interaction/form/bds-switch.spec.ts +62 -0
- package/src/web-components/interaction/form/bds-switch.ts +166 -0
- package/src/web-components/interaction/form/bds-textarea.spec.ts +55 -0
- package/src/web-components/interaction/form/bds-textarea.ts +143 -0
- package/src/web-components/test/helpers.ts +8 -0
- package/src/web-components/ui/BdsAlert.mdx +90 -0
- package/src/web-components/ui/BdsBadge.mdx +74 -0
- package/src/web-components/ui/bds-avatar.spec.ts +75 -0
- package/src/web-components/ui/bds-avatar.ts +122 -0
- package/src/web-components/ui/bds-breadcrumb.spec.ts +55 -0
- package/src/web-components/ui/bds-breadcrumb.ts +114 -0
- package/src/web-components/ui/bds-card.spec.ts +70 -0
- package/src/web-components/ui/bds-card.ts +156 -0
- package/src/web-components/ui/bds-description-list.spec.ts +36 -0
- package/src/web-components/ui/bds-description-list.ts +79 -0
- package/src/web-components/ui/bds-icon-wrapper.spec.ts +21 -0
- package/src/web-components/ui/bds-icon-wrapper.ts +68 -0
- package/src/web-components/ui/bds-link.spec.ts +72 -0
- package/src/web-components/ui/bds-link.ts +138 -0
- package/src/web-components/ui/bds-loading.spec.ts +38 -0
- package/src/web-components/ui/bds-loading.ts +76 -0
- package/src/web-components/ui/bds-notification-banner.spec.ts +82 -0
- package/src/web-components/ui/bds-notification-banner.ts +185 -0
- package/src/web-components/ui/bds-progress-circle.spec.ts +58 -0
- package/src/web-components/ui/bds-progress-circle.ts +142 -0
- package/src/web-components/ui/bds-progress.spec.ts +66 -0
- package/src/web-components/ui/bds-progress.ts +127 -0
- package/src/web-components/ui/bds-rating.spec.ts +70 -0
- package/src/web-components/ui/bds-rating.ts +80 -0
- package/src/web-components/ui/bds-section-header.spec.ts +57 -0
- package/src/web-components/ui/bds-section-header.ts +96 -0
- package/src/web-components/ui/bds-separator.spec.ts +50 -0
- package/src/web-components/ui/bds-separator.ts +66 -0
- package/src/web-components/ui/bds-skeleton.spec.ts +22 -0
- package/src/web-components/ui/bds-skeleton.ts +55 -0
- package/src/web-components/ui/bds-typography.spec.ts +76 -0
- package/src/web-components/ui/bds-typography.ts +112 -0
- package/dist/web-components.d.ts +0 -303
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsRating } from './bds-rating';
|
|
3
|
+
|
|
4
|
+
describe('bds-rating', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-rating')).toBe(BdsRating);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('defaults to max=5', async () => {
|
|
10
|
+
const el = await fixture('<bds-rating value="3"></bds-rating>') as BdsRating;
|
|
11
|
+
expect(el.max).toBe(5);
|
|
12
|
+
cleanup(el);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders a container with role="img"', async () => {
|
|
16
|
+
const el = await fixture('<bds-rating value="3"></bds-rating>');
|
|
17
|
+
const container = el.shadowRoot!.querySelector('.rating');
|
|
18
|
+
expect(container!.getAttribute('role')).toBe('img');
|
|
19
|
+
cleanup(el);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('sets aria-label with value and max', async () => {
|
|
23
|
+
const el = await fixture('<bds-rating value="3"></bds-rating>');
|
|
24
|
+
const container = el.shadowRoot!.querySelector('.rating');
|
|
25
|
+
expect(container!.getAttribute('aria-label')).toBe('3 out of 5 stars');
|
|
26
|
+
cleanup(el);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders max number of star SVGs', async () => {
|
|
30
|
+
const el = await fixture('<bds-rating value="3"></bds-rating>');
|
|
31
|
+
const stars = el.shadowRoot!.querySelectorAll('.star');
|
|
32
|
+
expect(stars.length).toBe(5);
|
|
33
|
+
cleanup(el);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('marks the correct stars as filled', async () => {
|
|
37
|
+
const el = await fixture('<bds-rating value="3"></bds-rating>');
|
|
38
|
+
const stars = el.shadowRoot!.querySelectorAll('.star');
|
|
39
|
+
const filledStars = [...stars].filter(s => s.classList.contains('--filled'));
|
|
40
|
+
expect(filledStars.length).toBe(3);
|
|
41
|
+
cleanup(el);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders correct count with custom max', async () => {
|
|
45
|
+
const el = await fixture('<bds-rating value="2" max="10"></bds-rating>');
|
|
46
|
+
const stars = el.shadowRoot!.querySelectorAll('.star');
|
|
47
|
+
expect(stars.length).toBe(10);
|
|
48
|
+
const filledStars = [...stars].filter(s => s.classList.contains('--filled'));
|
|
49
|
+
expect(filledStars.length).toBe(2);
|
|
50
|
+
cleanup(el);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('stars have aria-hidden="true"', async () => {
|
|
54
|
+
const el = await fixture('<bds-rating value="3"></bds-rating>');
|
|
55
|
+
const stars = el.shadowRoot!.querySelectorAll('.star');
|
|
56
|
+
[...stars].forEach(star => {
|
|
57
|
+
expect(star.getAttribute('aria-hidden')).toBe('true');
|
|
58
|
+
});
|
|
59
|
+
cleanup(el);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('updates when value property changes', async () => {
|
|
63
|
+
const el = await fixture('<bds-rating value="1"></bds-rating>') as BdsRating;
|
|
64
|
+
el.value = 4;
|
|
65
|
+
await el.updateComplete;
|
|
66
|
+
const filledStars = [...el.shadowRoot!.querySelectorAll('.star')].filter(s => s.classList.contains('--filled'));
|
|
67
|
+
expect(filledStars.length).toBe(4);
|
|
68
|
+
cleanup(el);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
const STAR_PATH = 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `<bds-rating>` — framework-agnostic read-only star Rating custom element.
|
|
7
|
+
*
|
|
8
|
+
* Attributes:
|
|
9
|
+
* value — number of filled stars
|
|
10
|
+
* max — total number of stars (default: 5)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <bds-rating value="3"></bds-rating>
|
|
14
|
+
* <bds-rating value="4" max="10"></bds-rating>
|
|
15
|
+
*/
|
|
16
|
+
export class BdsRating extends LitElement {
|
|
17
|
+
static styles = css`
|
|
18
|
+
:host {
|
|
19
|
+
display: inline-flex;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.rating {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
gap: var(--bds-space_xxxs);
|
|
25
|
+
color: var(--bds-color_bg);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.star {
|
|
29
|
+
inline-size: 1.5em;
|
|
30
|
+
block-size: 1.5em;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.star.--filled {
|
|
34
|
+
color: var(--bds-color_warning);
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
static properties = {
|
|
39
|
+
value: { type: Number },
|
|
40
|
+
max: { type: Number },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
declare value: number;
|
|
44
|
+
declare max: number;
|
|
45
|
+
|
|
46
|
+
constructor() {
|
|
47
|
+
super();
|
|
48
|
+
this.value = 0;
|
|
49
|
+
this.max = 5;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
render() {
|
|
53
|
+
return html`
|
|
54
|
+
<div
|
|
55
|
+
class="rating"
|
|
56
|
+
role="img"
|
|
57
|
+
aria-label="${this.value} out of ${this.max} stars"
|
|
58
|
+
>
|
|
59
|
+
${Array.from({ length: this.max }, (_, i) => html`
|
|
60
|
+
<svg
|
|
61
|
+
class="star ${i < this.value ? '--filled' : ''}"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
fill="currentColor"
|
|
64
|
+
viewBox="0 0 24 24"
|
|
65
|
+
>
|
|
66
|
+
<path d=${STAR_PATH} />
|
|
67
|
+
</svg>
|
|
68
|
+
`)}
|
|
69
|
+
</div>
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
customElements.define('bds-rating', BdsRating);
|
|
75
|
+
|
|
76
|
+
declare global {
|
|
77
|
+
interface HTMLElementTagNameMap {
|
|
78
|
+
'bds-rating': BdsRating;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsSectionHeader } from './bds-section-header';
|
|
3
|
+
|
|
4
|
+
describe('bds-section-header', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-section-header')).toBe(BdsSectionHeader);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('renders an <h2> title by default', async () => {
|
|
10
|
+
const el = await fixture('<bds-section-header title="Hello"></bds-section-header>');
|
|
11
|
+
const h2 = el.shadowRoot!.querySelector('h2');
|
|
12
|
+
expect(h2).not.toBeNull();
|
|
13
|
+
expect(h2!.textContent).toBe('Hello');
|
|
14
|
+
cleanup(el);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders the title text', async () => {
|
|
18
|
+
const el = await fixture('<bds-section-header title="Welcome"></bds-section-header>');
|
|
19
|
+
expect(el.shadowRoot!.querySelector('.title')!.textContent).toBe('Welcome');
|
|
20
|
+
cleanup(el);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('does not render subtitle when not set', async () => {
|
|
24
|
+
const el = await fixture('<bds-section-header title="Hello"></bds-section-header>');
|
|
25
|
+
expect(el.shadowRoot!.querySelector('.subtitle')).toBeNull();
|
|
26
|
+
cleanup(el);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders subtitle when set', async () => {
|
|
30
|
+
const el = await fixture('<bds-section-header title="Hello" subtitle="Subtitle text"></bds-section-header>');
|
|
31
|
+
const subtitle = el.shadowRoot!.querySelector('.subtitle');
|
|
32
|
+
expect(subtitle).not.toBeNull();
|
|
33
|
+
expect(subtitle!.textContent).toBe('Subtitle text');
|
|
34
|
+
cleanup(el);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('uses title-as to override the title element', async () => {
|
|
38
|
+
const el = await fixture('<bds-section-header title="Hello" title-as="h1"></bds-section-header>');
|
|
39
|
+
expect(el.shadowRoot!.querySelector('h1')).not.toBeNull();
|
|
40
|
+
expect(el.shadowRoot!.querySelector('h2')).toBeNull();
|
|
41
|
+
cleanup(el);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('applies alignment class', async () => {
|
|
45
|
+
const el = await fixture('<bds-section-header title="Hello" alignment="center"></bds-section-header>');
|
|
46
|
+
const header = el.shadowRoot!.querySelector('.header');
|
|
47
|
+
expect(header!.classList.contains('--center')).toBe(true);
|
|
48
|
+
cleanup(el);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('applies size class', async () => {
|
|
52
|
+
const el = await fixture('<bds-section-header title="Hello" size="large"></bds-section-header>');
|
|
53
|
+
const header = el.shadowRoot!.querySelector('.header');
|
|
54
|
+
expect(header!.classList.contains('--large')).toBe(true);
|
|
55
|
+
cleanup(el);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
2
|
+
import { html as staticHtml, unsafeStatic } from 'lit/static-html.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `<bds-section-header>` — framework-agnostic Section Header custom element.
|
|
6
|
+
*
|
|
7
|
+
* Attributes:
|
|
8
|
+
* title — heading text (required)
|
|
9
|
+
* subtitle — optional subheading text
|
|
10
|
+
* alignment — "start" (default) | "center" | "end"
|
|
11
|
+
* size — "small" | "medium" (default) | "large"
|
|
12
|
+
* title-as — tag name for the title element (default: "h2")
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <bds-section-header title="Our Products" subtitle="Browse the full range" size="large"></bds-section-header>
|
|
16
|
+
*/
|
|
17
|
+
export class BdsSectionHeader extends LitElement {
|
|
18
|
+
static styles = css`
|
|
19
|
+
:host {
|
|
20
|
+
display: block;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.header {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: var(--bds-space_m);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.title {
|
|
30
|
+
color: var(--sectionHeader-title-color, var(--bds-color_on-bg));
|
|
31
|
+
margin: 0;
|
|
32
|
+
font-family: var(--bds-font_family--body);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.subtitle {
|
|
36
|
+
color: var(--sectionHeader_subtitle-color, var(--bds-color_grey--strong));
|
|
37
|
+
margin: 0;
|
|
38
|
+
font-family: var(--bds-font_family--body);
|
|
39
|
+
font-weight: var(--bds-font_weight--medium);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.header.--start { align-items: flex-start; text-align: start; }
|
|
43
|
+
.header.--center { align-items: center; text-align: center; }
|
|
44
|
+
.header.--end { align-items: flex-end; text-align: end; }
|
|
45
|
+
|
|
46
|
+
.header.--small .title { font-size: var(--bds-font_size--heading-1); line-height: var(--bds-font_line-height--heading); font-weight: var(--bds-font_weight--bold); }
|
|
47
|
+
.header.--small .subtitle { font-size: var(--bds-font_size--body); line-height: 1.4; }
|
|
48
|
+
|
|
49
|
+
.header.--medium .title { font-size: var(--bds-font_size--display); line-height: var(--bds-font_line-height--display); font-weight: var(--bds-font_weight--bold); }
|
|
50
|
+
.header.--medium .subtitle { font-size: var(--bds-font_size--heading-3); line-height: 1.3; }
|
|
51
|
+
|
|
52
|
+
.header.--large .title { font-size: var(--bds-font_size--display); line-height: var(--bds-font_line-height--display); font-weight: var(--bds-font_weight--bold); letter-spacing: var(--bds-font_tracking--tight); }
|
|
53
|
+
.header.--large .subtitle { font-size: var(--bds-font_size--heading-2); line-height: 1.3; font-weight: var(--bds-font_weight--medium); }
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
static properties = {
|
|
57
|
+
title: { type: String },
|
|
58
|
+
subtitle: { type: String },
|
|
59
|
+
alignment: { type: String, reflect: true },
|
|
60
|
+
size: { type: String, reflect: true },
|
|
61
|
+
titleAs: { type: String, attribute: 'title-as' },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
declare title: string;
|
|
65
|
+
declare subtitle: string;
|
|
66
|
+
declare alignment: 'start' | 'center' | 'end';
|
|
67
|
+
declare size: 'small' | 'medium' | 'large';
|
|
68
|
+
declare titleAs: string;
|
|
69
|
+
|
|
70
|
+
constructor() {
|
|
71
|
+
super();
|
|
72
|
+
this.title = '';
|
|
73
|
+
this.subtitle = '';
|
|
74
|
+
this.alignment = 'start';
|
|
75
|
+
this.size = 'medium';
|
|
76
|
+
this.titleAs = 'h2';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
render() {
|
|
80
|
+
const tag = unsafeStatic(this.titleAs || 'h2');
|
|
81
|
+
return html`
|
|
82
|
+
<div class="header --${this.alignment} --${this.size}">
|
|
83
|
+
${staticHtml`<${tag} class="title">${this.title}</${tag}>`}
|
|
84
|
+
${this.subtitle ? html`<p class="subtitle">${this.subtitle}</p>` : nothing}
|
|
85
|
+
</div>
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
customElements.define('bds-section-header', BdsSectionHeader);
|
|
91
|
+
|
|
92
|
+
declare global {
|
|
93
|
+
interface HTMLElementTagNameMap {
|
|
94
|
+
'bds-section-header': BdsSectionHeader;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsSeparator } from './bds-separator';
|
|
3
|
+
|
|
4
|
+
describe('bds-separator', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-separator')).toBe(BdsSeparator);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('defaults to orientation="horizontal"', async () => {
|
|
10
|
+
const el = await fixture('<bds-separator></bds-separator>');
|
|
11
|
+
expect(el.getAttribute('orientation')).toBe('horizontal');
|
|
12
|
+
cleanup(el);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders a separator div in shadow DOM', async () => {
|
|
16
|
+
const el = await fixture('<bds-separator></bds-separator>');
|
|
17
|
+
const sep = el.shadowRoot!.querySelector('.separator');
|
|
18
|
+
expect(sep).not.toBeNull();
|
|
19
|
+
cleanup(el);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('has role="separator"', async () => {
|
|
23
|
+
const el = await fixture('<bds-separator></bds-separator>');
|
|
24
|
+
const sep = el.shadowRoot!.querySelector('.separator');
|
|
25
|
+
expect(sep!.getAttribute('role')).toBe('separator');
|
|
26
|
+
cleanup(el);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('sets aria-orientation="horizontal" by default', async () => {
|
|
30
|
+
const el = await fixture('<bds-separator></bds-separator>');
|
|
31
|
+
const sep = el.shadowRoot!.querySelector('.separator');
|
|
32
|
+
expect(sep!.getAttribute('aria-orientation')).toBe('horizontal');
|
|
33
|
+
cleanup(el);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('sets aria-orientation="vertical" when orientation="vertical"', async () => {
|
|
37
|
+
const el = await fixture('<bds-separator orientation="vertical"></bds-separator>');
|
|
38
|
+
const sep = el.shadowRoot!.querySelector('.separator');
|
|
39
|
+
expect(sep!.getAttribute('aria-orientation')).toBe('vertical');
|
|
40
|
+
cleanup(el);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('reflects orientation property to attribute', async () => {
|
|
44
|
+
const el = await fixture('<bds-separator></bds-separator>') as BdsSeparator;
|
|
45
|
+
el.orientation = 'vertical';
|
|
46
|
+
await el.updateComplete;
|
|
47
|
+
expect(el.getAttribute('orientation')).toBe('vertical');
|
|
48
|
+
cleanup(el);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<bds-separator>` — framework-agnostic Separator custom element.
|
|
5
|
+
*
|
|
6
|
+
* Attributes:
|
|
7
|
+
* orientation — "horizontal" (default) | "vertical"
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <bds-separator></bds-separator>
|
|
11
|
+
* <bds-separator orientation="vertical"></bds-separator>
|
|
12
|
+
*/
|
|
13
|
+
export class BdsSeparator extends LitElement {
|
|
14
|
+
static styles = css`
|
|
15
|
+
:host {
|
|
16
|
+
display: block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:host([orientation='vertical']) {
|
|
20
|
+
display: inline-block;
|
|
21
|
+
align-self: stretch;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.separator {
|
|
25
|
+
border: none;
|
|
26
|
+
background-color: var(--separator_color, var(--bds-color_on-bg));
|
|
27
|
+
display: block;
|
|
28
|
+
inline-size: 100%;
|
|
29
|
+
block-size: var(--separator_thickness, 1px);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
:host([orientation='vertical']) .separator {
|
|
33
|
+
inline-size: var(--separator_thickness, 1px);
|
|
34
|
+
block-size: 100%;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
static properties = {
|
|
39
|
+
orientation: { type: String, reflect: true },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
declare orientation: 'horizontal' | 'vertical';
|
|
43
|
+
|
|
44
|
+
constructor() {
|
|
45
|
+
super();
|
|
46
|
+
this.orientation = 'horizontal';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
render() {
|
|
50
|
+
return html`
|
|
51
|
+
<div
|
|
52
|
+
class="separator"
|
|
53
|
+
role="separator"
|
|
54
|
+
aria-orientation=${this.orientation}
|
|
55
|
+
></div>
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
customElements.define('bds-separator', BdsSeparator);
|
|
61
|
+
|
|
62
|
+
declare global {
|
|
63
|
+
interface HTMLElementTagNameMap {
|
|
64
|
+
'bds-separator': BdsSeparator;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsSkeleton } from './bds-skeleton';
|
|
3
|
+
|
|
4
|
+
describe('bds-skeleton', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-skeleton')).toBe(BdsSkeleton);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('renders a skeleton element in shadow DOM', async () => {
|
|
10
|
+
const el = await fixture('<bds-skeleton></bds-skeleton>');
|
|
11
|
+
const skeleton = el.shadowRoot!.querySelector('.skeleton');
|
|
12
|
+
expect(skeleton).not.toBeNull();
|
|
13
|
+
cleanup(el);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('has aria-hidden="true" on the skeleton element', async () => {
|
|
17
|
+
const el = await fixture('<bds-skeleton></bds-skeleton>');
|
|
18
|
+
const skeleton = el.shadowRoot!.querySelector('.skeleton');
|
|
19
|
+
expect(skeleton!.getAttribute('aria-hidden')).toBe('true');
|
|
20
|
+
cleanup(el);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<bds-skeleton>` — framework-agnostic Skeleton loading placeholder custom element.
|
|
5
|
+
*
|
|
6
|
+
* No attributes. The host inherits width/height from its container or inline styles.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <bds-skeleton style="width: 200px; height: 1rem;"></bds-skeleton>
|
|
10
|
+
*/
|
|
11
|
+
export class BdsSkeleton extends LitElement {
|
|
12
|
+
static styles = css`
|
|
13
|
+
:host {
|
|
14
|
+
display: block;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.skeleton {
|
|
18
|
+
display: block;
|
|
19
|
+
background: linear-gradient(
|
|
20
|
+
90deg,
|
|
21
|
+
var(--bds-color_grey--subtle) 25%,
|
|
22
|
+
var(--bds-color_bg) 50%,
|
|
23
|
+
var(--bds-color_grey--subtle) 75%
|
|
24
|
+
);
|
|
25
|
+
background-size: 200% 100%;
|
|
26
|
+
animation: shimmer 1.5s infinite;
|
|
27
|
+
border-radius: var(--bds-border_radius--xs);
|
|
28
|
+
min-block-size: var(--bds-space_m);
|
|
29
|
+
inline-size: 100%;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@keyframes shimmer {
|
|
33
|
+
0% { background-position: 200% 0; }
|
|
34
|
+
100% { background-position: -200% 0; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@media (prefers-reduced-motion: reduce) {
|
|
38
|
+
.skeleton {
|
|
39
|
+
animation: none;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
render() {
|
|
45
|
+
return html`<span class="skeleton" aria-hidden="true"></span>`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
customElements.define('bds-skeleton', BdsSkeleton);
|
|
50
|
+
|
|
51
|
+
declare global {
|
|
52
|
+
interface HTMLElementTagNameMap {
|
|
53
|
+
'bds-skeleton': BdsSkeleton;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsTypography } from './bds-typography';
|
|
3
|
+
|
|
4
|
+
describe('bds-typography', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-typography')).toBe(BdsTypography);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('defaults to variant="body"', async () => {
|
|
10
|
+
const el = await fixture('<bds-typography>Text</bds-typography>');
|
|
11
|
+
expect(el.getAttribute('variant')).toBe('body');
|
|
12
|
+
cleanup(el);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders a <p> element for body variant', async () => {
|
|
16
|
+
const el = await fixture('<bds-typography variant="body">Text</bds-typography>');
|
|
17
|
+
const p = el.shadowRoot!.querySelector('p');
|
|
18
|
+
expect(p).not.toBeNull();
|
|
19
|
+
cleanup(el);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders a <p> element for body_s variant', async () => {
|
|
23
|
+
const el = await fixture('<bds-typography variant="body_s">Text</bds-typography>');
|
|
24
|
+
const p = el.shadowRoot!.querySelector('p');
|
|
25
|
+
expect(p).not.toBeNull();
|
|
26
|
+
cleanup(el);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders an <h1> element for h1 variant', async () => {
|
|
30
|
+
const el = await fixture('<bds-typography variant="h1">Heading</bds-typography>');
|
|
31
|
+
const h1 = el.shadowRoot!.querySelector('h1');
|
|
32
|
+
expect(h1).not.toBeNull();
|
|
33
|
+
cleanup(el);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renders an <h2> element for h2 variant', async () => {
|
|
37
|
+
const el = await fixture('<bds-typography variant="h2">Heading</bds-typography>');
|
|
38
|
+
expect(el.shadowRoot!.querySelector('h2')).not.toBeNull();
|
|
39
|
+
cleanup(el);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders an <h3> element for h3 variant', async () => {
|
|
43
|
+
const el = await fixture('<bds-typography variant="h3">Heading</bds-typography>');
|
|
44
|
+
expect(el.shadowRoot!.querySelector('h3')).not.toBeNull();
|
|
45
|
+
cleanup(el);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('renders the element with typography and variant classes', async () => {
|
|
49
|
+
const el = await fixture('<bds-typography variant="h1">Heading</bds-typography>');
|
|
50
|
+
const h1 = el.shadowRoot!.querySelector('h1');
|
|
51
|
+
expect(h1!.classList.contains('typography')).toBe(true);
|
|
52
|
+
expect(h1!.classList.contains('--h1')).toBe(true);
|
|
53
|
+
cleanup(el);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('uses the component attribute to override the tag', async () => {
|
|
57
|
+
const el = await fixture('<bds-typography component="span">Text</bds-typography>');
|
|
58
|
+
expect(el.shadowRoot!.querySelector('span')).not.toBeNull();
|
|
59
|
+
cleanup(el);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('renders slotted content', async () => {
|
|
63
|
+
const el = await fixture('<bds-typography>Hello world</bds-typography>');
|
|
64
|
+
expect(el.textContent?.trim()).toBe('Hello world');
|
|
65
|
+
cleanup(el);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('reflects variant property to attribute', async () => {
|
|
69
|
+
const el = await fixture('<bds-typography>Text</bds-typography>') as BdsTypography;
|
|
70
|
+
el.variant = 'h2';
|
|
71
|
+
await el.updateComplete;
|
|
72
|
+
expect(el.getAttribute('variant')).toBe('h2');
|
|
73
|
+
expect(el.shadowRoot!.querySelector('h2')).not.toBeNull();
|
|
74
|
+
cleanup(el);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { LitElement, css } from 'lit';
|
|
2
|
+
import { html, unsafeStatic } from 'lit/static-html.js';
|
|
3
|
+
|
|
4
|
+
export type TypographyVariant = 'h1' | 'h2' | 'h3' | 'body' | 'body_s';
|
|
5
|
+
|
|
6
|
+
const VARIANT_TAG: Record<TypographyVariant, string> = {
|
|
7
|
+
h1: 'h1',
|
|
8
|
+
h2: 'h2',
|
|
9
|
+
h3: 'h3',
|
|
10
|
+
body: 'p',
|
|
11
|
+
body_s: 'p',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `<bds-typography>` — framework-agnostic Typography custom element.
|
|
16
|
+
*
|
|
17
|
+
* Renders slotted content inside the appropriate semantic HTML element.
|
|
18
|
+
*
|
|
19
|
+
* Attributes:
|
|
20
|
+
* variant — "h1" | "h2" | "h3" | "body" (default) | "body_s"
|
|
21
|
+
* component — override the rendered tag name (e.g. "span", "label")
|
|
22
|
+
*
|
|
23
|
+
* Slots:
|
|
24
|
+
* (default) — text content
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <bds-typography variant="h1">Page title</bds-typography>
|
|
28
|
+
* <bds-typography variant="body_s" component="span">Small text</bds-typography>
|
|
29
|
+
*/
|
|
30
|
+
export class BdsTypography extends LitElement {
|
|
31
|
+
static styles = css`
|
|
32
|
+
:host {
|
|
33
|
+
display: block;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.typography {
|
|
37
|
+
margin: 0;
|
|
38
|
+
font-family: var(--bds-font_family--body);
|
|
39
|
+
color: var(--typography_color, var(--bds-color_on-bg));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.typography.--h1 {
|
|
43
|
+
font-family: var(--bds-font_family--heading);
|
|
44
|
+
font-size: var(--bds-font_size--heading-1);
|
|
45
|
+
font-weight: var(--bds-font_weight--bold);
|
|
46
|
+
line-height: var(--bds-font_line-height--heading);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.typography.--h2 {
|
|
50
|
+
font-family: var(--bds-font_family--heading);
|
|
51
|
+
font-size: var(--bds-font_size--heading-2);
|
|
52
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
53
|
+
line-height: var(--bds-font_line-height--heading);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.typography.--h3 {
|
|
57
|
+
font-family: var(--bds-font_family--heading);
|
|
58
|
+
font-size: var(--bds-font_size--heading-3);
|
|
59
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
60
|
+
line-height: var(--bds-font_line-height--heading);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.typography.--body {
|
|
64
|
+
font-size: var(--bds-font_size--body);
|
|
65
|
+
line-height: var(--bds-font_line-height--body);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.typography.--body_s {
|
|
69
|
+
font-size: var(--bds-font_size--body--s);
|
|
70
|
+
line-height: var(--bds-font_line-height--body);
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
static properties = {
|
|
75
|
+
variant: { type: String, reflect: true },
|
|
76
|
+
component: { type: String },
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
declare variant: TypographyVariant;
|
|
80
|
+
declare component: string;
|
|
81
|
+
|
|
82
|
+
constructor() {
|
|
83
|
+
super();
|
|
84
|
+
this.variant = 'body';
|
|
85
|
+
this.component = '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private get _tag(): string {
|
|
89
|
+
if (this.component) return this.component;
|
|
90
|
+
return VARIANT_TAG[this.variant] ?? 'p';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
render() {
|
|
94
|
+
// unsafeStatic is safe here — tag values are derived from a known allowlist
|
|
95
|
+
// (VARIANT_TAG map + the `component` property which is developer-controlled).
|
|
96
|
+
const tag = unsafeStatic(this._tag);
|
|
97
|
+
const variant = unsafeStatic(this.variant);
|
|
98
|
+
return html`
|
|
99
|
+
<${tag} class="typography --${variant}">
|
|
100
|
+
<slot></slot>
|
|
101
|
+
</${tag}>
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
customElements.define('bds-typography', BdsTypography);
|
|
107
|
+
|
|
108
|
+
declare global {
|
|
109
|
+
interface HTMLElementTagNameMap {
|
|
110
|
+
'bds-typography': BdsTypography;
|
|
111
|
+
}
|
|
112
|
+
}
|