@boostdev/design-system-components 1.1.1 → 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/dist/client.cjs +50 -50
- package/dist/client.css +456 -456
- package/dist/client.js +50 -50
- package/dist/index.cjs +50 -50
- package/dist/index.css +456 -456
- package/dist/index.js +50 -50
- 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 -844
- 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/web-components/index.ts +47 -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-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/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 -304
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
export interface BreadcrumbItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `<bds-breadcrumb>` — framework-agnostic Breadcrumb custom element.
|
|
10
|
+
*
|
|
11
|
+
* Attributes:
|
|
12
|
+
* items — JSON string array of `{ label: string, href?: string }` objects.
|
|
13
|
+
* The last item is treated as the current page (no link).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <bds-breadcrumb items='[{"label":"Home","href":"/"},{"label":"Products","href":"/products"},{"label":"Item"}]'></bds-breadcrumb>
|
|
17
|
+
*/
|
|
18
|
+
export class BdsBreadcrumb extends LitElement {
|
|
19
|
+
static styles = css`
|
|
20
|
+
:host {
|
|
21
|
+
display: block;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.breadcrumb {
|
|
25
|
+
font-size: var(--bds-font_size--body--s);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.list {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-wrap: wrap;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: var(--bds-space_xxs);
|
|
33
|
+
list-style: none;
|
|
34
|
+
margin: 0;
|
|
35
|
+
padding: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.item {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: var(--bds-space_xxs);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.link {
|
|
45
|
+
color: var(--bds-color_interactive_on-bg);
|
|
46
|
+
text-decoration: none;
|
|
47
|
+
transition: var(--bds-animation_transition);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.link:focus-visible {
|
|
51
|
+
outline: var(--bds-outline_default);
|
|
52
|
+
outline-offset: var(--bds-outline_offset);
|
|
53
|
+
border-radius: var(--bds-border_radius--xs);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@media (hover: hover) and (pointer: fine) {
|
|
57
|
+
.link:hover {
|
|
58
|
+
text-decoration: underline;
|
|
59
|
+
text-underline-offset: 0.2em;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.separator {
|
|
64
|
+
color: var(--bds-color_on-bg--subtle);
|
|
65
|
+
user-select: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.current {
|
|
69
|
+
color: var(--bds-color_on-bg--subtle);
|
|
70
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
static properties = {
|
|
75
|
+
items: { type: Array },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
declare items: BreadcrumbItem[];
|
|
79
|
+
|
|
80
|
+
constructor() {
|
|
81
|
+
super();
|
|
82
|
+
this.items = [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
render() {
|
|
86
|
+
return html`
|
|
87
|
+
<nav class="breadcrumb" aria-label="Breadcrumb">
|
|
88
|
+
<ol class="list">
|
|
89
|
+
${this.items.map((item, index) => {
|
|
90
|
+
const isLast = index === this.items.length - 1;
|
|
91
|
+
return html`
|
|
92
|
+
<li class="item">
|
|
93
|
+
${isLast
|
|
94
|
+
? html`<span class="current" aria-current="page">${item.label}</span>`
|
|
95
|
+
: html`
|
|
96
|
+
<a class="link" href=${item.href ?? '#'}>${item.label}</a>
|
|
97
|
+
<span class="separator" aria-hidden="true">›</span>
|
|
98
|
+
`}
|
|
99
|
+
</li>
|
|
100
|
+
`;
|
|
101
|
+
})}
|
|
102
|
+
</ol>
|
|
103
|
+
</nav>
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
customElements.define('bds-breadcrumb', BdsBreadcrumb);
|
|
109
|
+
|
|
110
|
+
declare global {
|
|
111
|
+
interface HTMLElementTagNameMap {
|
|
112
|
+
'bds-breadcrumb': BdsBreadcrumb;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsCard } from './bds-card';
|
|
3
|
+
|
|
4
|
+
describe('bds-card', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-card')).toBe(BdsCard);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('defaults to variant="default", padding="medium", text-align="start"', async () => {
|
|
10
|
+
const el = await fixture('<bds-card>Content</bds-card>') as BdsCard;
|
|
11
|
+
expect(el.getAttribute('variant')).toBe('default');
|
|
12
|
+
expect(el.getAttribute('padding')).toBe('medium');
|
|
13
|
+
expect(el.getAttribute('text-align')).toBe('start');
|
|
14
|
+
cleanup(el);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders a <div> when not clickable', async () => {
|
|
18
|
+
const el = await fixture('<bds-card>Content</bds-card>');
|
|
19
|
+
expect(el.shadowRoot!.querySelector('div.card')).not.toBeNull();
|
|
20
|
+
expect(el.shadowRoot!.querySelector('button')).toBeNull();
|
|
21
|
+
cleanup(el);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders a <button> when clickable is set', async () => {
|
|
25
|
+
const el = await fixture('<bds-card clickable>Content</bds-card>');
|
|
26
|
+
expect(el.shadowRoot!.querySelector('button.card')).not.toBeNull();
|
|
27
|
+
expect(el.shadowRoot!.querySelector('div.card')).toBeNull();
|
|
28
|
+
cleanup(el);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('button has type="button"', async () => {
|
|
32
|
+
const el = await fixture('<bds-card clickable>Content</bds-card>');
|
|
33
|
+
const btn = el.shadowRoot!.querySelector('button');
|
|
34
|
+
expect(btn!.getAttribute('type')).toBe('button');
|
|
35
|
+
cleanup(el);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('fires bds-click event when clickable card is clicked', async () => {
|
|
39
|
+
const el = await fixture('<bds-card clickable>Content</bds-card>');
|
|
40
|
+
const btn = el.shadowRoot!.querySelector('button')!;
|
|
41
|
+
const handler = vi.fn();
|
|
42
|
+
el.addEventListener('bds-click', handler);
|
|
43
|
+
btn.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
|
|
44
|
+
expect(handler).toHaveBeenCalledOnce();
|
|
45
|
+
cleanup(el);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('applies variant, padding and text-align classes to the card element', async () => {
|
|
49
|
+
const el = await fixture('<bds-card variant="elevated" padding="large" text-align="center">Content</bds-card>');
|
|
50
|
+
const card = el.shadowRoot!.querySelector('.card')!;
|
|
51
|
+
expect(card.classList.contains('--elevated')).toBe(true);
|
|
52
|
+
expect(card.classList.contains('--padding-large')).toBe(true);
|
|
53
|
+
expect(card.classList.contains('--text-center')).toBe(true);
|
|
54
|
+
cleanup(el);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('renders slotted content', async () => {
|
|
58
|
+
const el = await fixture('<bds-card>Hello card</bds-card>');
|
|
59
|
+
expect(el.textContent?.trim()).toBe('Hello card');
|
|
60
|
+
cleanup(el);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('reflects variant property update', async () => {
|
|
64
|
+
const el = await fixture('<bds-card>Content</bds-card>') as BdsCard;
|
|
65
|
+
el.variant = 'outlined';
|
|
66
|
+
await el.updateComplete;
|
|
67
|
+
expect(el.getAttribute('variant')).toBe('outlined');
|
|
68
|
+
cleanup(el);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { LitElement, css, html, nothing } from 'lit';
|
|
2
|
+
|
|
3
|
+
export type CardVariant = 'default' | 'elevated' | 'outlined';
|
|
4
|
+
export type CardPadding = 'none' | 'small' | 'medium' | 'large';
|
|
5
|
+
export type CardTextAlign = 'start' | 'center' | 'end';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `<bds-card>` — framework-agnostic Card layout custom element.
|
|
9
|
+
*
|
|
10
|
+
* Attributes:
|
|
11
|
+
* variant — "default" (default) | "elevated" | "outlined"
|
|
12
|
+
* padding — "none" | "small" | "medium" (default) | "large"
|
|
13
|
+
* text-align — "start" (default) | "center" | "end"
|
|
14
|
+
* clickable — boolean; renders as a <button> with hover/active effects
|
|
15
|
+
* aria-label — accessible label (useful when clickable)
|
|
16
|
+
*
|
|
17
|
+
* Slots:
|
|
18
|
+
* (default) — card content
|
|
19
|
+
*
|
|
20
|
+
* Events:
|
|
21
|
+
* bds-click — fired (bubbles, composed) when the card button is clicked
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <bds-card variant="elevated" padding="large">
|
|
25
|
+
* <p>Card content</p>
|
|
26
|
+
* </bds-card>
|
|
27
|
+
*
|
|
28
|
+
* <bds-card clickable aria-label="Open details">
|
|
29
|
+
* <p>Clickable card</p>
|
|
30
|
+
* </bds-card>
|
|
31
|
+
*/
|
|
32
|
+
export class BdsCard extends LitElement {
|
|
33
|
+
static styles = css`
|
|
34
|
+
:host {
|
|
35
|
+
display: block;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.card {
|
|
39
|
+
background-color: var(--card_color, var(--bds-color_bg));
|
|
40
|
+
color: var(--card_on-color, var(--bds-color_on-bg));
|
|
41
|
+
border-radius: var(--bds-border_radius--m);
|
|
42
|
+
transition: all 0.3s ease;
|
|
43
|
+
position: relative;
|
|
44
|
+
box-sizing: border-box;
|
|
45
|
+
inline-size: 100%;
|
|
46
|
+
text-align: inherit;
|
|
47
|
+
font: inherit;
|
|
48
|
+
border: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.card.--default { box-shadow: var(--bds-shadow_s); }
|
|
52
|
+
.card.--elevated { box-shadow: var(--bds-shadow_m); }
|
|
53
|
+
|
|
54
|
+
.card.--outlined {
|
|
55
|
+
--bdc_color: currentcolor;
|
|
56
|
+
outline: var(--bdc-outline_width) solid var(--bdc_color);
|
|
57
|
+
outline-offset: var(--bdc-outline_offset);
|
|
58
|
+
box-shadow: var(--bds-shadow_s);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.card.--padding-none { padding: 0; }
|
|
62
|
+
.card.--padding-small { padding: var(--bds-space_s); }
|
|
63
|
+
.card.--padding-medium { padding: var(--bds-space_m); }
|
|
64
|
+
.card.--padding-large { padding: var(--bds-space_l); }
|
|
65
|
+
|
|
66
|
+
.card.--text-start { text-align: start; }
|
|
67
|
+
.card.--text-center { text-align: center; }
|
|
68
|
+
.card.--text-end { text-align: end; }
|
|
69
|
+
|
|
70
|
+
.card.--clickable {
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
display: block;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.card.--clickable:focus-visible {
|
|
76
|
+
outline: 2px solid var(--color_focus, currentcolor);
|
|
77
|
+
outline-offset: 2px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.card.--clickable:active {
|
|
81
|
+
transform: translateY(0);
|
|
82
|
+
box-shadow: var(--bds-shadow_s);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@media (hover: hover) and (pointer: fine) {
|
|
86
|
+
.card.--clickable:hover {
|
|
87
|
+
transform: translateY(-2px);
|
|
88
|
+
box-shadow: var(--bds-shadow_xl);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.card.--default:hover { box-shadow: var(--bds-shadow_m); }
|
|
92
|
+
.card.--elevated:hover { box-shadow: var(--bds-shadow_2xl); }
|
|
93
|
+
.card.--outlined:hover { box-shadow: var(--bds-shadow_s); }
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
static properties = {
|
|
98
|
+
variant: { type: String, reflect: true },
|
|
99
|
+
padding: { type: String, reflect: true },
|
|
100
|
+
textAlign: { type: String, attribute: 'text-align', reflect: true },
|
|
101
|
+
clickable: { type: Boolean, reflect: true },
|
|
102
|
+
ariaLabel: { type: String, attribute: 'aria-label' },
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
declare variant: CardVariant;
|
|
106
|
+
declare padding: CardPadding;
|
|
107
|
+
declare textAlign: CardTextAlign;
|
|
108
|
+
declare clickable: boolean;
|
|
109
|
+
declare ariaLabel: string;
|
|
110
|
+
|
|
111
|
+
constructor() {
|
|
112
|
+
super();
|
|
113
|
+
this.variant = 'default';
|
|
114
|
+
this.padding = 'medium';
|
|
115
|
+
this.textAlign = 'start';
|
|
116
|
+
this.clickable = false;
|
|
117
|
+
this.ariaLabel = '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private _handleClick() {
|
|
121
|
+
this.dispatchEvent(new CustomEvent('bds-click', { bubbles: true, composed: true }));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
render() {
|
|
125
|
+
const classes = [
|
|
126
|
+
'card',
|
|
127
|
+
`--${this.variant}`,
|
|
128
|
+
`--padding-${this.padding}`,
|
|
129
|
+
`--text-${this.textAlign}`,
|
|
130
|
+
this.clickable ? '--clickable' : '',
|
|
131
|
+
].filter(Boolean).join(' ');
|
|
132
|
+
|
|
133
|
+
if (this.clickable) {
|
|
134
|
+
return html`
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
class=${classes}
|
|
138
|
+
@click=${this._handleClick}
|
|
139
|
+
aria-label=${this.ariaLabel || nothing}
|
|
140
|
+
>
|
|
141
|
+
<slot></slot>
|
|
142
|
+
</button>
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return html`<div class=${classes}><slot></slot></div>`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
customElements.define('bds-card', BdsCard);
|
|
151
|
+
|
|
152
|
+
declare global {
|
|
153
|
+
interface HTMLElementTagNameMap {
|
|
154
|
+
'bds-card': BdsCard;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsDescriptionList } from './bds-description-list';
|
|
3
|
+
|
|
4
|
+
describe('bds-description-list', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-description-list')).toBe(BdsDescriptionList);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('defaults to layout="stacked"', async () => {
|
|
10
|
+
const el = await fixture('<bds-description-list></bds-description-list>');
|
|
11
|
+
expect(el.getAttribute('layout')).toBe('stacked');
|
|
12
|
+
cleanup(el);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders a <dl> in shadow DOM', async () => {
|
|
16
|
+
const el = await fixture('<bds-description-list></bds-description-list>');
|
|
17
|
+
expect(el.shadowRoot!.querySelector('dl')).not.toBeNull();
|
|
18
|
+
cleanup(el);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('accepts slotted <dt> and <dd> elements', async () => {
|
|
22
|
+
const el = await fixture('<bds-description-list><dt>Name</dt><dd>Jane</dd></bds-description-list>');
|
|
23
|
+
expect(el.querySelector('dt')!.textContent).toBe('Name');
|
|
24
|
+
expect(el.querySelector('dd')!.textContent).toBe('Jane');
|
|
25
|
+
cleanup(el);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('reflects layout attribute', async () => {
|
|
29
|
+
const el = await fixture('<bds-description-list layout="inline"></bds-description-list>') as BdsDescriptionList;
|
|
30
|
+
expect(el.getAttribute('layout')).toBe('inline');
|
|
31
|
+
el.layout = 'stacked';
|
|
32
|
+
await el.updateComplete;
|
|
33
|
+
expect(el.getAttribute('layout')).toBe('stacked');
|
|
34
|
+
cleanup(el);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<bds-description-list>` — framework-agnostic Description List custom element.
|
|
5
|
+
*
|
|
6
|
+
* Renders a `<dl>` styled list. Consumers provide `<dt>` and `<dd>` elements
|
|
7
|
+
* as slotted children; this component handles layout and styling.
|
|
8
|
+
*
|
|
9
|
+
* Attributes:
|
|
10
|
+
* layout — "stacked" (default) | "inline"
|
|
11
|
+
*
|
|
12
|
+
* Slots:
|
|
13
|
+
* (default) — `<dt>` and `<dd>` pairs
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <bds-description-list layout="inline">
|
|
17
|
+
* <dt>Name</dt>
|
|
18
|
+
* <dd>Jane Doe</dd>
|
|
19
|
+
* <dt>Email</dt>
|
|
20
|
+
* <dd>jane@example.com</dd>
|
|
21
|
+
* </bds-description-list>
|
|
22
|
+
*/
|
|
23
|
+
export class BdsDescriptionList extends LitElement {
|
|
24
|
+
static styles = css`
|
|
25
|
+
:host {
|
|
26
|
+
display: block;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.list {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
gap: var(--bds-space_s);
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 0;
|
|
35
|
+
font-size: var(--bds-font_size--body);
|
|
36
|
+
color: var(--bds-color_on-bg);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Style slotted dt/dd elements */
|
|
40
|
+
::slotted(dt) {
|
|
41
|
+
font-weight: var(--bds-font_weight--semibold);
|
|
42
|
+
color: var(--bds-color_on-bg);
|
|
43
|
+
margin: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
::slotted(dd) {
|
|
47
|
+
margin: 0;
|
|
48
|
+
color: var(--bds-color_on-bg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Inline layout: pair each dt/dd side by side */
|
|
52
|
+
:host([layout='inline']) .list {
|
|
53
|
+
gap: var(--bds-space_xs);
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
static properties = {
|
|
58
|
+
layout: { type: String, reflect: true },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
declare layout: 'stacked' | 'inline';
|
|
62
|
+
|
|
63
|
+
constructor() {
|
|
64
|
+
super();
|
|
65
|
+
this.layout = 'stacked';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
render() {
|
|
69
|
+
return html`<dl class="list"><slot></slot></dl>`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
customElements.define('bds-description-list', BdsDescriptionList);
|
|
74
|
+
|
|
75
|
+
declare global {
|
|
76
|
+
interface HTMLElementTagNameMap {
|
|
77
|
+
'bds-description-list': BdsDescriptionList;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsIconWrapper } from './bds-icon-wrapper';
|
|
3
|
+
|
|
4
|
+
describe('bds-icon-wrapper', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-icon-wrapper')).toBe(BdsIconWrapper);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('renders a wrapper div in shadow DOM', async () => {
|
|
10
|
+
const el = await fixture('<bds-icon-wrapper></bds-icon-wrapper>');
|
|
11
|
+
expect(el.shadowRoot!.querySelector('.wrapper')).not.toBeNull();
|
|
12
|
+
cleanup(el);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders slotted content', async () => {
|
|
16
|
+
const el = await fixture('<bds-icon-wrapper><svg aria-hidden="true"></svg></bds-icon-wrapper>');
|
|
17
|
+
const svg = el.querySelector('svg');
|
|
18
|
+
expect(svg).not.toBeNull();
|
|
19
|
+
cleanup(el);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<bds-icon-wrapper>` — framework-agnostic Icon Wrapper custom element.
|
|
5
|
+
*
|
|
6
|
+
* Displays an icon inside a circular container with background and foreground theming.
|
|
7
|
+
*
|
|
8
|
+
* Attributes:
|
|
9
|
+
* aria-hidden — boolean attribute; set to "true" for decorative icons
|
|
10
|
+
*
|
|
11
|
+
* Slots:
|
|
12
|
+
* (default) — icon content (SVG or img)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <bds-icon-wrapper aria-hidden="true">
|
|
16
|
+
* <svg>…</svg>
|
|
17
|
+
* </bds-icon-wrapper>
|
|
18
|
+
*/
|
|
19
|
+
export class BdsIconWrapper extends LitElement {
|
|
20
|
+
static styles = css`
|
|
21
|
+
:host {
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.wrapper {
|
|
26
|
+
inline-size: 2em;
|
|
27
|
+
block-size: 2em;
|
|
28
|
+
font-size: 3em;
|
|
29
|
+
border-radius: 50%;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
margin: var(--bds-space_l) auto var(--bds-space_m);
|
|
34
|
+
background: var(--icon-wrapper-color, var(--bds-color_bg));
|
|
35
|
+
color: var(--bds-on-icon-wrapper-color, var(--bds-color_on-bg));
|
|
36
|
+
fill: var(--bds-on-icon-wrapper-color, var(--bds-color_on-bg));
|
|
37
|
+
position: relative;
|
|
38
|
+
transition: var(--bds-animation_transition);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.wrapper svg {
|
|
42
|
+
fill: currentcolor;
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
static properties = {
|
|
47
|
+
ariaHidden: { type: String, attribute: 'aria-hidden', reflect: true },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
declare ariaHidden: string;
|
|
51
|
+
|
|
52
|
+
constructor() {
|
|
53
|
+
super();
|
|
54
|
+
this.ariaHidden = '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render() {
|
|
58
|
+
return html`<div class="wrapper"><slot></slot></div>`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
customElements.define('bds-icon-wrapper', BdsIconWrapper);
|
|
63
|
+
|
|
64
|
+
declare global {
|
|
65
|
+
interface HTMLElementTagNameMap {
|
|
66
|
+
'bds-icon-wrapper': BdsIconWrapper;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { fixture, cleanup } from '../test/helpers';
|
|
2
|
+
import { BdsLink } from './bds-link';
|
|
3
|
+
|
|
4
|
+
describe('bds-link', () => {
|
|
5
|
+
it('is registered as a custom element', () => {
|
|
6
|
+
expect(customElements.get('bds-link')).toBe(BdsLink);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('defaults to variant="default"', async () => {
|
|
10
|
+
const el = await fixture('<bds-link href="/test">Link</bds-link>');
|
|
11
|
+
expect(el.getAttribute('variant')).toBe('default');
|
|
12
|
+
cleanup(el);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders an <a> with the given href', async () => {
|
|
16
|
+
const el = await fixture('<bds-link href="/about">About</bds-link>');
|
|
17
|
+
const a = el.shadowRoot!.querySelector('a');
|
|
18
|
+
expect(a).not.toBeNull();
|
|
19
|
+
expect(a!.getAttribute('href')).toBe('/about');
|
|
20
|
+
cleanup(el);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders slotted content', async () => {
|
|
24
|
+
const el = await fixture('<bds-link href="/">Home</bds-link>');
|
|
25
|
+
expect(el.textContent?.trim()).toBe('Home');
|
|
26
|
+
cleanup(el);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('adds target="_blank" and rel="noreferrer noopener" when external is set', async () => {
|
|
30
|
+
const el = await fixture('<bds-link href="https://example.com" external>Visit</bds-link>');
|
|
31
|
+
const a = el.shadowRoot!.querySelector('a');
|
|
32
|
+
expect(a!.getAttribute('target')).toBe('_blank');
|
|
33
|
+
expect(a!.getAttribute('rel')).toBe('noreferrer noopener');
|
|
34
|
+
cleanup(el);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders visually hidden external label when external is set', async () => {
|
|
38
|
+
const el = await fixture('<bds-link href="https://example.com" external>Visit</bds-link>');
|
|
39
|
+
const label = el.shadowRoot!.querySelector('.external-label');
|
|
40
|
+
expect(label).not.toBeNull();
|
|
41
|
+
expect(label!.textContent).toBe('(opens in new tab)');
|
|
42
|
+
cleanup(el);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('uses custom external-label when provided', async () => {
|
|
46
|
+
const el = await fixture('<bds-link href="https://example.com" external external-label="(new window)">Visit</bds-link>');
|
|
47
|
+
const label = el.shadowRoot!.querySelector('.external-label');
|
|
48
|
+
expect(label!.textContent).toBe('(new window)');
|
|
49
|
+
cleanup(el);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('does not render external label when external is not set', async () => {
|
|
53
|
+
const el = await fixture('<bds-link href="/about">About</bds-link>');
|
|
54
|
+
expect(el.shadowRoot!.querySelector('.external-label')).toBeNull();
|
|
55
|
+
cleanup(el);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('applies variant class to the anchor', async () => {
|
|
59
|
+
const el = await fixture('<bds-link href="/test" variant="subtle">Link</bds-link>');
|
|
60
|
+
const a = el.shadowRoot!.querySelector('a');
|
|
61
|
+
expect(a!.classList.contains('--variant_subtle')).toBe(true);
|
|
62
|
+
cleanup(el);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('reflects variant property updates', async () => {
|
|
66
|
+
const el = await fixture('<bds-link href="/test">Link</bds-link>') as BdsLink;
|
|
67
|
+
el.variant = 'standalone';
|
|
68
|
+
await el.updateComplete;
|
|
69
|
+
expect(el.getAttribute('variant')).toBe('standalone');
|
|
70
|
+
cleanup(el);
|
|
71
|
+
});
|
|
72
|
+
});
|