@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.
Files changed (206) hide show
  1. package/dist/client.cjs +50 -50
  2. package/dist/client.css +456 -456
  3. package/dist/client.js +50 -50
  4. package/dist/index.cjs +50 -50
  5. package/dist/index.css +456 -456
  6. package/dist/index.js +50 -50
  7. package/dist/web-components/chunk-2QA62WCQ.js +48 -0
  8. package/dist/web-components/chunk-4BX6AWXX.js +146 -0
  9. package/dist/web-components/chunk-57TRNCWQ.js +93 -0
  10. package/dist/web-components/chunk-5IPHEONG.js +139 -0
  11. package/dist/web-components/chunk-5R6IFELV.js +63 -0
  12. package/dist/web-components/chunk-5TFWOPLH.js +49 -0
  13. package/dist/web-components/chunk-6I3CFC26.js +89 -0
  14. package/dist/web-components/chunk-6T3RKUKG.js +65 -0
  15. package/dist/web-components/chunk-7FMR53BW.js +127 -0
  16. package/dist/web-components/chunk-A67LCT7C.js +134 -0
  17. package/dist/web-components/chunk-AGKJTMJI.js +56 -0
  18. package/dist/web-components/chunk-BQLZ7I4G.js +56 -0
  19. package/dist/web-components/chunk-CAPI25CU.js +53 -0
  20. package/dist/web-components/chunk-CJ5ZOO42.js +229 -0
  21. package/dist/web-components/chunk-CR5PGUHV.js +109 -0
  22. package/dist/web-components/chunk-ECW3HHWA.js +185 -0
  23. package/dist/web-components/chunk-HGBNDR22.js +98 -0
  24. package/dist/web-components/chunk-IK3PTFZ4.js +133 -0
  25. package/dist/web-components/chunk-IQGLIQSO.js +188 -0
  26. package/dist/web-components/chunk-ITHLKR7Q.js +105 -0
  27. package/dist/web-components/chunk-K5CE6HSB.js +169 -0
  28. package/dist/web-components/chunk-L3XHOMN6.js +111 -0
  29. package/dist/{web-components.js → web-components/chunk-LGEZYFUU.js} +62 -844
  30. package/dist/web-components/chunk-LSC4N56Q.js +168 -0
  31. package/dist/web-components/chunk-LY62SAVN.js +64 -0
  32. package/dist/web-components/chunk-O4UFI2RX.js +244 -0
  33. package/dist/web-components/chunk-O7I63SQX.js +124 -0
  34. package/dist/web-components/chunk-OHUTLSIQ.js +69 -0
  35. package/dist/web-components/chunk-OQTRTMAB.js +27 -0
  36. package/dist/web-components/chunk-OTX4COSV.js +187 -0
  37. package/dist/web-components/chunk-PY62FTRW.js +179 -0
  38. package/dist/web-components/chunk-Q4T5BJT6.js +94 -0
  39. package/dist/web-components/chunk-QDRH46TF.js +281 -0
  40. package/dist/web-components/chunk-QDST57JC.js +119 -0
  41. package/dist/web-components/chunk-QPOKHLMQ.js +45 -0
  42. package/dist/web-components/chunk-SOQQ7RVJ.js +88 -0
  43. package/dist/web-components/chunk-UFFCKVZE.js +149 -0
  44. package/dist/web-components/chunk-VM3YHGQ2.js +152 -0
  45. package/dist/web-components/chunk-VMMVWLXL.js +53 -0
  46. package/dist/web-components/chunk-VSS5ITNE.js +109 -0
  47. package/dist/web-components/chunk-XIENXR7H.js +118 -0
  48. package/dist/web-components/chunk-Y4MAXTMA.js +80 -0
  49. package/dist/web-components/chunk-YLVTEY23.js +165 -0
  50. package/dist/web-components/globals.d.ts +2 -0
  51. package/dist/web-components/globals.js +1 -0
  52. package/dist/web-components/index.d.ts +41 -0
  53. package/dist/web-components/index.js +165 -0
  54. package/dist/web-components/interaction/bds-accordion-item.d.ts +54 -0
  55. package/dist/web-components/interaction/bds-accordion-item.js +7 -0
  56. package/dist/web-components/interaction/bds-accordion.d.ts +50 -0
  57. package/dist/web-components/interaction/bds-accordion.js +7 -0
  58. package/dist/web-components/interaction/bds-button.d.ts +100 -0
  59. package/dist/web-components/interaction/bds-button.js +7 -0
  60. package/dist/web-components/interaction/bds-collapsible.d.ts +52 -0
  61. package/dist/web-components/interaction/bds-collapsible.js +7 -0
  62. package/dist/web-components/interaction/bds-dialog.d.ts +56 -0
  63. package/dist/web-components/interaction/bds-dialog.js +7 -0
  64. package/dist/web-components/interaction/bds-drawer.d.ts +64 -0
  65. package/dist/web-components/interaction/bds-drawer.js +7 -0
  66. package/dist/web-components/interaction/bds-popover.d.ts +62 -0
  67. package/dist/web-components/interaction/bds-popover.js +7 -0
  68. package/dist/web-components/interaction/bds-skip-link.d.ts +36 -0
  69. package/dist/web-components/interaction/bds-skip-link.js +7 -0
  70. package/dist/web-components/interaction/bds-tab-panel.d.ts +26 -0
  71. package/dist/web-components/interaction/bds-tab-panel.js +7 -0
  72. package/dist/web-components/interaction/bds-tab.d.ts +45 -0
  73. package/dist/web-components/interaction/bds-tab.js +7 -0
  74. package/dist/web-components/interaction/bds-tabs.d.ts +46 -0
  75. package/dist/web-components/interaction/bds-tabs.js +7 -0
  76. package/dist/web-components/interaction/bds-toast-provider.d.ts +57 -0
  77. package/dist/web-components/interaction/bds-toast-provider.js +7 -0
  78. package/dist/web-components/interaction/bds-tooltip.d.ts +50 -0
  79. package/dist/web-components/interaction/bds-tooltip.js +7 -0
  80. package/dist/web-components/interaction/form/bds-checkbox.d.ts +70 -0
  81. package/dist/web-components/interaction/form/bds-checkbox.js +7 -0
  82. package/dist/web-components/interaction/form/bds-combobox.d.ts +84 -0
  83. package/dist/web-components/interaction/form/bds-combobox.js +7 -0
  84. package/dist/web-components/interaction/form/bds-file-input.d.ts +62 -0
  85. package/dist/web-components/interaction/form/bds-file-input.js +7 -0
  86. package/dist/web-components/interaction/form/bds-number-input.d.ts +68 -0
  87. package/dist/web-components/interaction/form/bds-number-input.js +7 -0
  88. package/dist/web-components/interaction/form/bds-radio.d.ts +68 -0
  89. package/dist/web-components/interaction/form/bds-radio.js +7 -0
  90. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +71 -0
  91. package/dist/web-components/interaction/form/bds-segmented-control.js +7 -0
  92. package/dist/web-components/interaction/form/bds-select.d.ts +79 -0
  93. package/dist/web-components/interaction/form/bds-select.js +7 -0
  94. package/dist/web-components/interaction/form/bds-slider.d.ts +70 -0
  95. package/dist/web-components/interaction/form/bds-slider.js +7 -0
  96. package/dist/web-components/interaction/form/bds-switch.d.ts +67 -0
  97. package/dist/web-components/interaction/form/bds-switch.js +7 -0
  98. package/dist/web-components/interaction/form/bds-textarea.d.ts +70 -0
  99. package/dist/web-components/interaction/form/bds-textarea.js +7 -0
  100. package/dist/web-components/ui/bds-alert.d.ts +66 -0
  101. package/dist/web-components/ui/bds-alert.js +7 -0
  102. package/dist/web-components/ui/bds-avatar.d.ts +50 -0
  103. package/dist/web-components/ui/bds-avatar.js +7 -0
  104. package/dist/web-components/ui/bds-badge.d.ts +35 -0
  105. package/dist/web-components/ui/bds-badge.js +7 -0
  106. package/dist/web-components/ui/bds-breadcrumb.d.ts +35 -0
  107. package/dist/web-components/ui/bds-breadcrumb.js +7 -0
  108. package/dist/web-components/ui/bds-card.d.ts +72 -0
  109. package/dist/web-components/ui/bds-card.js +7 -0
  110. package/dist/web-components/ui/bds-description-list.d.ts +42 -0
  111. package/dist/web-components/ui/bds-description-list.js +7 -0
  112. package/dist/web-components/ui/bds-icon-wrapper.d.ts +39 -0
  113. package/dist/web-components/ui/bds-icon-wrapper.js +7 -0
  114. package/dist/web-components/ui/bds-link.d.ts +59 -0
  115. package/dist/web-components/ui/bds-link.js +7 -0
  116. package/dist/web-components/ui/bds-loading.d.ts +32 -0
  117. package/dist/web-components/ui/bds-loading.js +7 -0
  118. package/dist/web-components/ui/bds-notification-banner.d.ts +61 -0
  119. package/dist/web-components/ui/bds-notification-banner.js +7 -0
  120. package/dist/web-components/ui/bds-progress-circle.d.ts +53 -0
  121. package/dist/web-components/ui/bds-progress-circle.js +7 -0
  122. package/dist/web-components/ui/bds-progress.d.ts +54 -0
  123. package/dist/web-components/ui/bds-progress.js +7 -0
  124. package/dist/web-components/ui/bds-rating.d.ts +36 -0
  125. package/dist/web-components/ui/bds-rating.js +7 -0
  126. package/dist/web-components/ui/bds-section-header.d.ts +53 -0
  127. package/dist/web-components/ui/bds-section-header.js +8 -0
  128. package/dist/web-components/ui/bds-separator.d.ts +32 -0
  129. package/dist/web-components/ui/bds-separator.js +7 -0
  130. package/dist/web-components/ui/bds-skeleton.d.ts +22 -0
  131. package/dist/web-components/ui/bds-skeleton.js +7 -0
  132. package/dist/web-components/ui/bds-typography.d.ts +44 -0
  133. package/dist/web-components/ui/bds-typography.js +8 -0
  134. package/package.json +169 -4
  135. package/src/web-components/index.ts +47 -0
  136. package/src/web-components/interaction/bds-accordion-item.spec.ts +75 -0
  137. package/src/web-components/interaction/bds-accordion-item.ts +181 -0
  138. package/src/web-components/interaction/bds-accordion.spec.ts +73 -0
  139. package/src/web-components/interaction/bds-accordion.ts +99 -0
  140. package/src/web-components/interaction/bds-collapsible.spec.ts +56 -0
  141. package/src/web-components/interaction/bds-collapsible.ts +170 -0
  142. package/src/web-components/interaction/bds-dialog.spec.ts +119 -0
  143. package/src/web-components/interaction/bds-dialog.ts +224 -0
  144. package/src/web-components/interaction/bds-drawer.spec.ts +113 -0
  145. package/src/web-components/interaction/bds-drawer.ts +266 -0
  146. package/src/web-components/interaction/bds-skip-link.spec.ts +42 -0
  147. package/src/web-components/interaction/bds-skip-link.ts +74 -0
  148. package/src/web-components/interaction/bds-tab-panel.spec.ts +32 -0
  149. package/src/web-components/interaction/bds-tab-panel.ts +38 -0
  150. package/src/web-components/interaction/bds-tab.spec.ts +71 -0
  151. package/src/web-components/interaction/bds-tab.ts +110 -0
  152. package/src/web-components/interaction/bds-tabs.spec.ts +120 -0
  153. package/src/web-components/interaction/bds-tabs.ts +152 -0
  154. package/src/web-components/interaction/bds-tooltip.spec.ts +49 -0
  155. package/src/web-components/interaction/bds-tooltip.ts +127 -0
  156. package/src/web-components/interaction/form/bds-checkbox.spec.ts +63 -0
  157. package/src/web-components/interaction/form/bds-checkbox.ts +168 -0
  158. package/src/web-components/interaction/form/bds-combobox.spec.ts +117 -0
  159. package/src/web-components/interaction/form/bds-combobox.ts +336 -0
  160. package/src/web-components/interaction/form/bds-file-input.spec.ts +58 -0
  161. package/src/web-components/interaction/form/bds-file-input.ts +209 -0
  162. package/src/web-components/interaction/form/bds-number-input.spec.ts +87 -0
  163. package/src/web-components/interaction/form/bds-number-input.ts +221 -0
  164. package/src/web-components/interaction/form/bds-radio.spec.ts +70 -0
  165. package/src/web-components/interaction/form/bds-radio.ts +157 -0
  166. package/src/web-components/interaction/form/bds-segmented-control.spec.ts +74 -0
  167. package/src/web-components/interaction/form/bds-segmented-control.ts +211 -0
  168. package/src/web-components/interaction/form/bds-select.spec.ts +74 -0
  169. package/src/web-components/interaction/form/bds-select.ts +190 -0
  170. package/src/web-components/interaction/form/bds-slider.spec.ts +41 -0
  171. package/src/web-components/interaction/form/bds-slider.ts +162 -0
  172. package/src/web-components/interaction/form/bds-switch.spec.ts +62 -0
  173. package/src/web-components/interaction/form/bds-switch.ts +166 -0
  174. package/src/web-components/interaction/form/bds-textarea.spec.ts +55 -0
  175. package/src/web-components/interaction/form/bds-textarea.ts +143 -0
  176. package/src/web-components/ui/bds-avatar.spec.ts +75 -0
  177. package/src/web-components/ui/bds-avatar.ts +122 -0
  178. package/src/web-components/ui/bds-breadcrumb.spec.ts +55 -0
  179. package/src/web-components/ui/bds-breadcrumb.ts +114 -0
  180. package/src/web-components/ui/bds-card.spec.ts +70 -0
  181. package/src/web-components/ui/bds-card.ts +156 -0
  182. package/src/web-components/ui/bds-description-list.spec.ts +36 -0
  183. package/src/web-components/ui/bds-description-list.ts +79 -0
  184. package/src/web-components/ui/bds-icon-wrapper.spec.ts +21 -0
  185. package/src/web-components/ui/bds-icon-wrapper.ts +68 -0
  186. package/src/web-components/ui/bds-link.spec.ts +72 -0
  187. package/src/web-components/ui/bds-link.ts +138 -0
  188. package/src/web-components/ui/bds-loading.spec.ts +38 -0
  189. package/src/web-components/ui/bds-loading.ts +76 -0
  190. package/src/web-components/ui/bds-notification-banner.spec.ts +82 -0
  191. package/src/web-components/ui/bds-notification-banner.ts +185 -0
  192. package/src/web-components/ui/bds-progress-circle.spec.ts +58 -0
  193. package/src/web-components/ui/bds-progress-circle.ts +142 -0
  194. package/src/web-components/ui/bds-progress.spec.ts +66 -0
  195. package/src/web-components/ui/bds-progress.ts +127 -0
  196. package/src/web-components/ui/bds-rating.spec.ts +70 -0
  197. package/src/web-components/ui/bds-rating.ts +80 -0
  198. package/src/web-components/ui/bds-section-header.spec.ts +57 -0
  199. package/src/web-components/ui/bds-section-header.ts +96 -0
  200. package/src/web-components/ui/bds-separator.spec.ts +50 -0
  201. package/src/web-components/ui/bds-separator.ts +66 -0
  202. package/src/web-components/ui/bds-skeleton.spec.ts +22 -0
  203. package/src/web-components/ui/bds-skeleton.ts +55 -0
  204. package/src/web-components/ui/bds-typography.spec.ts +76 -0
  205. package/src/web-components/ui/bds-typography.ts +112 -0
  206. package/dist/web-components.d.ts +0 -304
