@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,142 @@
1
+ import { LitElement, css, html, nothing } from 'lit';
2
+
3
+ const SIZE_PX = { small: 40, medium: 64, large: 96 } as const;
4
+ const STROKE_WIDTH = 4;
5
+
6
+ /**
7
+ * `<bds-progress-circle>` — framework-agnostic circular Progress custom element.
8
+ *
9
+ * Attributes:
10
+ * value — current progress value (required)
11
+ * max — maximum value (default: 100)
12
+ * label — accessible label for the progress bar
13
+ * show-value — boolean; when set, displays the percentage in the centre
14
+ * size — "small" | "medium" (default) | "large"
15
+ *
16
+ * @example
17
+ * <bds-progress-circle value="75" label="Loading" show-value></bds-progress-circle>
18
+ */
19
+ export class BdsProgressCircle extends LitElement {
20
+ static styles = css`
21
+ :host {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ position: relative;
26
+ }
27
+
28
+ .svg {
29
+ display: block;
30
+ }
31
+
32
+ .track {
33
+ stroke: var(--bds-color_bg--subtle);
34
+ }
35
+
36
+ .fill {
37
+ stroke: var(--progressCircle_color-active, var(--bdc-color_active));
38
+ transition: stroke-dashoffset var(--bds-animation_transition-duration) var(--bds-animation_easing);
39
+ }
40
+
41
+ .value {
42
+ position: absolute;
43
+ font-size: var(--bds-font_size--body--s);
44
+ font-weight: var(--bds-font_weight--semibold);
45
+ font-variant-numeric: tabular-nums;
46
+ color: var(--bds-color_on-bg);
47
+ line-height: 1;
48
+ }
49
+
50
+ :host([size='small']) .value { font-size: 0.5rem; }
51
+ :host([size='large']) .value { font-size: var(--bds-font_size--body); }
52
+
53
+ @media (prefers-reduced-motion: reduce) {
54
+ .fill {
55
+ transition: none;
56
+ }
57
+ }
58
+ `;
59
+
60
+ static properties = {
61
+ value: { type: Number },
62
+ max: { type: Number },
63
+ label: { type: String },
64
+ showValue: { type: Boolean, attribute: 'show-value', reflect: true },
65
+ size: { type: String, reflect: true },
66
+ };
67
+
68
+ declare value: number;
69
+ declare max: number;
70
+ declare label: string;
71
+ declare showValue: boolean;
72
+ declare size: 'small' | 'medium' | 'large';
73
+
74
+ constructor() {
75
+ super();
76
+ this.value = 0;
77
+ this.max = 100;
78
+ this.label = '';
79
+ this.showValue = false;
80
+ this.size = 'medium';
81
+ }
82
+
83
+ render() {
84
+ const percentage = Math.min(100, Math.max(0, (this.value / this.max) * 100));
85
+ const px = SIZE_PX[this.size] ?? SIZE_PX.medium;
86
+ const radius = (px - STROKE_WIDTH * 2) / 2;
87
+ const circumference = 2 * Math.PI * radius;
88
+ const offset = circumference - (percentage / 100) * circumference;
89
+ const cx = px / 2;
90
+
91
+ return html`
92
+ <div
93
+ role="progressbar"
94
+ aria-label=${this.label}
95
+ aria-valuenow=${this.value}
96
+ aria-valuemin="0"
97
+ aria-valuemax=${this.max}
98
+ style="position: relative; display: inline-flex; align-items: center; justify-content: center;"
99
+ >
100
+ <svg
101
+ class="svg"
102
+ width=${px}
103
+ height=${px}
104
+ viewBox="0 0 ${px} ${px}"
105
+ aria-hidden="true"
106
+ >
107
+ <circle
108
+ class="track"
109
+ cx=${cx}
110
+ cy=${cx}
111
+ r=${radius}
112
+ stroke-width=${STROKE_WIDTH}
113
+ fill="none"
114
+ />
115
+ <circle
116
+ class="fill"
117
+ cx=${cx}
118
+ cy=${cx}
119
+ r=${radius}
120
+ stroke-width=${STROKE_WIDTH}
121
+ fill="none"
122
+ stroke-dasharray=${circumference}
123
+ stroke-dashoffset=${offset}
124
+ stroke-linecap="round"
125
+ transform="rotate(-90 ${cx} ${cx})"
126
+ />
127
+ </svg>
128
+ ${this.showValue
129
+ ? html`<span class="value" aria-hidden="true">${Math.round(percentage)}%</span>`
130
+ : nothing}
131
+ </div>
132
+ `;
133
+ }
134
+ }
135
+
136
+ customElements.define('bds-progress-circle', BdsProgressCircle);
137
+
138
+ declare global {
139
+ interface HTMLElementTagNameMap {
140
+ 'bds-progress-circle': BdsProgressCircle;
141
+ }
142
+ }
@@ -0,0 +1,66 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsProgress } from './bds-progress';
3
+
4
+ describe('bds-progress', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-progress')).toBe(BdsProgress);
7
+ });
8
+
9
+ it('defaults to max=100 and size="medium"', async () => {
10
+ const el = await fixture('<bds-progress value="50" label="Loading"></bds-progress>') as BdsProgress;
11
+ expect(el.max).toBe(100);
12
+ expect(el.getAttribute('size')).toBe('medium');
13
+ cleanup(el);
14
+ });
15
+
16
+ it('renders a progressbar track in shadow DOM', async () => {
17
+ const el = await fixture('<bds-progress value="50" label="Loading"></bds-progress>');
18
+ const track = el.shadowRoot!.querySelector('.track');
19
+ expect(track).not.toBeNull();
20
+ expect(track!.getAttribute('role')).toBe('progressbar');
21
+ cleanup(el);
22
+ });
23
+
24
+ it('sets aria-label, aria-valuenow, aria-valuemax on the track', async () => {
25
+ const el = await fixture('<bds-progress value="60" max="200" label="Upload"></bds-progress>');
26
+ const track = el.shadowRoot!.querySelector('.track');
27
+ expect(track!.getAttribute('aria-label')).toBe('Upload');
28
+ expect(track!.getAttribute('aria-valuenow')).toBe('60');
29
+ expect(track!.getAttribute('aria-valuemax')).toBe('200');
30
+ cleanup(el);
31
+ });
32
+
33
+ it('does not render the label row by default', async () => {
34
+ const el = await fixture('<bds-progress value="50" label="Loading"></bds-progress>');
35
+ expect(el.shadowRoot!.querySelector('.label-row')).toBeNull();
36
+ cleanup(el);
37
+ });
38
+
39
+ it('renders label row when show-label is set', async () => {
40
+ const el = await fixture('<bds-progress value="50" label="Upload" show-label></bds-progress>');
41
+ const labelRow = el.shadowRoot!.querySelector('.label-row');
42
+ expect(labelRow).not.toBeNull();
43
+ expect(labelRow!.querySelector('.label')!.textContent).toBe('Upload');
44
+ cleanup(el);
45
+ });
46
+
47
+ it('clamps percentage to 100 for value > max', async () => {
48
+ const el = await fixture('<bds-progress value="150" label="Done" show-label></bds-progress>');
49
+ const valueSpan = el.shadowRoot!.querySelector('.value');
50
+ expect(valueSpan!.textContent).toBe('100%');
51
+ cleanup(el);
52
+ });
53
+
54
+ it('clamps percentage to 0 for negative value', async () => {
55
+ const el = await fixture('<bds-progress value="-10" label="Done" show-label></bds-progress>');
56
+ const valueSpan = el.shadowRoot!.querySelector('.value');
57
+ expect(valueSpan!.textContent).toBe('0%');
58
+ cleanup(el);
59
+ });
60
+
61
+ it('reflects size attribute', async () => {
62
+ const el = await fixture('<bds-progress value="50" label="Loading" size="large"></bds-progress>') as BdsProgress;
63
+ expect(el.getAttribute('size')).toBe('large');
64
+ cleanup(el);
65
+ });
66
+ });
@@ -0,0 +1,127 @@
1
+ import { LitElement, css, html, nothing } from 'lit';
2
+
3
+ /**
4
+ * `<bds-progress>` — framework-agnostic Progress bar custom element.
5
+ *
6
+ * Attributes:
7
+ * value — current progress value (required)
8
+ * max — maximum value (default: 100)
9
+ * label — accessible label for the progress bar
10
+ * show-label — boolean; when set, displays the label and percentage above the bar
11
+ * size — "small" | "medium" (default) | "large"
12
+ *
13
+ * @example
14
+ * <bds-progress value="60" label="Upload progress" show-label></bds-progress>
15
+ */
16
+ export class BdsProgress extends LitElement {
17
+ static styles = css`
18
+ :host {
19
+ display: block;
20
+ }
21
+
22
+ .container {
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--bds-space_xxs);
26
+ inline-size: 100%;
27
+ }
28
+
29
+ .label-row {
30
+ display: flex;
31
+ justify-content: space-between;
32
+ align-items: baseline;
33
+ font-size: var(--bds-font_size--body--s);
34
+ color: var(--bds-color_on-bg);
35
+ }
36
+
37
+ .value {
38
+ font-variant-numeric: tabular-nums;
39
+ }
40
+
41
+ .track {
42
+ inline-size: 100%;
43
+ block-size: var(--progress_height, var(--bds-space_xs));
44
+ background-color: var(--bds-color_bg--subtle);
45
+ border-radius: 999px;
46
+ overflow: hidden;
47
+ }
48
+
49
+ :host([size='small']) .track { --progress_height: var(--bds-space_xxs); }
50
+ :host([size='medium']) .track { --progress_height: var(--bds-space_xs); }
51
+ :host([size='large']) .track { --progress_height: var(--bds-space_s); }
52
+
53
+ .fill {
54
+ block-size: 100%;
55
+ background-color: var(--progress_color-active, var(--bdc-color_active));
56
+ border-radius: 999px;
57
+ transition: inline-size var(--bds-animation_transition-duration) var(--bds-animation_easing);
58
+ }
59
+
60
+ @media (prefers-reduced-motion: reduce) {
61
+ .fill {
62
+ transition: none;
63
+ }
64
+ }
65
+ `;
66
+
67
+ static properties = {
68
+ value: { type: Number },
69
+ max: { type: Number },
70
+ label: { type: String },
71
+ showLabel: { type: Boolean, attribute: 'show-label', reflect: true },
72
+ size: { type: String, reflect: true },
73
+ };
74
+
75
+ declare value: number;
76
+ declare max: number;
77
+ declare label: string;
78
+ declare showLabel: boolean;
79
+ declare size: 'small' | 'medium' | 'large';
80
+
81
+ constructor() {
82
+ super();
83
+ this.value = 0;
84
+ this.max = 100;
85
+ this.label = '';
86
+ this.showLabel = false;
87
+ this.size = 'medium';
88
+ }
89
+
90
+ private get _percentage(): number {
91
+ return Math.min(100, Math.max(0, (this.value / this.max) * 100));
92
+ }
93
+
94
+ render() {
95
+ const pct = this._percentage;
96
+ return html`
97
+ <div class="container">
98
+ ${this.showLabel
99
+ ? html`
100
+ <div class="label-row">
101
+ <span class="label">${this.label}</span>
102
+ <span class="value">${Math.round(pct)}%</span>
103
+ </div>
104
+ `
105
+ : nothing}
106
+ <div
107
+ class="track"
108
+ role="progressbar"
109
+ aria-label=${this.label}
110
+ aria-valuenow=${this.value}
111
+ aria-valuemin="0"
112
+ aria-valuemax=${this.max}
113
+ >
114
+ <div class="fill" style="inline-size: ${pct}%"></div>
115
+ </div>
116
+ </div>
117
+ `;
118
+ }
119
+ }
120
+
121
+ customElements.define('bds-progress', BdsProgress);
122
+
123
+ declare global {
124
+ interface HTMLElementTagNameMap {
125
+ 'bds-progress': BdsProgress;
126
+ }
127
+ }
@@ -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
+ });