@boostdev/design-system-components 1.1.2 → 1.2.1

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 (213) hide show
  1. package/AGENTS.md +38 -14
  2. package/dist/client.cjs +85 -103
  3. package/dist/client.css +610 -601
  4. package/dist/client.d.cts +13 -16
  5. package/dist/client.d.ts +13 -16
  6. package/dist/client.js +95 -113
  7. package/dist/index.cjs +85 -103
  8. package/dist/index.css +610 -601
  9. package/dist/index.d.cts +13 -16
  10. package/dist/index.d.ts +13 -16
  11. package/dist/index.js +95 -113
  12. package/dist/native/index.cjs +9 -8
  13. package/dist/native/index.d.cts +1 -1
  14. package/dist/native/index.d.ts +1 -1
  15. package/dist/native/index.js +9 -8
  16. package/dist/web-components/{chunk-K5CE6HSB.js → chunk-2FGATTGT.js} +79 -17
  17. package/dist/web-components/{chunk-O4UFI2RX.js → chunk-3REOIRDW.js} +13 -11
  18. package/dist/web-components/{chunk-5IPHEONG.js → chunk-6I2DBFQ7.js} +1 -0
  19. package/dist/web-components/{chunk-VM3YHGQ2.js → chunk-AJSXNDAP.js} +1 -2
  20. package/dist/web-components/{chunk-LSC4N56Q.js → chunk-BX3IPVOB.js} +1 -2
  21. package/dist/web-components/{chunk-CAPI25CU.js → chunk-DZRSJGPB.js} +3 -3
  22. package/dist/web-components/{chunk-VSS5ITNE.js → chunk-EBJM3VD4.js} +1 -3
  23. package/dist/web-components/{chunk-O7I63SQX.js → chunk-HHHRF2PS.js} +6 -12
  24. package/dist/web-components/{chunk-OTX4COSV.js → chunk-JUKB3BUP.js} +3 -4
  25. package/dist/web-components/{chunk-IQGLIQSO.js → chunk-N6IMUOY4.js} +2 -5
  26. package/dist/web-components/{chunk-CJ5ZOO42.js → chunk-OCODKRVZ.js} +68 -44
  27. package/dist/web-components/{chunk-QDRH46TF.js → chunk-QLZ2362S.js} +1 -3
  28. package/dist/web-components/{chunk-IK3PTFZ4.js → chunk-T6AETZRP.js} +1 -3
  29. package/dist/web-components/{chunk-4BX6AWXX.js → chunk-X3FKVHLK.js} +2 -4
  30. package/dist/web-components/{chunk-A67LCT7C.js → chunk-XNA6WTXG.js} +3 -4
  31. package/dist/web-components/{chunk-XIENXR7H.js → chunk-YRXCVKHV.js} +1 -3
  32. package/dist/web-components/{chunk-6T3RKUKG.js → chunk-ZTC6GRP7.js} +1 -2
  33. package/dist/web-components/globals.js +1 -1
  34. package/dist/web-components/index.d.ts +571 -1
  35. package/dist/web-components/index.js +1515 -18
  36. package/dist/web-components/interaction/bds-accordion.js +1 -1
  37. package/dist/web-components/interaction/bds-button.d.ts +2 -2
  38. package/dist/web-components/interaction/bds-button.js +1 -1
  39. package/dist/web-components/interaction/bds-collapsible.js +1 -1
  40. package/dist/web-components/interaction/bds-dialog.js +1 -1
  41. package/dist/web-components/interaction/bds-drawer.js +1 -1
  42. package/dist/web-components/interaction/form/bds-checkbox.js +1 -1
  43. package/dist/web-components/interaction/form/bds-combobox.js +1 -1
  44. package/dist/web-components/interaction/form/bds-number-input.js +1 -1
  45. package/dist/web-components/interaction/form/bds-radio.js +1 -1
  46. package/dist/web-components/interaction/form/bds-segmented-control.d.ts +20 -3
  47. package/dist/web-components/interaction/form/bds-segmented-control.js +1 -1
  48. package/dist/web-components/interaction/form/bds-select.js +1 -1
  49. package/dist/web-components/interaction/form/bds-switch.js +1 -1
  50. package/dist/web-components/interaction/form/bds-textarea.js +1 -1
  51. package/dist/web-components/ui/bds-alert.js +1 -1
  52. package/dist/web-components/ui/bds-card.js +1 -1
  53. package/dist/web-components/ui/bds-notification-banner.js +1 -1
  54. package/package.json +2 -2
  55. package/src/components/interaction/Button/Button.module.css +12 -11
  56. package/src/components/interaction/Button/Button.native.tsx +10 -9
  57. package/src/components/interaction/Button/Button.tsx +1 -1
  58. package/src/components/interaction/Command/Command.module.css +3 -7
  59. package/src/components/interaction/Dialog/Dialog.module.css +7 -5
  60. package/src/components/interaction/Drawer/Drawer.module.css +48 -25
  61. package/src/components/interaction/Drawer/Drawer.tsx +21 -17
  62. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +1 -3
  63. package/src/components/interaction/Popover/Popover.module.css +1 -3
  64. package/src/components/interaction/form/Checkbox/Checkbox.module.css +1 -3
  65. package/src/components/interaction/form/Combobox/Combobox.module.css +2 -6
  66. package/src/components/interaction/form/FormInput/FormInput.module.css +1 -3
  67. package/src/components/interaction/form/NumberInput/NumberInput.module.css +16 -4
  68. package/src/components/interaction/form/Radio/Radio.module.css +6 -16
  69. package/src/components/interaction/form/SegmentedControl/SegmentedControl.mdx +74 -25
  70. package/src/components/interaction/form/SegmentedControl/SegmentedControl.module.css +65 -36
  71. package/src/components/interaction/form/SegmentedControl/SegmentedControl.spec.tsx +127 -56
  72. package/src/components/interaction/form/SegmentedControl/SegmentedControl.stories.tsx +89 -72
  73. package/src/components/interaction/form/SegmentedControl/SegmentedControl.tsx +40 -62
  74. package/src/components/interaction/form/SegmentedControl/index.ts +1 -1
  75. package/src/components/interaction/form/Select/Select.module.css +2 -4
  76. package/src/components/interaction/form/Switch/Switch.module.css +5 -7
  77. package/src/components/interaction/form/Switch/Switch.native.spec.tsx +9 -9
  78. package/src/components/interaction/form/Switch/Switch.native.tsx +2 -2
  79. package/src/components/interaction/form/Textarea/Textarea.module.css +1 -3
  80. package/src/components/interaction/form/atoms/InputContainer.stories.tsx +64 -0
  81. package/src/components/interaction/form/atoms/Label.stories.tsx +33 -0
  82. package/src/components/interaction/form/atoms/Message.stories.tsx +33 -0
  83. package/src/components/layout/Card/Card.module.css +1 -3
  84. package/src/components/ui/Accordion/Accordion.module.css +1 -2
  85. package/src/components/ui/Alert/Alert.module.css +1 -2
  86. package/src/components/ui/Calendar/Calendar.module.css +2 -5
  87. package/src/components/ui/Carousel/Carousel.module.css +1 -3
  88. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +1 -2
  89. package/src/components/ui/Pagination/Pagination.module.css +1 -2
  90. package/src/components/ui/SkipLink/SkipLink.module.css +1 -2
  91. package/src/components/ui/Table/Table.module.css +1 -2
  92. package/src/css/bdc.css +16 -11
  93. package/src/index.ts +1 -1
  94. package/src/stories/Introduction.mdx +2 -1
  95. package/src/web-components/globals.ts +3 -3
  96. package/src/web-components/index.ts +12 -0
  97. package/src/web-components/interaction/BdsAccordion.mdx +103 -0
  98. package/src/web-components/interaction/BdsAccordion.stories.tsx +94 -0
  99. package/src/web-components/interaction/BdsCollapsible.mdx +116 -0
  100. package/src/web-components/interaction/BdsCollapsible.stories.tsx +68 -0
  101. package/src/web-components/interaction/BdsDialog.mdx +110 -0
  102. package/src/web-components/interaction/BdsDialog.stories.tsx +157 -0
  103. package/src/web-components/interaction/BdsDrawer.mdx +108 -0
  104. package/src/web-components/interaction/BdsDrawer.stories.tsx +196 -0
  105. package/src/web-components/interaction/BdsDropdownMenu.mdx +108 -0
  106. package/src/web-components/interaction/BdsDropdownMenu.stories.tsx +91 -0
  107. package/src/web-components/interaction/BdsSkipLink.mdx +90 -0
  108. package/src/web-components/interaction/BdsSkipLink.stories.tsx +68 -0
  109. package/src/web-components/interaction/BdsTabs.mdx +117 -0
  110. package/src/web-components/interaction/BdsTabs.stories.tsx +94 -0
  111. package/src/web-components/interaction/BdsTooltip.mdx +104 -0
  112. package/src/web-components/interaction/BdsTooltip.stories.tsx +87 -0
  113. package/src/web-components/interaction/bds-accordion.ts +1 -2
  114. package/src/web-components/interaction/bds-button.ts +15 -13
  115. package/src/web-components/interaction/bds-collapsible.ts +1 -0
  116. package/src/web-components/interaction/bds-dialog.ts +2 -5
  117. package/src/web-components/interaction/bds-drawer.ts +70 -44
  118. package/src/web-components/interaction/bds-dropdown-menu-item.ts +124 -0
  119. package/src/web-components/interaction/bds-dropdown-menu.spec.ts +102 -0
  120. package/src/web-components/interaction/bds-dropdown-menu.ts +200 -0
  121. package/src/web-components/interaction/form/BdsCheckbox.mdx +76 -0
  122. package/src/web-components/interaction/form/BdsCheckbox.stories.tsx +72 -0
  123. package/src/web-components/interaction/form/BdsCheckboxGroup.mdx +70 -0
  124. package/src/web-components/interaction/form/BdsCheckboxGroup.stories.tsx +81 -0
  125. package/src/web-components/interaction/form/BdsCombobox.mdx +71 -0
  126. package/src/web-components/interaction/form/BdsCombobox.stories.tsx +93 -0
  127. package/src/web-components/interaction/form/BdsFileInput.mdx +79 -0
  128. package/src/web-components/interaction/form/BdsFileInput.stories.tsx +69 -0
  129. package/src/web-components/interaction/form/BdsFormInput.mdx +98 -0
  130. package/src/web-components/interaction/form/BdsFormInput.stories.tsx +139 -0
  131. package/src/web-components/interaction/form/BdsNumberInput.mdx +70 -0
  132. package/src/web-components/interaction/form/BdsNumberInput.stories.tsx +68 -0
  133. package/src/web-components/interaction/form/BdsRadio.mdx +72 -0
  134. package/src/web-components/interaction/form/BdsRadio.stories.tsx +58 -0
  135. package/src/web-components/interaction/form/BdsRadioGroup.mdx +73 -0
  136. package/src/web-components/interaction/form/BdsRadioGroup.stories.tsx +88 -0
  137. package/src/web-components/interaction/form/BdsSegmentedControl.mdx +95 -0
  138. package/src/web-components/interaction/form/BdsSegmentedControl.stories.tsx +102 -0
  139. package/src/web-components/interaction/form/BdsSelect.mdx +76 -0
  140. package/src/web-components/interaction/form/BdsSelect.stories.tsx +84 -0
  141. package/src/web-components/interaction/form/BdsSlider.mdx +77 -0
  142. package/src/web-components/interaction/form/BdsSlider.stories.tsx +68 -0
  143. package/src/web-components/interaction/form/BdsSwitch.mdx +72 -0
  144. package/src/web-components/interaction/form/BdsSwitch.stories.tsx +58 -0
  145. package/src/web-components/interaction/form/BdsTextarea.mdx +73 -0
  146. package/src/web-components/interaction/form/BdsTextarea.stories.tsx +70 -0
  147. package/src/web-components/interaction/form/bds-checkbox-group.spec.ts +55 -0
  148. package/src/web-components/interaction/form/bds-checkbox-group.ts +117 -0
  149. package/src/web-components/interaction/form/bds-checkbox.ts +1 -3
  150. package/src/web-components/interaction/form/bds-combobox.ts +1 -3
  151. package/src/web-components/interaction/form/bds-form-input.spec.ts +83 -0
  152. package/src/web-components/interaction/form/bds-form-input.ts +268 -0
  153. package/src/web-components/interaction/form/bds-number-input.ts +3 -4
  154. package/src/web-components/interaction/form/bds-radio-group.spec.ts +62 -0
  155. package/src/web-components/interaction/form/bds-radio-group.ts +142 -0
  156. package/src/web-components/interaction/form/bds-radio.ts +6 -12
  157. package/src/web-components/interaction/form/bds-segmented-control.ts +96 -20
  158. package/src/web-components/interaction/form/bds-select.ts +2 -4
  159. package/src/web-components/interaction/form/bds-switch.ts +3 -4
  160. package/src/web-components/interaction/form/bds-textarea.ts +1 -3
  161. package/src/web-components/ui/BdsAvatar.mdx +76 -0
  162. package/src/web-components/ui/BdsAvatar.stories.tsx +48 -0
  163. package/src/web-components/ui/BdsBreadcrumb.mdx +66 -0
  164. package/src/web-components/ui/BdsBreadcrumb.stories.tsx +49 -0
  165. package/src/web-components/ui/BdsButtonGroup.mdx +69 -0
  166. package/src/web-components/ui/BdsButtonGroup.stories.tsx +66 -0
  167. package/src/web-components/ui/BdsCalendar.mdx +93 -0
  168. package/src/web-components/ui/BdsCalendar.stories.tsx +63 -0
  169. package/src/web-components/ui/BdsCard.mdx +88 -0
  170. package/src/web-components/ui/BdsCard.stories.tsx +67 -0
  171. package/src/web-components/ui/BdsCarousel.mdx +83 -0
  172. package/src/web-components/ui/BdsCarousel.stories.tsx +96 -0
  173. package/src/web-components/ui/BdsDescriptionList.mdx +64 -0
  174. package/src/web-components/ui/BdsDescriptionList.stories.tsx +64 -0
  175. package/src/web-components/ui/BdsIconWrapper.mdx +70 -0
  176. package/src/web-components/ui/BdsIconWrapper.stories.tsx +81 -0
  177. package/src/web-components/ui/BdsLink.mdx +79 -0
  178. package/src/web-components/ui/BdsLink.stories.tsx +63 -0
  179. package/src/web-components/ui/BdsLoading.mdx +58 -0
  180. package/src/web-components/ui/BdsLoading.stories.tsx +35 -0
  181. package/src/web-components/ui/BdsNotificationBanner.mdx +85 -0
  182. package/src/web-components/ui/BdsNotificationBanner.stories.tsx +84 -0
  183. package/src/web-components/ui/BdsPagination.mdx +74 -0
  184. package/src/web-components/ui/BdsPagination.stories.tsx +36 -0
  185. package/src/web-components/ui/BdsProgress.mdx +70 -0
  186. package/src/web-components/ui/BdsProgress.stories.tsx +96 -0
  187. package/src/web-components/ui/BdsProgressCircle.mdx +70 -0
  188. package/src/web-components/ui/BdsProgressCircle.stories.tsx +57 -0
  189. package/src/web-components/ui/BdsRating.mdx +61 -0
  190. package/src/web-components/ui/BdsRating.stories.tsx +45 -0
  191. package/src/web-components/ui/BdsSectionHeader.mdx +72 -0
  192. package/src/web-components/ui/BdsSectionHeader.stories.tsx +54 -0
  193. package/src/web-components/ui/BdsSeparator.mdx +77 -0
  194. package/src/web-components/ui/BdsSeparator.stories.tsx +55 -0
  195. package/src/web-components/ui/BdsSkeleton.mdx +61 -0
  196. package/src/web-components/ui/BdsSkeleton.stories.tsx +50 -0
  197. package/src/web-components/ui/BdsTable.mdx +81 -0
  198. package/src/web-components/ui/BdsTable.stories.tsx +83 -0
  199. package/src/web-components/ui/BdsTypography.mdx +74 -0
  200. package/src/web-components/ui/BdsTypography.stories.tsx +52 -0
  201. package/src/web-components/ui/bds-alert.ts +1 -2
  202. package/src/web-components/ui/bds-button-group.spec.ts +40 -0
  203. package/src/web-components/ui/bds-button-group.ts +78 -0
  204. package/src/web-components/ui/bds-calendar.spec.ts +91 -0
  205. package/src/web-components/ui/bds-calendar.ts +427 -0
  206. package/src/web-components/ui/bds-card.ts +1 -3
  207. package/src/web-components/ui/bds-carousel.spec.ts +64 -0
  208. package/src/web-components/ui/bds-carousel.ts +296 -0
  209. package/src/web-components/ui/bds-notification-banner.ts +1 -2
  210. package/src/web-components/ui/bds-pagination.spec.ts +67 -0
  211. package/src/web-components/ui/bds-pagination.ts +197 -0
  212. package/src/web-components/ui/bds-table.spec.ts +45 -0
  213. package/src/web-components/ui/bds-table.ts +96 -0