@@ -0,0 +1,138 @@
1
+ import { LitElement, css, html, nothing } from 'lit';
2
+
3
+ export type LinkVariant = 'default' | 'subtle' | 'standalone';
4
+
5
+ /**
6
+ * `<bds-link>` — framework-agnostic Link custom element.
7
+ *
8
+ * Attributes:
9
+ * href — URL for the anchor
10
+ * target — link target (e.g. "_blank")
11
+ * variant — "default" (default) | "subtle" | "standalone"
12
+ * external — boolean; when set, adds rel="noreferrer noopener" and a
13
+ * visually-hidden label for screen readers
14
+ * external-label — the screen reader text appended for external links
15
+ * (default: "(opens in new tab)")
16
+ *
17
+ * Slots:
18
+ * (default) — link text / content
19
+ *
20
+ * @example
21
+ * <bds-link href="https://example.com" external>Visit site</bds-link>
22
+ */
23
+ export class BdsLink extends LitElement {
24
+ static styles = css`
25
+ :host {
26
+ display: inline;
27
+ }
28
+
29
+ .link {
30
+ color: var(--link_color, var(--bds-color_interactive_on-bg));
31
+ text-decoration: underline;
32
+ text-underline-offset: 0.2em;
33
+ transition: var(--bds-animation_transition);
34
+ cursor: pointer;
35
+ }
36
+
37
+ .link:focus-visible {
38
+ outline: var(--bds-outline_default);
39
+ outline-offset: var(--bds-outline_offset);
40
+ border-radius: var(--bds-border_radius--xs);
41
+ }
42
+
43
+ @media (hover: hover) and (pointer: fine) {
44
+ .link:hover {
45
+ --link_color: var(--link_color-hover, var(--bds-color_blue--strong));
46
+ }
47
+ }
48
+
49
+ .link.--variant_default {
50
+ --link_color: var(--bds-color_interactive_on-bg);
51
+ --link_color-hover: var(--bds-color_blue--strong);
52
+ }
53
+
54
+ .link.--variant_subtle {
55
+ --link_color: var(--bds-color_on-bg);
56
+ --link_color-hover: var(--bds-color_interactive_on-bg);
57
+ }
58
+
59
+ .link.--variant_standalone {
60
+ --link_color: var(--bds-color_interactive_on-bg);
61
+ --link_color-hover: var(--bds-color_blue--strong);
62
+ display: inline-flex;
63
+ align-items: center;
64
+ gap: var(--bds-space_xxs);
65
+ font-weight: var(--bds-font_weight--semibold);
66
+ text-decoration: none;
67
+ }
68
+
69
+ @media (hover: hover) and (pointer: fine) {
70
+ .link.--variant_standalone:hover {
71
+ text-decoration: underline;
72
+ text-underline-offset: 0.2em;
73
+ }
74
+ }
75
+
76
+ .external-label {
77
+ position: absolute;
78
+ inline-size: 1px;
79
+ block-size: 1px;
80
+ padding: 0;
81
+ margin: -1px;
82
+ overflow: hidden;
83
+ clip: rect(0, 0, 0, 0);
84
+ white-space: nowrap;
85
+ border-width: 0;
86
+ }
87
+ `;
88
+
89
+ static properties = {
90
+ href: { type: String },
91
+ target: { type: String },
92
+ variant: { type: String, reflect: true },
93
+ external: { type: Boolean, reflect: true },
94
+ externalLabel: { type: String, attribute: 'external-label' },
95
+ };
96
+
97
+ declare href: string;
98
+ declare target: string;
99
+ declare variant: LinkVariant;
100
+ declare external: boolean;
101
+ declare externalLabel: string;
102
+
103
+ constructor() {
104
+ super();
105
+ this.href = '';
106
+ this.target = '';
107
+ this.variant = 'default';
108
+ this.external = false;
109
+ this.externalLabel = '(opens in new tab)';
110
+ }
111
+
112
+ render() {
113
+ const rel = this.external ? 'noreferrer noopener' : nothing;
114
+ const target = this.external ? '_blank' : (this.target || nothing);
115
+
116
+ return html`
117
+ <a
118
+ class="link --variant_${this.variant}"
119
+ href=${this.href || nothing}
120
+ target=${target}
121
+ rel=${rel}
122
+ >
123
+ <slot></slot>
124
+ ${this.external
125
+ ? html`<span class="external-label">${this.externalLabel}</span>`
126
+ : nothing}
127
+ </a>
128
+ `;
129
+ }
130
+ }
131
+
132
+ customElements.define('bds-link', BdsLink);
133
+
134
+ declare global {
135
+ interface HTMLElementTagNameMap {
136
+ 'bds-link': BdsLink;
137
+ }
138
+ }
@@ -0,0 +1,38 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsLoading } from './bds-loading';
3
+
4
+ describe('bds-loading', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-loading')).toBe(BdsLoading);
7
+ });
8
+
9
+ it('defaults to size="medium"', async () => {
10
+ const el = await fixture('<bds-loading></bds-loading>');
11
+ expect(el.getAttribute('size')).toBe('medium');
12
+ cleanup(el);
13
+ });
14
+
15
+ it('renders a spinner in shadow DOM', async () => {
16
+ const el = await fixture('<bds-loading></bds-loading>');
17
+ const spinner = el.shadowRoot!.querySelector('.spinner');
18
+ expect(spinner).not.toBeNull();
19
+ cleanup(el);
20
+ });
21
+
22
+ it('spinner has role="status" and aria-label="Loading"', async () => {
23
+ const el = await fixture('<bds-loading></bds-loading>');
24
+ const spinner = el.shadowRoot!.querySelector('.spinner');
25
+ expect(spinner!.getAttribute('role')).toBe('status');
26
+ expect(spinner!.getAttribute('aria-label')).toBe('Loading');
27
+ cleanup(el);
28
+ });
29
+
30
+ it('reflects size attribute', async () => {
31
+ const el = await fixture('<bds-loading size="large"></bds-loading>') as BdsLoading;
32
+ expect(el.size).toBe('large');
33
+ el.size = 'small';
34
+ await el.updateComplete;
35
+ expect(el.getAttribute('size')).toBe('small');
36
+ cleanup(el);
37
+ });
38
+ });
@@ -0,0 +1,76 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ /**
4
+ * `<bds-loading>` — framework-agnostic Loading spinner custom element.
5
+ *
6
+ * Attributes:
7
+ * size — "small" | "medium" (default) | "large"
8
+ *
9
+ * @example
10
+ * <bds-loading></bds-loading>
11
+ * <bds-loading size="large"></bds-loading>
12
+ */
13
+ export class BdsLoading extends LitElement {
14
+ static styles = css`
15
+ :host {
16
+ display: flex;
17
+ justify-content: center;
18
+ align-items: center;
19
+ }
20
+
21
+ .spinner {
22
+ inline-size: 2.5em;
23
+ block-size: 2.5em;
24
+ border: 4px solid var(--bds-color_bg);
25
+ border-block-start: 4px solid var(--bds-color_interactive);
26
+ border-radius: 50%;
27
+ animation: spin 1s linear infinite;
28
+ }
29
+
30
+ :host([size='small']) .spinner {
31
+ inline-size: 1.5em;
32
+ block-size: 1.5em;
33
+ border-width: 2px;
34
+ }
35
+
36
+ :host([size='large']) .spinner {
37
+ inline-size: 4em;
38
+ block-size: 4em;
39
+ border-width: 6px;
40
+ }
41
+
42
+ @keyframes spin {
43
+ 0% { transform: rotate(0deg); }
44
+ 100% { transform: rotate(360deg); }
45
+ }
46
+
47
+ @media (prefers-reduced-motion: reduce) {
48
+ .spinner {
49
+ animation: none;
50
+ }
51
+ }
52
+ `;
53
+
54
+ static properties = {
55
+ size: { type: String, reflect: true },
56
+ };
57
+
58
+ declare size: 'small' | 'medium' | 'large';
59
+
60
+ constructor() {
61
+ super();
62
+ this.size = 'medium';
63
+ }
64
+
65
+ render() {
66
+ return html`<div class="spinner" role="status" aria-label="Loading"></div>`;
67
+ }
68
+ }
69
+
70
+ customElements.define('bds-loading', BdsLoading);
71
+
72
+ declare global {
73
+ interface HTMLElementTagNameMap {
74
+ 'bds-loading': BdsLoading;
75
+ }
76
+ }
@@ -0,0 +1,82 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsNotificationBanner } from './bds-notification-banner';
3
+
4
+ describe('bds-notification-banner', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-notification-banner')).toBe(BdsNotificationBanner);
7
+ });
8
+
9
+ it('defaults to variant="info"', async () => {
10
+ const el = await fixture('<bds-notification-banner>Message</bds-notification-banner>');
11
+ expect(el.getAttribute('variant')).toBe('info');
12
+ cleanup(el);
13
+ });
14
+
15
+ it('renders banner with role="status" for info variant', async () => {
16
+ const el = await fixture('<bds-notification-banner variant="info">Info</bds-notification-banner>');
17
+ const banner = el.shadowRoot!.querySelector('.banner');
18
+ expect(banner!.getAttribute('role')).toBe('status');
19
+ expect(banner!.getAttribute('aria-live')).toBe('polite');
20
+ cleanup(el);
21
+ });
22
+
23
+ it('renders banner with role="alert" for error variant', async () => {
24
+ const el = await fixture('<bds-notification-banner variant="error">Error</bds-notification-banner>');
25
+ const banner = el.shadowRoot!.querySelector('.banner');
26
+ expect(banner!.getAttribute('role')).toBe('alert');
27
+ expect(banner!.getAttribute('aria-live')).toBe('assertive');
28
+ cleanup(el);
29
+ });
30
+
31
+ it('renders banner with role="alert" for warning variant', async () => {
32
+ const el = await fixture('<bds-notification-banner variant="warning">Warning</bds-notification-banner>');
33
+ const banner = el.shadowRoot!.querySelector('.banner');
34
+ expect(banner!.getAttribute('role')).toBe('alert');
35
+ cleanup(el);
36
+ });
37
+
38
+ it('does not render dismiss button by default', async () => {
39
+ const el = await fixture('<bds-notification-banner>Message</bds-notification-banner>');
40
+ expect(el.shadowRoot!.querySelector('button.dismiss')).toBeNull();
41
+ cleanup(el);
42
+ });
43
+
44
+ it('renders dismiss button when dismissible is set', async () => {
45
+ const el = await fixture('<bds-notification-banner dismissible>Message</bds-notification-banner>');
46
+ const btn = el.shadowRoot!.querySelector('button.dismiss');
47
+ expect(btn).not.toBeNull();
48
+ expect(btn!.getAttribute('aria-label')).toBe('Dismiss notification');
49
+ cleanup(el);
50
+ });
51
+
52
+ it('uses custom dismiss-label when set', async () => {
53
+ const el = await fixture('<bds-notification-banner dismissible dismiss-label="Close banner">Message</bds-notification-banner>');
54
+ const btn = el.shadowRoot!.querySelector('button.dismiss');
55
+ expect(btn!.getAttribute('aria-label')).toBe('Close banner');
56
+ cleanup(el);
57
+ });
58
+
59
+ it('fires bds-dismiss when dismiss button is clicked', async () => {
60
+ const el = await fixture('<bds-notification-banner dismissible>Message</bds-notification-banner>');
61
+ const btn = el.shadowRoot!.querySelector('button.dismiss')!;
62
+ const handler = vi.fn();
63
+ el.addEventListener('bds-dismiss', handler);
64
+ btn.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
65
+ expect(handler).toHaveBeenCalledOnce();
66
+ cleanup(el);
67
+ });
68
+
69
+ it('renders slotted content', async () => {
70
+ const el = await fixture('<bds-notification-banner>Hello world</bds-notification-banner>');
71
+ expect(el.textContent?.trim()).toBe('Hello world');
72
+ cleanup(el);
73
+ });
74
+
75
+ it('reflects variant property updates', async () => {
76
+ const el = await fixture('<bds-notification-banner>Message</bds-notification-banner>') as BdsNotificationBanner;
77
+ el.variant = 'success';
78
+ await el.updateComplete;
79
+ expect(el.getAttribute('variant')).toBe('success');
80
+ cleanup(el);
81
+ });
82
+ });
@@ -0,0 +1,185 @@
1
+ import { LitElement, css, html, nothing } from 'lit';
2
+
3
+ export type NotificationBannerVariant = 'info' | 'success' | 'warning' | 'error';
4
+
5
+ /**
6
+ * `<bds-notification-banner>` — framework-agnostic Notification Banner custom element.
7
+ *
8
+ * Attributes:
9
+ * variant — "info" (default) | "success" | "warning" | "error"
10
+ * dismissible — boolean; shows a dismiss button when present
11
+ * dismiss-label — aria-label for the dismiss button (default: "Dismiss notification")
12
+ *
13
+ * Slots:
14
+ * (default) — banner body content
15
+ * action — optional action area (e.g. a button)
16
+ *
17
+ * Events:
18
+ * bds-dismiss — fired (bubbles, composed) when the dismiss button is clicked
19
+ *
20
+ * @example
21
+ * <bds-notification-banner variant="success">
22
+ * Your changes have been saved.
23
+ * <button slot="action" type="button">Undo</button>
24
+ * </bds-notification-banner>
25
+ */
26
+ export class BdsNotificationBanner extends LitElement {
27
+ static styles = css`
28
+ :host {
29
+ display: block;
30
+ }
31
+
32
+ .banner {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: var(--bds-space_m);
36
+ padding: var(--bds-space_s) var(--bds-space_m);
37
+ background-color: var(--banner_bg, var(--bds-color_blue--subtle));
38
+ color: var(--banner_text, var(--bds-color_on-blue--subtle));
39
+ border-block-end: 3px solid var(--banner_border, var(--bds-color_blue));
40
+ font-size: var(--bds-font_size--body);
41
+ line-height: var(--bds-font_line-height--body);
42
+ inline-size: 100%;
43
+ box-sizing: border-box;
44
+ }
45
+
46
+ :host([variant='info']) .banner {
47
+ --banner_bg: var(--bds-color_blue--subtle);
48
+ --banner_text: var(--bds-color_on-blue--subtle);
49
+ --banner_border: var(--bds-color_blue);
50
+ }
51
+
52
+ :host([variant='success']) .banner {
53
+ --banner_bg: var(--bds-color_success--subtle);
54
+ --banner_text: var(--bds-color_on-success--subtle);
55
+ --banner_border: var(--bds-color_success);
56
+ }
57
+
58
+ :host([variant='warning']) .banner {
59
+ --banner_bg: var(--bds-color_orange--subtle);
60
+ --banner_text: var(--bds-color_on-orange--subtle);
61
+ --banner_border: var(--bds-color_warning);
62
+ }
63
+
64
+ :host([variant='error']) .banner {
65
+ --banner_bg: rgb(from var(--bds-color_error) r g b / 12%);
66
+ --banner_text: var(--bds-color_on-bg);
67
+ --banner_border: var(--bds-color_error);
68
+ }
69
+
70
+ .content {
71
+ flex: 1;
72
+ }
73
+
74
+ .action {
75
+ flex-shrink: 0;
76
+ display: none;
77
+ }
78
+
79
+ .action.has-content {
80
+ display: block;
81
+ }
82
+
83
+ .dismiss {
84
+ all: unset;
85
+ display: flex;
86
+ align-items: center;
87
+ justify-content: center;
88
+ flex-shrink: 0;
89
+ cursor: pointer;
90
+ color: currentcolor;
91
+ border-radius: 50%;
92
+ transition: var(--bds-animation_transition);
93
+ --bdc_color: currentcolor;
94
+ outline: var(--bdc-outline_width) solid var(--bdc_color);
95
+ outline-offset: var(--bdc-outline_offset);
96
+ }
97
+
98
+ .dismiss svg {
99
+ inline-size: 1rem;
100
+ block-size: 1rem;
101
+ }
102
+
103
+ .dismiss:focus-visible {
104
+ outline: var(--bds-outline_default);
105
+ outline-offset: var(--bds-outline_offset);
106
+ }
107
+
108
+ @media (hover: hover) and (pointer: fine) {
109
+ .dismiss:hover {
110
+ opacity: 0.7;
111
+ }
112
+ }
113
+ `;
114
+
115
+ static properties = {
116
+ variant: { type: String, reflect: true },
117
+ dismissible: { type: Boolean, reflect: true },
118
+ dismissLabel: { type: String, attribute: 'dismiss-label' },
119
+ _actionFilled: { state: true },
120
+ };
121
+
122
+ declare variant: NotificationBannerVariant;
123
+ declare dismissible: boolean;
124
+ declare dismissLabel: string;
125
+ declare private _actionFilled: boolean;
126
+
127
+ constructor() {
128
+ super();
129
+ this.variant = 'info';
130
+ this.dismissible = false;
131
+ this.dismissLabel = 'Dismiss notification';
132
+ this._actionFilled = false;
133
+ }
134
+
135
+ private _onActionSlotChange(e: Event) {
136
+ const slot = e.target as HTMLSlotElement;
137
+ this._actionFilled = slot.assignedNodes({ flatten: true }).length > 0;
138
+ }
139
+
140
+ private get _isUrgent() {
141
+ return this.variant === 'error' || this.variant === 'warning';
142
+ }
143
+
144
+ private _handleDismiss() {
145
+ this.dispatchEvent(new CustomEvent('bds-dismiss', { bubbles: true, composed: true }));
146
+ }
147
+
148
+ render() {
149
+ return html`
150
+ <div
151
+ class="banner"
152
+ role=${this._isUrgent ? 'alert' : 'status'}
153
+ aria-live=${this._isUrgent ? 'assertive' : 'polite'}
154
+ aria-atomic="true"
155
+ >
156
+ <div class="content"><slot></slot></div>
157
+ <div class="action ${this._actionFilled ? 'has-content' : ''}">
158
+ <slot name="action" @slotchange=${this._onActionSlotChange}></slot>
159
+ </div>
160
+ ${this.dismissible
161
+ ? html`
162
+ <button
163
+ type="button"
164
+ class="dismiss"
165
+ @click=${this._handleDismiss}
166
+ aria-label=${this.dismissLabel}
167
+ >
168
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
169
+ <path stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12" />
170
+ </svg>
171
+ </button>
172
+ `
173
+ : nothing}
174
+ </div>
175
+ `;
176
+ }
177
+ }
178
+
179
+ customElements.define('bds-notification-banner', BdsNotificationBanner);
180
+
181
+ declare global {
182
+ interface HTMLElementTagNameMap {
183
+ 'bds-notification-banner': BdsNotificationBanner;
184
+ }
185
+ }
@@ -0,0 +1,58 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsProgressCircle } from './bds-progress-circle';
3
+
4
+ describe('bds-progress-circle', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-progress-circle')).toBe(BdsProgressCircle);
7
+ });
8
+
9
+ it('defaults to max=100 and size="medium"', async () => {
10
+ const el = await fixture('<bds-progress-circle value="50" label="Loading"></bds-progress-circle>') as BdsProgressCircle;
11
+ expect(el.max).toBe(100);
12
+ expect(el.getAttribute('size')).toBe('medium');
13
+ cleanup(el);
14
+ });
15
+
16
+ it('renders a progressbar wrapper in shadow DOM', async () => {
17
+ const el = await fixture('<bds-progress-circle value="50" label="Loading"></bds-progress-circle>');
18
+ const wrapper = el.shadowRoot!.querySelector('[role="progressbar"]');
19
+ expect(wrapper).not.toBeNull();
20
+ cleanup(el);
21
+ });
22
+
23
+ it('sets aria-label and aria-valuenow', async () => {
24
+ const el = await fixture('<bds-progress-circle value="75" label="Upload"></bds-progress-circle>');
25
+ const wrapper = el.shadowRoot!.querySelector('[role="progressbar"]');
26
+ expect(wrapper!.getAttribute('aria-label')).toBe('Upload');
27
+ expect(wrapper!.getAttribute('aria-valuenow')).toBe('75');
28
+ cleanup(el);
29
+ });
30
+
31
+ it('renders two SVG circles (track and fill)', async () => {
32
+ const el = await fixture('<bds-progress-circle value="50" label="Loading"></bds-progress-circle>');
33
+ const circles = el.shadowRoot!.querySelectorAll('circle');
34
+ expect(circles.length).toBe(2);
35
+ cleanup(el);
36
+ });
37
+
38
+ it('does not render the value text by default', async () => {
39
+ const el = await fixture('<bds-progress-circle value="50" label="Loading"></bds-progress-circle>');
40
+ expect(el.shadowRoot!.querySelector('.value')).toBeNull();
41
+ cleanup(el);
42
+ });
43
+
44
+ it('renders percentage text when show-value is set', async () => {
45
+ const el = await fixture('<bds-progress-circle value="75" label="Loading" show-value></bds-progress-circle>');
46
+ const value = el.shadowRoot!.querySelector('.value');
47
+ expect(value).not.toBeNull();
48
+ expect(value!.textContent).toBe('75%');
49
+ cleanup(el);
50
+ });
51
+
52
+ it('SVG has aria-hidden="true"', async () => {
53
+ const el = await fixture('<bds-progress-circle value="50" label="Loading"></bds-progress-circle>');
54
+ const svg = el.shadowRoot!.querySelector('svg');
55
+ expect(svg!.getAttribute('aria-hidden')).toBe('true');
56
+ cleanup(el);
57
+ });
58
+ });