@reliability-design/web 1.0.2 → 1.0.6

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 (145) hide show
  1. package/dist/components/rel-accordion/README.md +132 -0
  2. package/dist/components/rel-accordion/rel-accordion.ts +61 -29
  3. package/dist/components/rel-alert/README.md +141 -0
  4. package/dist/components/rel-alert/rel-alert.ts +110 -67
  5. package/dist/components/rel-avatar/README.md +119 -0
  6. package/dist/components/rel-avatar/rel-avatar.ts +8 -15
  7. package/dist/components/rel-badge/README.md +86 -0
  8. package/dist/components/rel-badge/rel-badge.ts +28 -21
  9. package/dist/components/rel-breadcrumb/README.md +102 -0
  10. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +24 -18
  11. package/dist/components/rel-button/README.md +130 -0
  12. package/dist/components/rel-button/rel-button.ts +39 -59
  13. package/dist/components/rel-calendar/README.md +109 -0
  14. package/dist/components/rel-calendar/index.ts +1 -0
  15. package/dist/components/rel-calendar/rel-calendar.ts +467 -0
  16. package/dist/components/rel-card/README.md +94 -0
  17. package/dist/components/rel-card/rel-card.ts +21 -11
  18. package/dist/components/rel-cardmedia/README.md +127 -0
  19. package/dist/components/rel-cardmedia/rel-cardmedia.ts +22 -18
  20. package/dist/components/rel-carousel/README.md +166 -0
  21. package/dist/components/rel-carousel/index.ts +1 -0
  22. package/dist/components/rel-carousel/rel-carousel.ts +469 -0
  23. package/dist/components/rel-chart/README.md +286 -0
  24. package/dist/components/rel-chart/index.ts +1 -0
  25. package/dist/components/rel-chart/rel-chart.ts +736 -0
  26. package/dist/components/rel-checkbox/README.md +77 -0
  27. package/dist/components/rel-checkbox/rel-checkbox.ts +36 -45
  28. package/dist/components/rel-chip/README.md +114 -0
  29. package/dist/components/rel-chip/rel-chip.ts +47 -122
  30. package/dist/components/rel-container/README.md +99 -0
  31. package/dist/components/rel-container/rel-container.ts +1 -6
  32. package/dist/components/rel-date-picker/README.md +253 -0
  33. package/dist/components/rel-date-picker/index.ts +1 -0
  34. package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
  35. package/dist/components/rel-divider/README.md +100 -0
  36. package/dist/components/rel-divider/rel-divider.ts +37 -37
  37. package/dist/components/rel-drawer/README.md +200 -0
  38. package/dist/components/rel-drawer/index.ts +1 -0
  39. package/dist/components/rel-drawer/rel-drawer.ts +621 -0
  40. package/dist/components/rel-kbd/README.md +151 -0
  41. package/dist/components/rel-kbd/rel-kbd.ts +118 -0
  42. package/dist/components/rel-layout/README.md +88 -0
  43. package/dist/components/rel-layout/rel-layout.ts +12 -14
  44. package/dist/components/rel-menu/README.md +151 -0
  45. package/dist/components/rel-menu/rel-menu.ts +18 -10
  46. package/dist/components/rel-menubar/README.md +329 -0
  47. package/dist/components/rel-menubar/index.ts +2 -0
  48. package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
  49. package/dist/components/rel-menubar/rel-menubar.ts +576 -0
  50. package/dist/components/rel-nav-menu/README.md +289 -0
  51. package/dist/components/rel-nav-menu/index.ts +3 -0
  52. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +143 -0
  53. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
  54. package/dist/components/rel-nav-menu/rel-nav-menu.ts +331 -0
  55. package/dist/components/rel-navbar/README.md +126 -0
  56. package/dist/components/rel-navbar/rel-navbar.ts +91 -0
  57. package/dist/components/rel-otp/README.md +440 -0
  58. package/dist/components/rel-otp/index.ts +1 -0
  59. package/dist/components/rel-otp/rel-otp.ts +504 -0
  60. package/dist/components/rel-pagination/README.md +410 -0
  61. package/dist/components/rel-pagination/index.ts +1 -0
  62. package/dist/components/rel-pagination/rel-pagination.ts +376 -0
  63. package/dist/components/rel-popover/README.md +568 -0
  64. package/dist/components/rel-popover/index.ts +2 -0
  65. package/dist/components/rel-popover/rel-popover-content.ts +55 -0
  66. package/dist/components/rel-popover/rel-popover.ts +327 -0
  67. package/dist/components/rel-progress/README.md +110 -0
  68. package/dist/components/rel-progress/rel-progress.ts +58 -20
  69. package/dist/components/rel-quote-page/README.md +166 -0
  70. package/dist/components/rel-quote-page/rel-quote-page.ts +816 -0
  71. package/dist/components/rel-radio/README.md +120 -0
  72. package/dist/components/rel-radio/rel-radio.ts +41 -73
  73. package/dist/components/rel-resizable/README.md +140 -0
  74. package/dist/components/rel-resizable/index.ts +3 -0
  75. package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
  76. package/dist/components/rel-resizable/rel-resizable-handle.ts +149 -0
  77. package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
  78. package/dist/components/rel-segmented-button/README.md +141 -0
  79. package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
  80. package/dist/components/rel-select/README.md +107 -0
  81. package/dist/components/rel-select/rel-select.ts +14 -14
  82. package/dist/components/rel-sheet/README.md +224 -0
  83. package/dist/components/rel-sheet/rel-sheet.ts +683 -0
  84. package/dist/components/rel-sidebar/README.md +209 -0
  85. package/dist/components/rel-sidebar/rel-sidebar.ts +1158 -0
  86. package/dist/components/rel-slider/README.md +135 -0
  87. package/dist/components/rel-slider/rel-slider.ts +19 -7
  88. package/dist/components/rel-snackbar/README.md +134 -0
  89. package/dist/components/rel-snackbar/rel-snackbar.ts +73 -2
  90. package/dist/components/rel-stepper/README.md +160 -0
  91. package/dist/components/rel-stepper/rel-stepper.ts +29 -8
  92. package/dist/components/rel-switch/README.md +102 -0
  93. package/dist/components/rel-switch/rel-switch.ts +22 -11
  94. package/dist/components/rel-tab/README.md +163 -0
  95. package/dist/components/rel-tab/rel-tab.ts +46 -22
  96. package/dist/components/rel-table/README.md +178 -0
  97. package/dist/components/rel-table/rel-table-body.ts +16 -0
  98. package/dist/components/rel-table/rel-table-caption.ts +22 -0
  99. package/dist/components/rel-table/rel-table-cell.ts +49 -0
  100. package/dist/components/rel-table/rel-table-footer.ts +18 -0
  101. package/dist/components/rel-table/rel-table-head.ts +33 -0
  102. package/dist/components/rel-table/rel-table-header.ts +16 -0
  103. package/dist/components/rel-table/rel-table-row.ts +32 -0
  104. package/dist/components/rel-table/rel-table.ts +47 -0
  105. package/dist/components/rel-textfield/README.md +90 -0
  106. package/dist/components/rel-textfield/rel-textfield.ts +37 -21
  107. package/dist/components/rel-toast/README.md +148 -0
  108. package/dist/components/rel-toast/rel-toast.ts +32 -14
  109. package/dist/components/rel-tooltip/README.md +103 -0
  110. package/dist/components/rel-tooltip/rel-tooltip.ts +9 -9
  111. package/dist/index.d.mts +846 -7
  112. package/dist/index.d.ts +846 -7
  113. package/dist/index.js +10860 -1803
  114. package/dist/index.js.map +1 -1
  115. package/dist/index.mjs +10820 -1809
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/styles/vars.css +293 -183
  118. package/dist/vars-ITL2K3KO.css +294 -0
  119. package/package.json +2 -2
  120. package/dist/components/rel-accordion/rel-accordion.stories.ts +0 -313
  121. package/dist/components/rel-alert/rel-alert.stories.ts +0 -412
  122. package/dist/components/rel-avatar/rel-avatar.stories.ts +0 -327
  123. package/dist/components/rel-badge/rel-badge.stories.ts +0 -350
  124. package/dist/components/rel-breadcrumb/rel-breadcrumb.stories.ts +0 -306
  125. package/dist/components/rel-button/rel-button.stories.ts +0 -356
  126. package/dist/components/rel-card/rel-card.stories.ts +0 -299
  127. package/dist/components/rel-cardmedia/rel-cardmedia.stories.ts +0 -368
  128. package/dist/components/rel-checkbox/rel-checkbox.stories.ts +0 -414
  129. package/dist/components/rel-chip/rel-chip.stories.ts +0 -552
  130. package/dist/components/rel-container/rel-container.stories.ts +0 -239
  131. package/dist/components/rel-divider/rel-divider.stories.ts +0 -229
  132. package/dist/components/rel-layout/rel-layout.stories.ts +0 -713
  133. package/dist/components/rel-menu/rel-menu.stories.ts +0 -910
  134. package/dist/components/rel-progress/rel-progress.stories.ts +0 -817
  135. package/dist/components/rel-radio/rel-radio.stories.ts +0 -1048
  136. package/dist/components/rel-segmented-button/rel-segmented-button.stories.ts +0 -1079
  137. package/dist/components/rel-select/rel-select.stories.ts +0 -1201
  138. package/dist/components/rel-slider/rel-slider.stories.ts +0 -730
  139. package/dist/components/rel-snackbar/rel-snackbar.stories.ts +0 -808
  140. package/dist/components/rel-stepper/rel-stepper.stories.ts +0 -752
  141. package/dist/components/rel-switch/rel-switch.stories.ts +0 -441
  142. package/dist/components/rel-tab/rel-tab.stories.ts +0 -492
  143. package/dist/components/rel-textfield/rel-textfield.stories.ts +0 -710
  144. package/dist/components/rel-toast/rel-toast.stories.ts +0 -394
  145. package/dist/components/rel-tooltip/rel-tooltip.stories.ts +0 -364