@@ -0,0 +1,296 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ /**
4
+ * `<bds-carousel>` — scroll-snap based carousel/slider.
5
+ *
6
+ * Attributes:
7
+ * current-index — active slide index (0-based, default: 0)
8
+ * indicators — show dot indicators
9
+ * loop — wrap from last slide back to first and vice versa
10
+ *
11
+ * Slots:
12
+ * (default) — slide content elements (each direct child = one slide)
13
+ * prev — custom previous button (renders a default button if omitted)
14
+ * next — custom next button (renders a default button if omitted)
15
+ *
16
+ * Events:
17
+ * bds-slide-change — fired when active slide changes; `detail: { index: number }`
18
+ *
19
+ * @example
20
+ * <bds-carousel indicators loop>
21
+ * <div>Slide 1</div>
22
+ * <div>Slide 2</div>
23
+ * <div>Slide 3</div>
24
+ * </bds-carousel>
25
+ */
26
+ export class BdsCarousel extends LitElement {
27
+ static styles = css`
28
+ :host {
29
+ display: block;
30
+ position: relative;
31
+ }
32
+
33
+ .track {
34
+ display: flex;
35
+ overflow-inline: auto;
36
+ scroll-snap-type: inline mandatory;
37
+ scroll-behavior: smooth;
38
+ scrollbar-width: none;
39
+ -ms-overflow-style: none;
40
+ }
41
+
42
+ .track::-webkit-scrollbar {
43
+ display: none;
44
+ }
45
+
46
+ ::slotted(*) {
47
+ flex-shrink: 0;
48
+ inline-size: 100%;
49
+ scroll-snap-align: start;
50
+ }
51
+
52
+ .controls {
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: space-between;
56
+ margin-block-start: var(--bds-space_s);
57
+ }
58
+
59
+ .nav-btn {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ inline-size: 2.5rem;
64
+ block-size: 2.5rem;
65
+ border: none;
66
+ border-radius: var(--bds-border_radius--full, 9999px);
67
+ background-color: var(--bds-color_bg--subtle);
68
+ color: var(--bds-color_on-bg);
69
+ cursor: pointer;
70
+ transition: var(--bds-animation_transition);
71
+ }
72
+
73
+ .nav-btn:focus-visible {
74
+ outline: var(--bds-outline_default);
75
+ outline-offset: var(--bds-outline_offset);
76
+ }
77
+
78
+ @media (hover: hover) and (pointer: fine) {
79
+ .nav-btn:hover {
80
+ background-color: var(--bds-color_bg--strong);
81
+ }
82
+ }
83
+
84
+ .nav-btn:disabled {
85
+ opacity: 0.4;
86
+ cursor: not-allowed;
87
+ }
88
+
89
+ .nav-btn svg {
90
+ inline-size: 1.25rem;
91
+ block-size: 1.25rem;
92
+ }
93
+
94
+ .indicators {
95
+ display: flex;
96
+ gap: var(--bds-space_xxs);
97
+ align-items: center;
98
+ justify-content: center;
99
+ flex: 1;
100
+ }
101
+
102
+ .dot {
103
+ inline-size: 0.5rem;
104
+ block-size: 0.5rem;
105
+ border-radius: 50%;
106
+ border: none;
107
+ padding: 0;
108
+ background-color: var(--bds-color_bg--strong);
109
+ cursor: pointer;
110
+ transition: var(--bds-animation_transition);
111
+ }
112
+
113
+ .dot.--active {
114
+ background-color: var(--bds-color_interactive);
115
+ inline-size: 1rem;
116
+ border-radius: 0.25rem;
117
+ }
118
+
119
+ .dot:focus-visible {
120
+ outline: var(--bds-outline_default);
121
+ outline-offset: var(--bds-outline_offset);
122
+ }
123
+
124
+ @media (prefers-reduced-motion: reduce) {
125
+ .track {
126
+ scroll-behavior: auto;
127
+ }
128
+ }
129
+ `;
130
+
131
+ static properties = {
132
+ currentIndex: { type: Number, attribute: 'current-index', reflect: true },
133
+ indicators: { type: Boolean, reflect: true },
134
+ loop: { type: Boolean, reflect: true },
135
+ };
136
+
137
+ declare currentIndex: number;
138
+ declare indicators: boolean;
139
+ declare loop: boolean;
140
+
141
+ private _slideCount = 0;
142
+ private _io: IntersectionObserver | null = null;
143
+
144
+ constructor() {
145
+ super();
146
+ this.currentIndex = 0;
147
+ this.indicators = false;
148
+ this.loop = false;
149
+ }
150
+
151
+ private get _track(): HTMLElement | null {
152
+ return this.shadowRoot?.querySelector('.track') ?? null;
153
+ }
154
+
155
+ connectedCallback() {
156
+ super.connectedCallback();
157
+ // Count slides from light DOM children
158
+ this._updateSlideCount();
159
+ }
160
+
161
+ disconnectedCallback() {
162
+ super.disconnectedCallback();
163
+ this._io?.disconnect();
164
+ this._io = null;
165
+ }
166
+
167
+ private _updateSlideCount() {
168
+ this._slideCount = this.children.length;
169
+ this.requestUpdate();
170
+ }
171
+
172
+ private _goTo(index: number) {
173
+ const count = this._slideCount;
174
+ if (count === 0) return;
175
+
176
+ let next = index;
177
+ if (this.loop) {
178
+ next = ((index % count) + count) % count;
179
+ } else {
180
+ next = Math.max(0, Math.min(index, count - 1));
181
+ }
182
+
183
+ if (next === this.currentIndex && index === next) return;
184
+
185
+ this.currentIndex = next;
186
+
187
+ const track = this._track;
188
+ const slides = track?.querySelector('slot')?.assignedElements() ?? [];
189
+ const target = slides[next] as HTMLElement | undefined;
190
+ if (target && typeof target.scrollIntoView === 'function') {
191
+ target.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
192
+ }
193
+
194
+ this.dispatchEvent(new CustomEvent('bds-slide-change', {
195
+ detail: { index: next },
196
+ bubbles: true,
197
+ composed: true,
198
+ }));
199
+ }
200
+
201
+ private _onSlotChange() {
202
+ this._updateSlideCount();
203
+ // Set up IntersectionObserver on the default slot's items
204
+ this._io?.disconnect();
205
+ const track = this._track;
206
+ if (!track) return;
207
+
208
+ const slot = track.querySelector('slot') as HTMLSlotElement | null;
209
+ const slides = slot?.assignedElements() ?? [];
210
+
211
+ if (typeof IntersectionObserver === 'undefined') return;
212
+
213
+ this._io = new IntersectionObserver(
214
+ entries => {
215
+ const visible = entries.find(e => e.isIntersecting && e.intersectionRatio >= 0.5);
216
+ if (visible) {
217
+ const idx = slides.indexOf(visible.target as Element);
218
+ if (idx !== -1 && idx !== this.currentIndex) {
219
+ this.currentIndex = idx;
220
+ this.requestUpdate();
221
+ }
222
+ }
223
+ },
224
+ { root: track, threshold: 0.5 },
225
+ );
226
+
227
+ slides.forEach(slide => this._io!.observe(slide));
228
+ }
229
+
230
+ render() {
231
+ const atStart = this.currentIndex <= 0;
232
+ const atEnd = this.currentIndex >= this._slideCount - 1;
233
+ const showPrev = this.loop || !atStart;
234
+ const showNext = this.loop || !atEnd;
235
+
236
+ return html`
237
+ <div class="track" aria-label="Carousel" role="region">
238
+ <slot @slotchange=${this._onSlotChange}></slot>
239
+ </div>
240
+ <div class="controls">
241
+ <slot name="prev">
242
+ <button
243
+ class="nav-btn"
244
+ type="button"
245
+ aria-label="Previous slide"
246
+ ?disabled=${!showPrev}
247
+ @click=${() => this._goTo(this.currentIndex - 1)}
248
+ >
249
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
250
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
251
+ </svg>
252
+ </button>
253
+ </slot>
254
+
255
+ ${this.indicators
256
+ ? html`
257
+ <div class="indicators" role="tablist" aria-label="Slides">
258
+ ${Array.from({ length: this._slideCount }, (_, i) => html`
259
+ <button
260
+ class="dot ${i === this.currentIndex ? '--active' : ''}"
261
+ type="button"
262
+ role="tab"
263
+ aria-selected=${i === this.currentIndex ? 'true' : 'false'}
264
+ aria-label="Slide ${i + 1}"
265
+ @click=${() => this._goTo(i)}
266
+ ></button>
267
+ `)}
268
+ </div>
269
+ `
270
+ : html`<span style="flex:1"></span>`}
271
+
272
+ <slot name="next">
273
+ <button
274
+ class="nav-btn"
275
+ type="button"
276
+ aria-label="Next slide"
277
+ ?disabled=${!showNext}
278
+ @click=${() => this._goTo(this.currentIndex + 1)}
279
+ >
280
+ <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
281
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
282
+ </svg>
283
+ </button>
284
+ </slot>
285
+ </div>
286
+ `;
287
+ }
288
+ }
289
+
290
+ customElements.define('bds-carousel', BdsCarousel);
291
+
292
+ declare global {
293
+ interface HTMLElementTagNameMap {
294
+ 'bds-carousel': BdsCarousel;
295
+ }
296
+ }
@@ -91,8 +91,7 @@ export class BdsNotificationBanner extends LitElement {
91
91
  border-radius: 50%;
92
92
  transition: var(--bds-animation_transition);
93
93
  --bdc_color: currentcolor;
94
- outline: var(--bdc-outline_width) solid var(--bdc_color);
95
- outline-offset: var(--bdc-outline_offset);
94
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color);
96
95
  }
97
96
 
98
97
  .dismiss svg {
@@ -0,0 +1,67 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsPagination } from './bds-pagination';
3
+
4
+ describe('bds-pagination', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-pagination')).toBe(BdsPagination);
7
+ });
8
+
9
+ it('renders a nav element', async () => {
10
+ const el = await fixture('<bds-pagination total-pages="5"></bds-pagination>');
11
+ expect(el.shadowRoot!.querySelector('nav')).not.toBeNull();
12
+ cleanup(el);
13
+ });
14
+
15
+ it('defaults current-page to 1', async () => {
16
+ const el = await fixture('<bds-pagination total-pages="5"></bds-pagination>') as BdsPagination;
17
+ expect(el.currentPage).toBe(1);
18
+ cleanup(el);
19
+ });
20
+
21
+ it('renders page buttons for small page count', async () => {
22
+ const el = await fixture('<bds-pagination current-page="1" total-pages="3"></bds-pagination>');
23
+ const buttons = el.shadowRoot!.querySelectorAll('.button:not(.--nav)');
24
+ expect(buttons.length).toBe(3);
25
+ cleanup(el);
26
+ });
27
+
28
+ it('marks active page with aria-current="page"', async () => {
29
+ const el = await fixture('<bds-pagination current-page="2" total-pages="5"></bds-pagination>');
30
+ const active = el.shadowRoot!.querySelector('[aria-current="page"]');
31
+ expect(active?.textContent?.trim()).toBe('2');
32
+ cleanup(el);
33
+ });
34
+
35
+ it('disables prev button on first page', async () => {
36
+ const el = await fixture('<bds-pagination current-page="1" total-pages="5"></bds-pagination>');
37
+ const prev = el.shadowRoot!.querySelector('[aria-label="Previous page"]') as HTMLButtonElement;
38
+ expect(prev.disabled).toBe(true);
39
+ cleanup(el);
40
+ });
41
+
42
+ it('disables next button on last page', async () => {
43
+ const el = await fixture('<bds-pagination current-page="5" total-pages="5"></bds-pagination>');
44
+ const next = el.shadowRoot!.querySelector('[aria-label="Next page"]') as HTMLButtonElement;
45
+ expect(next.disabled).toBe(true);
46
+ cleanup(el);
47
+ });
48
+
49
+ it('fires bds-page-change event on page click', async () => {
50
+ const el = await fixture('<bds-pagination current-page="1" total-pages="5"></bds-pagination>');
51
+ const pages: number[] = [];
52
+ el.addEventListener('bds-page-change', (e: Event) => {
53
+ pages.push((e as CustomEvent).detail.page);
54
+ });
55
+ const buttons = el.shadowRoot!.querySelectorAll<HTMLButtonElement>('.button:not(.--nav)');
56
+ buttons[1].click(); // click page 2
57
+ expect(pages).toEqual([2]);
58
+ cleanup(el);
59
+ });
60
+
61
+ it('renders ellipsis for large page counts', async () => {
62
+ const el = await fixture('<bds-pagination current-page="5" total-pages="20"></bds-pagination>');
63
+ const ellipsis = el.shadowRoot!.querySelectorAll('.ellipsis');
64
+ expect(ellipsis.length).toBeGreaterThan(0);
65
+ cleanup(el);
66
+ });
67
+ });
@@ -0,0 +1,197 @@
1
+ import { LitElement, css, html } from 'lit';
2
+
3
+ function getPageRange(current: number, total: number): (number | '...')[] {
4
+ const delta = 1;
5
+ const range: (number | '...')[] = [];
6
+ for (let i = 1; i <= total; i++) {
7
+ if (i === 1 || i === total || (i >= current - delta && i <= current + delta)) {
8
+ range.push(i);
9
+ } else if (range[range.length - 1] !== '...') {
10
+ range.push('...');
11
+ }
12
+ }
13
+ return range;
14
+ }
15
+
16
+ /**
17
+ * `<bds-pagination>` — pagination navigation control.
18
+ *
19
+ * Attributes:
20
+ * current-page — active page number (1-based, default: 1)
21
+ * total-pages — total number of pages (default: 1)
22
+ *
23
+ * Events:
24
+ * bds-page-change — fired when the user navigates; `detail: { page: number }`
25
+ *
26
+ * @example
27
+ * <bds-pagination current-page="3" total-pages="10"></bds-pagination>
28
+ * <script>
29
+ * document.querySelector('bds-pagination')
30
+ * .addEventListener('bds-page-change', e => console.log(e.detail.page));
31
+ * </script>
32
+ */
33
+ export class BdsPagination extends LitElement {
34
+ static styles = css`
35
+ :host {
36
+ display: block;
37
+ }
38
+
39
+ .pagination {
40
+ display: flex;
41
+ align-items: center;
42
+ }
43
+
44
+ .list {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: var(--bds-space_xxs);
48
+ list-style: none;
49
+ margin: 0;
50
+ padding: 0;
51
+ }
52
+
53
+ .button {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ min-inline-size: 2rem;
58
+ block-size: 2rem;
59
+ padding-inline: var(--bds-space_xs);
60
+ font-size: var(--bds-font_size--body--s);
61
+ font-family: var(--bds-font_family--body);
62
+ border: none;
63
+ background: none;
64
+ border-radius: var(--bds-border_radius--xs);
65
+ cursor: pointer;
66
+ color: var(--bds-color_on-bg);
67
+ transition: var(--bds-animation_transition);
68
+ --bdc_color: transparent;
69
+ --bdc-outline_radius: var(--bds-border_radius--xs);
70
+ box-shadow: inset 0 0 0 var(--bdc-outline_width) var(--bdc_color), var(--bdc-outline_shadow);
71
+ }
72
+
73
+ .button:focus-visible {
74
+ outline: var(--bds-outline_default);
75
+ outline-offset: var(--bds-outline_offset);
76
+ }
77
+
78
+ @media (hover: hover) and (pointer: fine) {
79
+ .button:not(:disabled):not(.--active):hover {
80
+ background-color: var(--bds-color_bg--subtle);
81
+ }
82
+ }
83
+
84
+ .button.--active {
85
+ background-color: var(--bds-color_interactive);
86
+ color: var(--bds-color_on-interactive);
87
+ font-weight: var(--bds-font_weight--semibold);
88
+ }
89
+
90
+ .button.--nav {
91
+ color: var(--bds-color_on-bg--subtle);
92
+ }
93
+
94
+ .button:disabled {
95
+ opacity: 0.4;
96
+ cursor: not-allowed;
97
+ }
98
+
99
+ .ellipsis {
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ min-inline-size: 2rem;
104
+ color: var(--bds-color_on-bg--subtle);
105
+ font-size: var(--bds-font_size--body--s);
106
+ user-select: none;
107
+ }
108
+
109
+ .nav-icon {
110
+ inline-size: 1rem;
111
+ block-size: 1rem;
112
+ }
113
+ `;
114
+
115
+ static properties = {
116
+ currentPage: { type: Number, attribute: 'current-page', reflect: true },
117
+ totalPages: { type: Number, attribute: 'total-pages', reflect: true },
118
+ };
119
+
120
+ declare currentPage: number;
121
+ declare totalPages: number;
122
+
123
+ constructor() {
124
+ super();
125
+ this.currentPage = 1;
126
+ this.totalPages = 1;
127
+ }
128
+
129
+ private _navigate(page: number) {
130
+ if (page < 1 || page > this.totalPages || page === this.currentPage) return;
131
+ this.currentPage = page;
132
+ this.dispatchEvent(new CustomEvent('bds-page-change', {
133
+ detail: { page },
134
+ bubbles: true,
135
+ composed: true,
136
+ }));
137
+ }
138
+
139
+ render() {
140
+ const pages = getPageRange(this.currentPage, this.totalPages);
141
+
142
+ return html`
143
+ <nav class="pagination" aria-label="Pagination">
144
+ <ul class="list">
145
+ <li>
146
+ <button
147
+ type="button"
148
+ class="button --nav"
149
+ ?disabled=${this.currentPage <= 1}
150
+ aria-label="Previous page"
151
+ @click=${() => this._navigate(this.currentPage - 1)}
152
+ >
153
+ <svg class="nav-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
154
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
155
+ </svg>
156
+ </button>
157
+ </li>
158
+ ${pages.map(page =>
159
+ page === '...'
160
+ ? html`<li><span class="ellipsis" aria-hidden="true">…</span></li>`
161
+ : html`
162
+ <li>
163
+ <button
164
+ type="button"
165
+ class="button ${page === this.currentPage ? '--active' : ''}"
166
+ aria-current=${page === this.currentPage ? 'page' : 'false'}
167
+ @click=${() => this._navigate(page as number)}
168
+ >${page}</button>
169
+ </li>
170
+ `
171
+ )}
172
+ <li>
173
+ <button
174
+ type="button"
175
+ class="button --nav"
176
+ ?disabled=${this.currentPage >= this.totalPages}
177
+ aria-label="Next page"
178
+ @click=${() => this._navigate(this.currentPage + 1)}
179
+ >
180
+ <svg class="nav-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
181
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
182
+ </svg>
183
+ </button>
184
+ </li>
185
+ </ul>
186
+ </nav>
187
+ `;
188
+ }
189
+ }
190
+
191
+ customElements.define('bds-pagination', BdsPagination);
192
+
193
+ declare global {
194
+ interface HTMLElementTagNameMap {
195
+ 'bds-pagination': BdsPagination;
196
+ }
197
+ }
@@ -0,0 +1,45 @@
1
+ import { fixture, cleanup } from '../test/helpers';
2
+ import { BdsTable } from './bds-table';
3
+
4
+ describe('bds-table', () => {
5
+ it('is registered as a custom element', () => {
6
+ expect(customElements.get('bds-table')).toBe(BdsTable);
7
+ });
8
+
9
+ it('renders a wrapper div', async () => {
10
+ const el = await fixture('<bds-table></bds-table>');
11
+ expect(el.shadowRoot!.querySelector('.wrapper')).not.toBeNull();
12
+ cleanup(el);
13
+ });
14
+
15
+ it('renders a slot for table content', async () => {
16
+ const el = await fixture('<bds-table></bds-table>');
17
+ expect(el.shadowRoot!.querySelector('slot')).not.toBeNull();
18
+ cleanup(el);
19
+ });
20
+
21
+ it('sticky-header is false by default', async () => {
22
+ const el = await fixture('<bds-table></bds-table>') as BdsTable;
23
+ expect(el.stickyHeader).toBe(false);
24
+ cleanup(el);
25
+ });
26
+
27
+ it('sticky-header attribute is reflected', async () => {
28
+ const el = await fixture('<bds-table sticky-header></bds-table>') as BdsTable;
29
+ expect(el.stickyHeader).toBe(true);
30
+ cleanup(el);
31
+ });
32
+
33
+ it('wrapper has overflow style when sticky-header is set', async () => {
34
+ const el = await fixture('<bds-table sticky-header></bds-table>');
35
+ const wrapper = el.shadowRoot!.querySelector('.wrapper') as HTMLElement;
36
+ expect(wrapper.style.cssText).toContain('overflow');
37
+ cleanup(el);
38
+ });
39
+
40
+ it('accepts slotted table content', async () => {
41
+ const el = await fixture('<bds-table><table><tbody><tr><td>Cell</td></tr></tbody></table></bds-table>');
42
+ expect(el.querySelector('table')).not.toBeNull();
43
+ cleanup(el);
44
+ });
45
+ });