@@ -0,0 +1,1158 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property, state, query } from "lit/decorators.js";
3
+
4
+ export type SidebarVariant = "sidebar" | "floating" | "inset";
5
+ export type SidebarSide = "left" | "right";
6
+ export type SidebarCollapsible = "offcanvas" | "icon" | "none";
7
+ export type SidebarWidth = "sm" | "md" | "lg";
8
+ export type SidebarChangeReason = "trigger" | "rail" | "overlay" | "escape" | "shortcut" | "api";
9
+
10
+ // ============================================================================
11
+ // rel-sidebar-provider (Root State Manager)
12
+ // ============================================================================
13
+ @customElement("rel-sidebar-provider")
14
+ export class RelSidebarProvider extends LitElement {
15
+ @property({ type: Boolean, reflect: true }) open?: boolean;
16
+ @property({ type: Boolean, attribute: "defaultopen" }) defaultOpen = true;
17
+ @property({ type: Boolean, reflect: true }) collapsed?: boolean;
18
+ @property({ type: Boolean, attribute: "defaultcollapsed" }) defaultCollapsed = false;
19
+ @property({ type: String }) variant: SidebarVariant = "sidebar";
20
+ @property({ type: String }) side: SidebarSide = "left";
21
+ @property({ type: String }) collapsible: SidebarCollapsible = "icon";
22
+ @property({ type: String }) width: SidebarWidth = "md";
23
+ @property({ type: String, attribute: "persist-key" }) persistKey = "rel-sidebar";
24
+ @property({ type: Boolean, attribute: "keyboard-shortcut" }) keyboardShortcut = true;
25
+ @property({ type: Boolean, attribute: "disable-persist" }) disablePersist = false;
26
+ @property({ type: Boolean, attribute: "disable-overlay-close" }) disableOverlayClose = false;
27
+
28
+ @state() private internalOpen = true;
29
+ @state() private internalCollapsed = false;
30
+ @state() public isMobile = false;
31
+
32
+ private mediaQuery: MediaQueryList | null = null;
33
+ private isInitialized = false;
34
+
35
+ static styles = css`
36
+ :host {
37
+ display: contents;
38
+ }
39
+ `;
40
+
41
+ connectedCallback() {
42
+ super.connectedCallback();
43
+
44
+ // Setup mobile detection first (synchronous)
45
+ this.setupMediaQuery();
46
+
47
+ // Load state with mobile awareness
48
+ this.loadInitialState();
49
+
50
+ // Setup keyboard shortcut
51
+ if (this.keyboardShortcut) {
52
+ document.addEventListener("keydown", this.handleKeyDown);
53
+ }
54
+
55
+ this.isInitialized = true;
56
+
57
+ // Notify children of initial state after a microtask
58
+ requestAnimationFrame(() => {
59
+ this.notifyStateChange();
60
+ });
61
+ }
62
+
63
+ disconnectedCallback() {
64
+ super.disconnectedCallback();
65
+ if (this.mediaQuery) {
66
+ this.mediaQuery.removeEventListener("change", this.handleMediaChange);
67
+ }
68
+ document.removeEventListener("keydown", this.handleKeyDown);
69
+ }
70
+
71
+ updated(changed: Map<string, unknown>) {
72
+ if (changed.has("open") && this.isOpenControlled()) {
73
+ this.internalOpen = !!this.open;
74
+ this.notifyStateChange();
75
+ }
76
+ if (changed.has("collapsed") && this.isCollapsedControlled()) {
77
+ this.internalCollapsed = !!this.collapsed;
78
+ this.notifyStateChange();
79
+ }
80
+ }
81
+
82
+ private isOpenControlled() {
83
+ return this.open !== undefined;
84
+ }
85
+
86
+ private isCollapsedControlled() {
87
+ return this.collapsed !== undefined;
88
+ }
89
+
90
+ get activeOpen() {
91
+ return this.isOpenControlled() ? !!this.open : this.internalOpen;
92
+ }
93
+
94
+ get activeCollapsed() {
95
+ return this.isCollapsedControlled() ? !!this.collapsed : this.internalCollapsed;
96
+ }
97
+
98
+ private setupMediaQuery() {
99
+ this.mediaQuery = window.matchMedia("(max-width: 768px)");
100
+ this.isMobile = this.mediaQuery.matches;
101
+ this.mediaQuery.addEventListener("change", this.handleMediaChange);
102
+ }
103
+
104
+ private handleMediaChange = (e: MediaQueryListEvent) => {
105
+ const wasMobile = this.isMobile;
106
+ this.isMobile = e.matches;
107
+
108
+ // When switching to mobile, close the sidebar unless controlled
109
+ if (!wasMobile && this.isMobile && !this.isOpenControlled()) {
110
+ this.internalOpen = false;
111
+ this.persistState();
112
+ }
113
+
114
+ this.notifyStateChange();
115
+ };
116
+
117
+ private isOffcanvasMode(): boolean {
118
+ return this.isMobile || this.collapsible === "offcanvas";
119
+ }
120
+
121
+ private loadInitialState() {
122
+ // If controlled, use controlled value
123
+ if (this.isOpenControlled()) {
124
+ this.internalOpen = !!this.open;
125
+ } else if (this.isOffcanvasMode()) {
126
+ // Mobile/offcanvas: always start closed, ignore persisted state
127
+ this.internalOpen = false;
128
+ } else {
129
+ // Desktop: use persisted state or default
130
+ if (!this.disablePersist) {
131
+ this.loadPersistedState();
132
+ } else {
133
+ this.internalOpen = this.defaultOpen;
134
+ this.internalCollapsed = this.defaultCollapsed;
135
+ }
136
+ }
137
+
138
+ // Load collapsed state
139
+ if (!this.isCollapsedControlled()) {
140
+ if (!this.disablePersist && !this.isOffcanvasMode()) {
141
+ const persisted = this.getPersistedState();
142
+ if (persisted) {
143
+ this.internalCollapsed = persisted.collapsed ?? this.defaultCollapsed;
144
+ } else {
145
+ this.internalCollapsed = this.defaultCollapsed;
146
+ }
147
+ } else {
148
+ this.internalCollapsed = this.defaultCollapsed;
149
+ }
150
+ } else {
151
+ this.internalCollapsed = !!this.collapsed;
152
+ }
153
+ }
154
+
155
+ private getPersistedState(): { open?: boolean; collapsed?: boolean } | null {
156
+ try {
157
+ const stored = localStorage.getItem(this.persistKey);
158
+ if (stored) {
159
+ return JSON.parse(stored);
160
+ }
161
+ } catch (e) {
162
+ // ignore
163
+ }
164
+ return null;
165
+ }
166
+
167
+ private loadPersistedState() {
168
+ const persisted = this.getPersistedState();
169
+ if (persisted) {
170
+ this.internalOpen = persisted.open ?? this.defaultOpen;
171
+ this.internalCollapsed = persisted.collapsed ?? this.defaultCollapsed;
172
+ } else {
173
+ this.internalOpen = this.defaultOpen;
174
+ this.internalCollapsed = this.defaultCollapsed;
175
+ }
176
+ }
177
+
178
+ private persistState() {
179
+ if (this.disablePersist) return;
180
+
181
+ // Don't persist open state for offcanvas mode
182
+ const stateToPersist = this.isOffcanvasMode()
183
+ ? { collapsed: this.activeCollapsed }
184
+ : { open: this.activeOpen, collapsed: this.activeCollapsed };
185
+
186
+ try {
187
+ localStorage.setItem(this.persistKey, JSON.stringify(stateToPersist));
188
+ } catch (e) {
189
+ // Silently fail if localStorage is unavailable
190
+ }
191
+ }
192
+
193
+ private handleKeyDown = (e: KeyboardEvent) => {
194
+ if (!this.keyboardShortcut) return;
195
+
196
+ // Ctrl/Cmd + B
197
+ if ((e.ctrlKey || e.metaKey) && e.key === "b") {
198
+ e.preventDefault();
199
+
200
+ if (this.isMobile || this.collapsible === "offcanvas") {
201
+ // Toggle open state
202
+ this.setOpen(!this.activeOpen, "shortcut");
203
+ } else if (this.collapsible === "icon") {
204
+ // Toggle collapsed state
205
+ this.setCollapsed(!this.activeCollapsed, "shortcut");
206
+ }
207
+ }
208
+ };
209
+
210
+ setOpen(next: boolean, reason: SidebarChangeReason = "api") {
211
+ if (!this.isOpenControlled()) {
212
+ this.internalOpen = next;
213
+ }
214
+
215
+ this.persistState();
216
+ this.notifyStateChange();
217
+ this.dispatchChangeEvent(reason);
218
+ }
219
+
220
+ setCollapsed(next: boolean, reason: SidebarChangeReason = "api") {
221
+ if (!this.isCollapsedControlled()) {
222
+ this.internalCollapsed = next;
223
+ }
224
+
225
+ this.persistState();
226
+ this.notifyStateChange();
227
+ this.dispatchChangeEvent(reason);
228
+ }
229
+
230
+ private dispatchChangeEvent(reason: SidebarChangeReason) {
231
+ this.dispatchEvent(
232
+ new CustomEvent("rel-sidebar-change", {
233
+ detail: {
234
+ open: this.activeOpen,
235
+ collapsed: this.activeCollapsed,
236
+ reason,
237
+ },
238
+ bubbles: true,
239
+ composed: true,
240
+ })
241
+ );
242
+ }
243
+
244
+ private notifyStateChange() {
245
+ // Dispatch a state event that children can listen to
246
+ this.dispatchEvent(
247
+ new CustomEvent("rel-sidebar-state", {
248
+ detail: {
249
+ isOpen: this.activeOpen,
250
+ isCollapsed: this.activeCollapsed,
251
+ isMobile: this.isMobile,
252
+ side: this.side,
253
+ variant: this.variant,
254
+ collapsible: this.collapsible,
255
+ width: this.width,
256
+ },
257
+ bubbles: true,
258
+ composed: true,
259
+ })
260
+ );
261
+ }
262
+
263
+ render() {
264
+ return html`<slot></slot>`;
265
+ }
266
+ }
267
+
268
+ // ============================================================================
269
+ // rel-sidebar (Visual Container)
270
+ // ============================================================================
271
+ @customElement("rel-sidebar")
272
+ export class RelSidebar extends LitElement {
273
+ @property({ type: Boolean }) inset = false;
274
+
275
+ @state() private isOpen = true;
276
+ @state() private isCollapsed = false;
277
+ @state() private isMobile = false;
278
+ @state() private side: SidebarSide = "left";
279
+ @state() private variant: SidebarVariant = "sidebar";
280
+ @state() private collapsible: SidebarCollapsible = "icon";
281
+ @state() private width: SidebarWidth = "md";
282
+
283
+ @query(".sidebar-container") private containerElement!: HTMLElement;
284
+
285
+ private provider: RelSidebarProvider | null = null;
286
+ private triggerElement: HTMLElement | null = null;
287
+ private originalBodyOverflow = "";
288
+ private originalBodyPaddingRight = "";
289
+
290
+ static styles = css`
291
+ :host {
292
+ --sidebar-width-sm: 240px;
293
+ --sidebar-width-md: 280px;
294
+ --sidebar-width-lg: 320px;
295
+ --sidebar-width-collapsed: 56px;
296
+ --sidebar-transition-duration: 0.2s;
297
+ --sidebar-transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
298
+ }
299
+
300
+ .sidebar-wrapper {
301
+ position: relative;
302
+ height: 100%;
303
+ }
304
+
305
+ /* Overlay for offcanvas mode */
306
+ .overlay {
307
+ position: fixed;
308
+ top: 0;
309
+ left: 0;
310
+ right: 0;
311
+ bottom: 0;
312
+ background: var(--rel-overlay-scrim, rgba(0, 0, 0, 0.5));
313
+ z-index: 999;
314
+ opacity: 0;
315
+ pointer-events: none;
316
+ transition: opacity var(--sidebar-transition-duration)
317
+ var(--sidebar-transition-timing);
318
+ }
319
+
320
+ .overlay.visible {
321
+ opacity: 1;
322
+ pointer-events: auto;
323
+ }
324
+
325
+ /* Sidebar container */
326
+ .sidebar-container {
327
+ position: fixed;
328
+ top: 0;
329
+ bottom: 0;
330
+ z-index: 1000;
331
+ background: var(--rel-surface-secondary, #fafafa);
332
+ color: var(--rel-text-primary, #333);
333
+ display: flex;
334
+ flex-direction: column;
335
+ transition: transform var(--sidebar-transition-duration)
336
+ var(--sidebar-transition-timing),
337
+ width var(--sidebar-transition-duration)
338
+ var(--sidebar-transition-timing);
339
+ width: var(--sidebar-width);
340
+ outline: none;
341
+ overflow: hidden;
342
+ }
343
+
344
+ /* Left side */
345
+ .sidebar-container[data-side="left"] {
346
+ left: 0;
347
+ border-right: 1px solid var(--rel-border-default, #ddd);
348
+ }
349
+
350
+ .sidebar-container[data-side="left"].offcanvas-hidden {
351
+ transform: translateX(-100%);
352
+ }
353
+
354
+ /* Right side */
355
+ .sidebar-container[data-side="right"] {
356
+ right: 0;
357
+ border-left: 1px solid var(--rel-border-default, #ddd);
358
+ }
359
+
360
+ .sidebar-container[data-side="right"].offcanvas-hidden {
361
+ transform: translateX(100%);
362
+ }
363
+
364
+ /* Floating variant */
365
+ .sidebar-container.variant-floating {
366
+ top: var(--rel-spacing-space-md, 16px);
367
+ bottom: var(--rel-spacing-space-md, 16px);
368
+ background: var(--rel-surface-secondary, #fafafa);
369
+ border: 1px solid var(--rel-border-default, #ddd);
370
+ border-radius: var(--rel-borders-radius-border-radius-300, 16px);
371
+ box-shadow: var(--rel-shadows-shadow-400);
372
+ }
373
+
374
+ .sidebar-container.variant-floating[data-side="left"] {
375
+ left: var(--rel-spacing-space-md, 16px);
376
+ }
377
+
378
+ .sidebar-container.variant-floating[data-side="right"] {
379
+ right: var(--rel-spacing-space-md, 16px);
380
+ }
381
+
382
+ /* Inset variant */
383
+ .sidebar-container.variant-inset {
384
+ position: relative;
385
+ border: none;
386
+ border-radius: 0;
387
+ box-shadow: none;
388
+ }
389
+
390
+ .sidebar-container.variant-inset[data-side="left"] {
391
+ border-right: 1px solid var(--rel-border-default, #ddd);
392
+ }
393
+
394
+ .sidebar-container.variant-inset[data-side="right"] {
395
+ border-left: 1px solid var(--rel-border-default, #ddd);
396
+ }
397
+
398
+ /* Desktop mode - not offcanvas */
399
+ @media (min-width: 769px) {
400
+ .sidebar-container:not(.offcanvas-mode) {
401
+ position: relative;
402
+ }
403
+
404
+ .overlay:not(.offcanvas-mode) {
405
+ display: none;
406
+ }
407
+ }
408
+
409
+ /* Collapsed state */
410
+ .sidebar-container.collapsed {
411
+ width: var(--sidebar-width-collapsed);
412
+ }
413
+
414
+ /* Reduced motion */
415
+ @media (prefers-reduced-motion: reduce) {
416
+ .overlay,
417
+ .sidebar-container {
418
+ transition: none;
419
+ }
420
+ }
421
+ `;
422
+
423
+ connectedCallback() {
424
+ super.connectedCallback();
425
+ this.findProvider();
426
+ }
427
+
428
+ disconnectedCallback() {
429
+ super.disconnectedCallback();
430
+ this.cleanup();
431
+ }
432
+
433
+ private findProvider() {
434
+ let parent = this.parentElement;
435
+ while (parent) {
436
+ if (parent instanceof RelSidebarProvider) {
437
+ this.provider = parent;
438
+ break;
439
+ }
440
+ parent = parent.parentElement;
441
+ }
442
+
443
+ if (this.provider) {
444
+ this.provider.addEventListener(
445
+ "rel-sidebar-state",
446
+ this.handleStateChange,
447
+ );
448
+ // Trigger initial state sync
449
+ this.syncState();
450
+ }
451
+ }
452
+
453
+ private syncState() {
454
+ if (!this.provider) return;
455
+
456
+ const wasOpen = this.isOpen;
457
+ const wasOffcanvas = this.isOffcanvasMode();
458
+
459
+ this.isOpen = this.provider.activeOpen;
460
+ this.isCollapsed = this.provider.activeCollapsed;
461
+ this.isMobile = this.provider.isMobile;
462
+ this.side = this.provider.side;
463
+ this.variant = this.provider.variant;
464
+ this.collapsible = this.provider.collapsible;
465
+ this.width = this.provider.width;
466
+
467
+ const isOffcanvas = this.isOffcanvasMode();
468
+
469
+ // Handle offcanvas behavior
470
+ if (isOffcanvas) {
471
+ if (this.isOpen && !wasOpen) {
472
+ this.lockScroll();
473
+ this.setupFocusTrap();
474
+ } else if (!this.isOpen && wasOpen) {
475
+ this.unlockScroll();
476
+ this.removeFocusTrap();
477
+ }
478
+ } else if (wasOffcanvas && !isOffcanvas) {
479
+ // Switched from offcanvas to normal mode
480
+ this.unlockScroll();
481
+ this.removeFocusTrap();
482
+ }
483
+
484
+ this.requestUpdate();
485
+ }
486
+
487
+ private handleStateChange = (e: Event) => {
488
+ this.syncState();
489
+ };
490
+
491
+ private isOffcanvasMode(): boolean {
492
+ return this.isMobile || this.collapsible === "offcanvas";
493
+ }
494
+
495
+ private lockScroll() {
496
+ if (typeof document === "undefined") return;
497
+
498
+ const scrollbarWidth =
499
+ window.innerWidth - document.documentElement.clientWidth;
500
+
501
+ this.originalBodyOverflow = document.body.style.overflow;
502
+ this.originalBodyPaddingRight = document.body.style.paddingRight;
503
+
504
+ document.body.style.overflow = "hidden";
505
+ if (scrollbarWidth > 0) {
506
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
507
+ }
508
+ }
509
+
510
+ private unlockScroll() {
511
+ if (typeof document === "undefined") return;
512
+
513
+ document.body.style.overflow = this.originalBodyOverflow;
514
+ document.body.style.paddingRight = this.originalBodyPaddingRight;
515
+ }
516
+
517
+ private setupFocusTrap() {
518
+ this.triggerElement = document.activeElement as HTMLElement;
519
+ document.addEventListener("keydown", this.handleKeydown);
520
+
521
+ // Focus first focusable element or container
522
+ requestAnimationFrame(() => {
523
+ const focusable = this.getFocusableElements();
524
+ if (focusable.length > 0) {
525
+ focusable[0].focus();
526
+ } else if (this.containerElement) {
527
+ this.containerElement.setAttribute("tabindex", "-1");
528
+ this.containerElement.focus();
529
+ }
530
+ });
531
+ }
532
+
533
+ private removeFocusTrap() {
534
+ document.removeEventListener("keydown", this.handleKeydown);
535
+
536
+ // Restore focus to trigger
537
+ if (this.triggerElement && this.triggerElement.focus) {
538
+ requestAnimationFrame(() => {
539
+ this.triggerElement?.focus();
540
+ this.triggerElement = null;
541
+ });
542
+ }
543
+ }
544
+
545
+ private getFocusableElements(): HTMLElement[] {
546
+ if (!this.containerElement) return [];
547
+
548
+ const selectors = [
549
+ "a[href]",
550
+ "button:not([disabled])",
551
+ "textarea:not([disabled])",
552
+ "input:not([disabled])",
553
+ "select:not([disabled])",
554
+ '[tabindex]:not([tabindex="-1"])',
555
+ ].join(",");
556
+
557
+ const elements = Array.from(
558
+ this.containerElement.querySelectorAll(selectors),
559
+ ) as HTMLElement[];
560
+
561
+ return elements.filter((el) => el.offsetWidth || el.offsetHeight);
562
+ }
563
+
564
+ private handleKeydown = (e: KeyboardEvent) => {
565
+ if (!this.isOpen || !this.isOffcanvasMode()) return;
566
+
567
+ if (e.key === "Escape") {
568
+ e.preventDefault();
569
+ this.provider?.setOpen(false, "escape");
570
+ return;
571
+ }
572
+
573
+ // Focus trap
574
+ if (e.key === "Tab") {
575
+ const focusable = this.getFocusableElements();
576
+ if (focusable.length === 0) {
577
+ e.preventDefault();
578
+ return;
579
+ }
580
+
581
+ const firstFocusable = focusable[0];
582
+ const lastFocusable = focusable[focusable.length - 1];
583
+ const activeElement =
584
+ this.shadowRoot?.activeElement || document.activeElement;
585
+
586
+ if (e.shiftKey) {
587
+ if (activeElement === firstFocusable) {
588
+ e.preventDefault();
589
+ lastFocusable.focus();
590
+ }
591
+ } else {
592
+ if (activeElement === lastFocusable) {
593
+ e.preventDefault();
594
+ firstFocusable.focus();
595
+ }
596
+ }
597
+ }
598
+ };
599
+
600
+ private handleOverlayClick = () => {
601
+ if (this.provider && !this.provider.disableOverlayClose) {
602
+ this.provider.setOpen(false, "overlay");
603
+ }
604
+ };
605
+
606
+ private cleanup() {
607
+ this.unlockScroll();
608
+ this.removeFocusTrap();
609
+ if (this.provider) {
610
+ this.provider.removeEventListener(
611
+ "rel-sidebar-state",
612
+ this.handleStateChange,
613
+ );
614
+ }
615
+ }
616
+
617
+ private getWidthValue(): string {
618
+ const widthMap: Record<SidebarWidth, string> = {
619
+ sm: "var(--sidebar-width-sm)",
620
+ md: "var(--sidebar-width-md)",
621
+ lg: "var(--sidebar-width-lg)",
622
+ };
623
+ return widthMap[this.width] || widthMap.md;
624
+ }
625
+
626
+ render() {
627
+ const isOffcanvas = this.isOffcanvasMode();
628
+ const showOverlay = isOffcanvas && this.isOpen;
629
+ const containerClasses = [
630
+ "sidebar-container",
631
+ `variant-${this.variant}`,
632
+ this.isCollapsed && !isOffcanvas ? "collapsed" : "",
633
+ isOffcanvas ? "offcanvas-mode" : "",
634
+ isOffcanvas && !this.isOpen ? "offcanvas-hidden" : "",
635
+ ]
636
+ .filter(Boolean)
637
+ .join(" ");
638
+
639
+ return html`
640
+ <div class="sidebar-wrapper">
641
+ ${showOverlay
642
+ ? html`
643
+ <div
644
+ class="overlay visible"
645
+ @click="${this.handleOverlayClick}"
646
+ ></div>
647
+ `
648
+ : ""}
649
+
650
+ <nav
651
+ class="${containerClasses}"
652
+ data-side="${this.side}"
653
+ style="--sidebar-width: ${this.getWidthValue()}"
654
+ role="navigation"
655
+ aria-hidden="${isOffcanvas && !this.isOpen}"
656
+ tabindex="-1"
657
+ >
658
+ <slot></slot>
659
+ </nav>
660
+ </div>
661
+ `;
662
+ }
663
+ }
664
+
665
+ // ============================================================================
666
+ // rel-sidebar-trigger (Toggle Button)
667
+ // ============================================================================
668
+ @customElement("rel-sidebar-trigger")
669
+ export class RelSidebarTrigger extends LitElement {
670
+ @state() private isOpen = true;
671
+ @state() private isCollapsed = false;
672
+
673
+ private provider: RelSidebarProvider | null = null;
674
+
675
+ static styles = css`
676
+ :host {
677
+ display: inline-block;
678
+ }
679
+
680
+ button {
681
+ background: none;
682
+ border: none;
683
+ padding: var(--rel-spacing-space-xs, 8px);
684
+ margin: 0;
685
+ cursor: pointer;
686
+ display: flex;
687
+ align-items: center;
688
+ justify-content: center;
689
+ border-radius: var(--rel-borders-radius-border-radius-200, 8px);
690
+ color: var(--rel-text-heading, #2b2b2b);
691
+ transition: background 0.15s ease;
692
+ }
693
+
694
+ button:hover {
695
+ background: var(--rel-surface-surface, #f5f5f5);
696
+ }
697
+
698
+ button:active {
699
+ background: var(--rel-border-default, #ddd);
700
+ }
701
+
702
+ button:focus-visible {
703
+ outline: 2px solid var(--rel-border-info, #0091ea);
704
+ outline-offset: 2px;
705
+ }
706
+ `;
707
+
708
+ connectedCallback() {
709
+ super.connectedCallback();
710
+ this.findProvider();
711
+ }
712
+
713
+ disconnectedCallback() {
714
+ super.disconnectedCallback();
715
+ if (this.provider) {
716
+ this.provider.removeEventListener(
717
+ "rel-sidebar-state",
718
+ this.handleStateChange,
719
+ );
720
+ }
721
+ }
722
+
723
+ private findProvider() {
724
+ let parent = this.parentElement;
725
+ while (parent) {
726
+ if (parent instanceof RelSidebarProvider) {
727
+ this.provider = parent;
728
+ break;
729
+ }
730
+ parent = parent.parentElement;
731
+ }
732
+
733
+ if (this.provider) {
734
+ this.provider.addEventListener(
735
+ "rel-sidebar-state",
736
+ this.handleStateChange,
737
+ );
738
+ this.syncState();
739
+ }
740
+ }
741
+
742
+ private syncState() {
743
+ if (!this.provider) return;
744
+ this.isOpen = this.provider.activeOpen;
745
+ this.isCollapsed = this.provider.activeCollapsed;
746
+ }
747
+
748
+ private handleStateChange = () => {
749
+ this.syncState();
750
+ };
751
+
752
+ private handleClick = () => {
753
+ if (!this.provider) return;
754
+
755
+ if (this.provider.isMobile || this.provider.collapsible === "offcanvas") {
756
+ this.provider.setOpen(!this.isOpen, "trigger");
757
+ } else if (this.provider.collapsible === "icon") {
758
+ this.provider.setCollapsed(!this.isCollapsed, "rail");
759
+ }
760
+ };
761
+
762
+ render() {
763
+ return html`
764
+ <button
765
+ @click="${this.handleClick}"
766
+ type="button"
767
+ aria-expanded="${this.isOpen}"
768
+ aria-label="Toggle sidebar"
769
+ >
770
+ <slot>
771
+ <svg
772
+ width="20"
773
+ height="20"
774
+ viewBox="0 0 24 24"
775
+ fill="none"
776
+ stroke="currentColor"
777
+ stroke-width="2"
778
+ >
779
+ <line x1="3" y1="12" x2="21" y2="12"></line>
780
+ <line x1="3" y1="6" x2="21" y2="6"></line>
781
+ <line x1="3" y1="18" x2="21" y2="18"></line>
782
+ </svg>
783
+ </slot>
784
+ </button>
785
+ `;
786
+ }
787
+ }
788
+
789
+ // ============================================================================
790
+ // Layout Helpers
791
+ // ============================================================================
792
+
793
+ @customElement("rel-sidebar-header")
794
+ export class RelSidebarHeader extends LitElement {
795
+ static styles = css`
796
+ :host {
797
+ display: block;
798
+ padding: var(--rel-spacing-space-md, 16px);
799
+ border-bottom: 1px solid var(--rel-border-default, #ddd);
800
+ flex-shrink: 0;
801
+ color: var(--rel-text-heading, #2b2b2b);
802
+ }
803
+ `;
804
+
805
+ render() {
806
+ return html`<slot></slot>`;
807
+ }
808
+ }
809
+
810
+ @customElement("rel-sidebar-content")
811
+ export class RelSidebarContent extends LitElement {
812
+ static styles = css`
813
+ :host {
814
+ display: block;
815
+ flex: 1;
816
+ overflow-y: auto;
817
+ padding: var(--rel-spacing-space-sm, 12px) var(--rel-spacing-space-xs, 8px);
818
+ }
819
+
820
+ /* Scrollbar styling */
821
+ :host::-webkit-scrollbar {
822
+ width: 4px;
823
+ }
824
+ :host::-webkit-scrollbar-track {
825
+ background: transparent;
826
+ }
827
+ :host::-webkit-scrollbar-thumb {
828
+ background: var(--rel-border-default, #ddd);
829
+ border-radius: 2px;
830
+ }
831
+ `;
832
+
833
+ render() {
834
+ return html`<slot></slot>`;
835
+ }
836
+ }
837
+
838
+ @customElement("rel-sidebar-footer")
839
+ export class RelSidebarFooter extends LitElement {
840
+ static styles = css`
841
+ :host {
842
+ display: block;
843
+ padding: var(--rel-spacing-space-md, 16px);
844
+ border-top: 1px solid var(--rel-border-default, #ddd);
845
+ flex-shrink: 0;
846
+ color: var(--rel-text-primary, #333);
847
+ }
848
+ `;
849
+
850
+ render() {
851
+ return html`<slot></slot>`;
852
+ }
853
+ }
854
+
855
+ @customElement("rel-sidebar-group")
856
+ export class RelSidebarGroup extends LitElement {
857
+ static styles = css`
858
+ :host {
859
+ display: block;
860
+ margin-bottom: var(--rel-spacing-space-md, 16px);
861
+ }
862
+
863
+ :host(:last-child) {
864
+ margin-bottom: 0;
865
+ }
866
+ `;
867
+
868
+ render() {
869
+ return html`<slot></slot>`;
870
+ }
871
+ }
872
+
873
+ @customElement("rel-sidebar-group-label")
874
+ export class RelSidebarGroupLabel extends LitElement {
875
+ static styles = css`
876
+ :host {
877
+ display: block;
878
+ font-size: var(--rel-fontSizes-xs, 10px);
879
+ font-weight: var(--rel-fontWeights-semibold, 600);
880
+ color: var(--rel-text-placeholder, #a9a9a9);
881
+ text-transform: uppercase;
882
+ letter-spacing: 0.05em;
883
+ padding: var(--rel-spacing-space-xs, 8px) var(--rel-spacing-space-sm, 12px) var(--rel-spacing-space-2xs, 4px);
884
+ margin-bottom: var(--rel-spacing-space-2xs, 4px);
885
+ }
886
+
887
+ :host(.collapsed) {
888
+ text-align: center;
889
+ font-size: var(--rel-fontSizes-xs, 10px);
890
+ padding: var(--rel-spacing-space-xs, 8px) var(--rel-spacing-space-2xs, 4px) var(--rel-spacing-space-2xs, 4px);
891
+ }
892
+ `;
893
+
894
+ render() {
895
+ return html`<slot></slot>`;
896
+ }
897
+ }
898
+
899
+ @customElement("rel-sidebar-group-content")
900
+ export class RelSidebarGroupContent extends LitElement {
901
+ static styles = css`
902
+ :host {
903
+ display: block;
904
+ }
905
+ `;
906
+
907
+ render() {
908
+ return html`<slot></slot>`;
909
+ }
910
+ }
911
+
912
+ // ============================================================================
913
+ // Menu Primitives
914
+ // ============================================================================
915
+
916
+ @customElement("rel-sidebar-menu")
917
+ export class RelSidebarMenu extends LitElement {
918
+ static styles = css`
919
+ :host {
920
+ display: block;
921
+ list-style: none;
922
+ margin: 0;
923
+ padding: 0;
924
+ }
925
+ `;
926
+
927
+ connectedCallback() {
928
+ super.connectedCallback();
929
+ this.addEventListener("keydown", this.handleKeyDown);
930
+ }
931
+
932
+ disconnectedCallback() {
933
+ super.disconnectedCallback();
934
+ this.removeEventListener("keydown", this.handleKeyDown);
935
+ }
936
+
937
+ private handleKeyDown = (e: KeyboardEvent) => {
938
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
939
+
940
+ e.preventDefault();
941
+ const items = Array.from(
942
+ this.querySelectorAll("rel-sidebar-menu-button:not([disabled])"),
943
+ ) as HTMLElement[];
944
+ if (items.length === 0) return;
945
+
946
+ const active = items.findIndex(
947
+ (item) => item.shadowRoot?.activeElement || item === document.activeElement,
948
+ );
949
+ let next: number;
950
+ if (e.key === "ArrowDown") {
951
+ next = active < items.length - 1 ? active + 1 : 0;
952
+ } else {
953
+ next = active > 0 ? active - 1 : items.length - 1;
954
+ }
955
+
956
+ const btn = items[next]?.shadowRoot?.querySelector("button") as HTMLElement | null;
957
+ btn?.focus();
958
+ };
959
+
960
+ render() {
961
+ return html`<slot></slot>`;
962
+ }
963
+ }
964
+
965
+ @customElement("rel-sidebar-menu-item")
966
+ export class RelSidebarMenuItem extends LitElement {
967
+ static styles = css`
968
+ :host {
969
+ display: block;
970
+ margin-bottom: var(--rel-spacing-space-2xs, 4px);
971
+ padding: 0 var(--rel-spacing-space-xs, 8px);
972
+ }
973
+ `;
974
+
975
+ render() {
976
+ return html`<slot></slot>`;
977
+ }
978
+ }
979
+
980
+ @customElement("rel-sidebar-menu-button")
981
+ export class RelSidebarMenuButton extends LitElement {
982
+ @property({ type: Boolean }) active = false;
983
+ @property({ type: Boolean }) disabled = false;
984
+ @property({ type: String }) tooltip = "";
985
+
986
+ @state() private isCollapsed = false;
987
+
988
+ private provider: RelSidebarProvider | null = null;
989
+
990
+ static styles = css`
991
+ :host {
992
+ display: block;
993
+ }
994
+
995
+ button {
996
+ width: 100%;
997
+ display: flex;
998
+ align-items: center;
999
+ gap: var(--rel-spacing-space-sm, 12px);
1000
+ padding: var(--rel-spacing-space-xs, 8px) var(--rel-spacing-space-sm, 12px);
1001
+ min-height: 40px;
1002
+ background: none;
1003
+ border: none;
1004
+ border-radius: var(--rel-borders-radius-border-radius-200, 8px);
1005
+ cursor: pointer;
1006
+ font-family: var(--rel-fontFamilies-primary, sans-serif);
1007
+ font-size: var(--rel-fontSizes-md, 14px);
1008
+ font-weight: var(--rel-fontWeights-medium, 500);
1009
+ color: var(--rel-text-primary, #333);
1010
+ transition: background 0.15s ease, color 0.15s ease;
1011
+ text-align: left;
1012
+ position: relative;
1013
+ }
1014
+
1015
+ button:hover:not([disabled]) {
1016
+ background: var(--rel-surface-surface, #f5f5f5);
1017
+ color: var(--rel-text-heading, #2b2b2b);
1018
+ }
1019
+
1020
+ button:active:not([disabled]) {
1021
+ background: var(--rel-border-default, #ddd);
1022
+ }
1023
+
1024
+ button:focus-visible {
1025
+ outline: 2px solid var(--rel-border-info, #0091ea);
1026
+ outline-offset: -2px;
1027
+ }
1028
+
1029
+ button[disabled] {
1030
+ opacity: 0.5;
1031
+ cursor: not-allowed;
1032
+ }
1033
+
1034
+ button.active {
1035
+ background: var(--rel-surface-surface, #f5f5f5);
1036
+ color: var(--rel-text-heading, #2b2b2b);
1037
+ font-weight: var(--rel-fontWeights-semibold, 600);
1038
+ }
1039
+
1040
+ button.active::before {
1041
+ content: "";
1042
+ position: absolute;
1043
+ left: 0;
1044
+ top: 50%;
1045
+ transform: translateY(-50%);
1046
+ width: 3px;
1047
+ height: 20px;
1048
+ background: var(--rel-colors-primary-500, #0091ea);
1049
+ border-radius: 0 2px 2px 0;
1050
+ }
1051
+
1052
+ button.collapsed {
1053
+ justify-content: center;
1054
+ padding: var(--rel-spacing-space-xs, 8px);
1055
+ min-height: 40px;
1056
+ }
1057
+
1058
+ button.collapsed.active::before {
1059
+ display: none;
1060
+ }
1061
+
1062
+ button.collapsed.active {
1063
+ background: var(--rel-surface-surface, #f5f5f5);
1064
+ }
1065
+
1066
+ .icon {
1067
+ flex-shrink: 0;
1068
+ display: flex;
1069
+ align-items: center;
1070
+ justify-content: center;
1071
+ width: 18px;
1072
+ height: 18px;
1073
+ }
1074
+
1075
+ .label {
1076
+ flex: 1;
1077
+ white-space: nowrap;
1078
+ overflow: hidden;
1079
+ text-overflow: ellipsis;
1080
+ line-height: 1.4;
1081
+ }
1082
+
1083
+ button.collapsed .label {
1084
+ display: none;
1085
+ }
1086
+ `;
1087
+
1088
+ connectedCallback() {
1089
+ super.connectedCallback();
1090
+ this.findProvider();
1091
+ }
1092
+
1093
+ disconnectedCallback() {
1094
+ super.disconnectedCallback();
1095
+ if (this.provider) {
1096
+ this.provider.removeEventListener(
1097
+ "rel-sidebar-state",
1098
+ this.handleStateChange,
1099
+ );
1100
+ }
1101
+ }
1102
+
1103
+ private findProvider() {
1104
+ let parent = this.parentElement;
1105
+ while (parent) {
1106
+ if (parent instanceof RelSidebarProvider) {
1107
+ this.provider = parent;
1108
+ break;
1109
+ }
1110
+ parent = parent.parentElement;
1111
+ }
1112
+
1113
+ if (this.provider) {
1114
+ this.provider.addEventListener(
1115
+ "rel-sidebar-state",
1116
+ this.handleStateChange,
1117
+ );
1118
+ this.syncState();
1119
+ }
1120
+ }
1121
+
1122
+ private syncState() {
1123
+ if (!this.provider) return;
1124
+ this.isCollapsed = this.provider.activeCollapsed && !this.provider.isMobile;
1125
+ }
1126
+
1127
+ private handleStateChange = () => {
1128
+ this.syncState();
1129
+ };
1130
+
1131
+ render() {
1132
+ const buttonClass = [
1133
+ this.active ? "active" : "",
1134
+ this.isCollapsed ? "collapsed" : "",
1135
+ ]
1136
+ .filter(Boolean)
1137
+ .join(" ");
1138
+
1139
+ const tooltipTitle = this.isCollapsed && this.tooltip ? this.tooltip : "";
1140
+
1141
+ return html`
1142
+ <button
1143
+ class="${buttonClass}"
1144
+ ?disabled="${this.disabled}"
1145
+ title="${tooltipTitle}"
1146
+ aria-current="${this.active ? "page" : "false"}"
1147
+ part="button"
1148
+ >
1149
+ <span class="icon" part="icon">
1150
+ <slot name="icon"></slot>
1151
+ </span>
1152
+ <span class="label" part="label">
1153
+ <slot></slot>
1154
+ </span>
1155
+ </button>
1156
+ `;
1157
+ }
1158
+ }