@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,374 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import type { RelResizablePanel } from "./rel-resizable-panel";
4
+ import type { RelResizableHandle } from "./rel-resizable-handle";
5
+
6
+ @customElement("rel-resizable-group")
7
+ export class RelResizableGroup extends LitElement {
8
+ /* =======================
9
+ * PUBLIC API
10
+ * ======================= */
11
+
12
+ @property({ type: String }) direction: "horizontal" | "vertical" = "horizontal";
13
+
14
+ /* =======================
15
+ * INTERNAL STATE
16
+ * ======================= */
17
+
18
+ private panels: RelResizablePanel[] = [];
19
+ private handles: RelResizableHandle[] = [];
20
+ private isDragging = false;
21
+ private activeHandleIndex = -1;
22
+ private startPointerPos = 0;
23
+ private startSizes: number[] = [];
24
+
25
+ /* =======================
26
+ * STYLES
27
+ * ======================= */
28
+
29
+ static styles = css`
30
+ :host {
31
+ display: flex;
32
+ width: 100%;
33
+ height: 100%;
34
+ position: relative;
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ :host([direction="horizontal"]) {
39
+ flex-direction: row;
40
+ }
41
+
42
+ :host([direction="vertical"]) {
43
+ flex-direction: column;
44
+ }
45
+
46
+ ::slotted(rel-resizable-panel) {
47
+ flex-shrink: 0;
48
+ }
49
+ `;
50
+
51
+ /* =======================
52
+ * LIFECYCLE
53
+ * ======================= */
54
+
55
+ connectedCallback() {
56
+ super.connectedCallback();
57
+ this.addEventListener("pointerdown", this.onPointerDown);
58
+ this.addEventListener("keydown", this.onKeyDown);
59
+ }
60
+
61
+ disconnectedCallback() {
62
+ super.disconnectedCallback();
63
+ this.removeEventListener("pointerdown", this.onPointerDown);
64
+ this.removeEventListener("keydown", this.onKeyDown);
65
+ this.removeGlobalListeners();
66
+ }
67
+
68
+ firstUpdated() {
69
+ this.initializeLayout();
70
+ }
71
+
72
+ /* =======================
73
+ * INITIALIZATION
74
+ * ======================= */
75
+
76
+ private initializeLayout() {
77
+ // Query light DOM for panels and handles
78
+ this.panels = Array.from(
79
+ this.querySelectorAll(":scope > rel-resizable-panel")
80
+ ) as RelResizablePanel[];
81
+ this.handles = Array.from(
82
+ this.querySelectorAll(":scope > rel-resizable-handle")
83
+ ) as RelResizableHandle[];
84
+
85
+ // Set handle indices and orientations
86
+ this.handles.forEach((handle, idx) => {
87
+ handle.handleIndex = idx;
88
+ handle.orientation = this.direction;
89
+ });
90
+
91
+ // Calculate initial sizes
92
+ const sizes = this.calculateInitialSizes();
93
+ this.applySizes(sizes);
94
+ }
95
+
96
+ private calculateInitialSizes(): number[] {
97
+ const sizes: number[] = [];
98
+ let specifiedTotal = 0;
99
+ let unspecifiedCount = 0;
100
+
101
+ // First pass: collect specified sizes
102
+ this.panels.forEach((panel) => {
103
+ if (panel.defaultSize !== undefined) {
104
+ sizes.push(panel.defaultSize);
105
+ specifiedTotal += panel.defaultSize;
106
+ } else {
107
+ sizes.push(0);
108
+ unspecifiedCount++;
109
+ }
110
+ });
111
+
112
+ // Second pass: distribute remaining space to unspecified panels
113
+ if (unspecifiedCount > 0) {
114
+ const remaining = Math.max(0, 100 - specifiedTotal);
115
+ const perPanel = remaining / unspecifiedCount;
116
+ sizes.forEach((size, idx) => {
117
+ if (size === 0) {
118
+ sizes[idx] = perPanel;
119
+ }
120
+ });
121
+ }
122
+
123
+ // Normalize to 100%
124
+ const total = sizes.reduce((sum, s) => sum + s, 0);
125
+ if (total > 0) {
126
+ return sizes.map((s) => (s / total) * 100);
127
+ }
128
+
129
+ // Fallback: equal distribution
130
+ return this.panels.map(() => 100 / this.panels.length);
131
+ }
132
+
133
+ /* =======================
134
+ * POINTER HANDLING
135
+ * ======================= */
136
+
137
+ private onPointerDown = (e: PointerEvent) => {
138
+ const target = e.composedPath()[0] as HTMLElement;
139
+ if (target.tagName !== "REL-RESIZABLE-HANDLE") return;
140
+
141
+ const handle = target as RelResizableHandle;
142
+ const handleIndex = this.handles.indexOf(handle);
143
+ if (handleIndex === -1) return;
144
+
145
+ e.preventDefault();
146
+ this.startDrag(handleIndex, e);
147
+ };
148
+
149
+ private startDrag(handleIndex: number, e: PointerEvent) {
150
+ this.isDragging = true;
151
+ this.activeHandleIndex = handleIndex;
152
+ this.startPointerPos = this.direction === "horizontal" ? e.clientX : e.clientY;
153
+ this.startSizes = this.panels.map((p) => p.currentSize);
154
+
155
+ const handle = this.handles[handleIndex];
156
+ handle.setAttribute("data-dragging", "");
157
+
158
+ this.addGlobalListeners();
159
+
160
+ // Capture pointer
161
+ (e.target as HTMLElement).setPointerCapture(e.pointerId);
162
+ }
163
+
164
+ private onPointerMove = (e: PointerEvent) => {
165
+ if (!this.isDragging || this.activeHandleIndex === -1) return;
166
+
167
+ e.preventDefault();
168
+
169
+ const currentPos = this.direction === "horizontal" ? e.clientX : e.clientY;
170
+ const delta = currentPos - this.startPointerPos;
171
+
172
+ this.applyDragResize(delta);
173
+ };
174
+
175
+ private onPointerUp = (e: PointerEvent) => {
176
+ if (!this.isDragging) return;
177
+
178
+ e.preventDefault();
179
+
180
+ const handle = this.handles[this.activeHandleIndex];
181
+ if (handle) {
182
+ handle.removeAttribute("data-dragging");
183
+ }
184
+
185
+ this.isDragging = false;
186
+ this.activeHandleIndex = -1;
187
+
188
+ this.removeGlobalListeners();
189
+
190
+ // Dispatch resize-end event
191
+ this.dispatchResizeEvent("rel-resize-end");
192
+ };
193
+
194
+ private applyDragResize(deltaPixels: number) {
195
+ const rect = this.getBoundingClientRect();
196
+ const totalSize = this.direction === "horizontal" ? rect.width : rect.height;
197
+ const deltaPercent = (deltaPixels / totalSize) * 100;
198
+
199
+ const leftIdx = this.activeHandleIndex;
200
+ const rightIdx = this.activeHandleIndex + 1;
201
+
202
+ if (leftIdx < 0 || rightIdx >= this.panels.length) return;
203
+
204
+ const leftPanel = this.panels[leftIdx];
205
+ const rightPanel = this.panels[rightIdx];
206
+
207
+ let newLeftSize = this.startSizes[leftIdx] + deltaPercent;
208
+ let newRightSize = this.startSizes[rightIdx] - deltaPercent;
209
+
210
+ // Clamp to min/max
211
+ newLeftSize = Math.max(leftPanel.minSize, Math.min(leftPanel.maxSize, newLeftSize));
212
+ newRightSize = Math.max(rightPanel.minSize, Math.min(rightPanel.maxSize, newRightSize));
213
+
214
+ // Ensure total is preserved
215
+ const pairTotal = this.startSizes[leftIdx] + this.startSizes[rightIdx];
216
+ const adjustedTotal = newLeftSize + newRightSize;
217
+ if (adjustedTotal !== pairTotal) {
218
+ const ratio = pairTotal / adjustedTotal;
219
+ newLeftSize *= ratio;
220
+ newRightSize *= ratio;
221
+ }
222
+
223
+ // Update sizes
224
+ const newSizes = [...this.startSizes];
225
+ newSizes[leftIdx] = newLeftSize;
226
+ newSizes[rightIdx] = newRightSize;
227
+
228
+ this.applySizes(newSizes);
229
+
230
+ // Update handle aria value
231
+ const handle = this.handles[this.activeHandleIndex];
232
+ if (handle) {
233
+ const splitPercent = (newLeftSize / pairTotal) * 100;
234
+ handle.updateAriaValue(splitPercent);
235
+ }
236
+
237
+ // Dispatch resize event
238
+ this.dispatchResizeEvent("rel-resize");
239
+ }
240
+
241
+ /* =======================
242
+ * KEYBOARD HANDLING
243
+ * ======================= */
244
+
245
+ private onKeyDown = (e: KeyboardEvent) => {
246
+ const target = e.composedPath()[0] as HTMLElement;
247
+ if (target.tagName !== "REL-RESIZABLE-HANDLE") return;
248
+
249
+ const handle = target as RelResizableHandle;
250
+ const handleIndex = this.handles.indexOf(handle);
251
+ if (handleIndex === -1) return;
252
+
253
+ const isHorizontal = this.direction === "horizontal";
254
+ const step = e.shiftKey ? 5 : 2;
255
+ let applied = false;
256
+
257
+ if (isHorizontal && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
258
+ e.preventDefault();
259
+ const delta = e.key === "ArrowLeft" ? -step : step;
260
+ this.applyKeyboardResize(handleIndex, delta);
261
+ applied = true;
262
+ } else if (!isHorizontal && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
263
+ e.preventDefault();
264
+ const delta = e.key === "ArrowUp" ? -step : step;
265
+ this.applyKeyboardResize(handleIndex, delta);
266
+ applied = true;
267
+ }
268
+
269
+ if (applied) {
270
+ this.dispatchResizeEvent("rel-resize-end");
271
+ }
272
+ };
273
+
274
+ private applyKeyboardResize(handleIndex: number, deltaPercent: number) {
275
+ const leftIdx = handleIndex;
276
+ const rightIdx = handleIndex + 1;
277
+
278
+ if (leftIdx < 0 || rightIdx >= this.panels.length) return;
279
+
280
+ const leftPanel = this.panels[leftIdx];
281
+ const rightPanel = this.panels[rightIdx];
282
+ const currentSizes = this.panels.map((p) => p.currentSize);
283
+
284
+ let newLeftSize = currentSizes[leftIdx] + deltaPercent;
285
+ let newRightSize = currentSizes[rightIdx] - deltaPercent;
286
+
287
+ // Clamp to min/max
288
+ newLeftSize = Math.max(leftPanel.minSize, Math.min(leftPanel.maxSize, newLeftSize));
289
+ newRightSize = Math.max(rightPanel.minSize, Math.min(rightPanel.maxSize, newRightSize));
290
+
291
+ // Ensure total is preserved
292
+ const pairTotal = currentSizes[leftIdx] + currentSizes[rightIdx];
293
+ const adjustedTotal = newLeftSize + newRightSize;
294
+ if (adjustedTotal !== pairTotal) {
295
+ const ratio = pairTotal / adjustedTotal;
296
+ newLeftSize *= ratio;
297
+ newRightSize *= ratio;
298
+ }
299
+
300
+ // Update sizes
301
+ const newSizes = [...currentSizes];
302
+ newSizes[leftIdx] = newLeftSize;
303
+ newSizes[rightIdx] = newRightSize;
304
+
305
+ this.applySizes(newSizes);
306
+
307
+ // Update handle aria value
308
+ const handle = this.handles[handleIndex];
309
+ if (handle) {
310
+ const splitPercent = (newLeftSize / pairTotal) * 100;
311
+ handle.updateAriaValue(splitPercent);
312
+ }
313
+ }
314
+
315
+ /* =======================
316
+ * SIZE APPLICATION
317
+ * ======================= */
318
+
319
+ private applySizes(sizes: number[]) {
320
+ this.panels.forEach((panel, idx) => {
321
+ const size = sizes[idx] || 0;
322
+ panel.currentSize = size;
323
+ panel.style.flexBasis = `${size}%`;
324
+ panel.style.flexGrow = "0";
325
+ panel.style.flexShrink = "0";
326
+ });
327
+ }
328
+
329
+ /* =======================
330
+ * EVENTS
331
+ * ======================= */
332
+
333
+ private dispatchResizeEvent(eventName: string) {
334
+ const sizes = this.panels.map((p) => p.currentSize);
335
+ this.dispatchEvent(
336
+ new CustomEvent(eventName, {
337
+ detail: {
338
+ direction: this.direction,
339
+ sizes,
340
+ },
341
+ bubbles: true,
342
+ composed: true,
343
+ })
344
+ );
345
+ }
346
+
347
+ /* =======================
348
+ * GLOBAL LISTENERS
349
+ * ======================= */
350
+
351
+ private addGlobalListeners() {
352
+ document.addEventListener("pointermove", this.onPointerMove);
353
+ document.addEventListener("pointerup", this.onPointerUp);
354
+ }
355
+
356
+ private removeGlobalListeners() {
357
+ document.removeEventListener("pointermove", this.onPointerMove);
358
+ document.removeEventListener("pointerup", this.onPointerUp);
359
+ }
360
+
361
+ /* =======================
362
+ * RENDER
363
+ * ======================= */
364
+
365
+ render() {
366
+ return html`<slot></slot>`;
367
+ }
368
+ }
369
+
370
+ declare global {
371
+ interface HTMLElementTagNameMap {
372
+ "rel-resizable-group": RelResizableGroup;
373
+ }
374
+ }
@@ -0,0 +1,149 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+
4
+ @customElement("rel-resizable-handle")
5
+ export class RelResizableHandle extends LitElement {
6
+ /* =======================
7
+ * PUBLIC API
8
+ * ======================= */
9
+
10
+ @property({ type: String, reflect: true }) orientation: "horizontal" | "vertical" = "horizontal";
11
+
12
+ /* =======================
13
+ * INTERNAL STATE
14
+ * ======================= */
15
+
16
+ /** Index of this handle in the parent group */
17
+ public handleIndex = -1;
18
+
19
+ /** Current split value (0-100) for aria-valuenow */
20
+ public splitValue = 50;
21
+
22
+ /* =======================
23
+ * STYLES
24
+ * ======================= */
25
+
26
+ static styles = css`
27
+ :host {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ flex-shrink: 0;
32
+ position: relative;
33
+ outline: none;
34
+ background: var(--rel-colors-gray-border);
35
+ transition: background 0.15s ease;
36
+ }
37
+
38
+ :host([orientation="horizontal"]) {
39
+ width: var(--rel-borders-width-stroke-100);
40
+ min-width: var(--rel-borders-width-stroke-100);
41
+ cursor: col-resize;
42
+ padding: 0 var(--rel-spacing-space-xs);
43
+ margin: 0 calc(var(--rel-spacing-space-xs) * -1);
44
+ }
45
+
46
+ :host([orientation="vertical"]) {
47
+ height: var(--rel-borders-width-stroke-100);
48
+ min-height: var(--rel-borders-width-stroke-100);
49
+ cursor: row-resize;
50
+ padding: var(--rel-spacing-space-xs) 0;
51
+ margin: calc(var(--rel-spacing-space-xs) * -1) 0;
52
+ }
53
+
54
+ :host(:hover),
55
+ :host(:focus-visible) {
56
+ background: var(--rel-colors-blue-primary);
57
+ }
58
+
59
+ :host(:focus-visible) {
60
+ outline: 2px solid var(--rel-colors-blue-primary);
61
+ outline-offset: 2px;
62
+ }
63
+
64
+ .grabber {
65
+ background: var(--rel-colors-gray-placeholder);
66
+ border-radius: var(--rel-borders-radius-border-radius-500);
67
+ transition: background 0.15s ease;
68
+ }
69
+
70
+ :host([orientation="horizontal"]) .grabber {
71
+ width: 4px;
72
+ height: 24px;
73
+ }
74
+
75
+ :host([orientation="vertical"]) .grabber {
76
+ width: 24px;
77
+ height: 4px;
78
+ }
79
+
80
+ :host(:hover) .grabber,
81
+ :host(:focus-visible) .grabber {
82
+ background: var(--rel-colors-white);
83
+ }
84
+
85
+ :host([data-dragging]) {
86
+ background: var(--rel-colors-blue-primary);
87
+ }
88
+
89
+ :host([data-dragging]) .grabber {
90
+ background: var(--rel-colors-white);
91
+ }
92
+
93
+ @media (prefers-reduced-motion: reduce) {
94
+ :host {
95
+ transition: none;
96
+ }
97
+
98
+ .grabber {
99
+ transition: none;
100
+ }
101
+ }
102
+ `;
103
+
104
+ /* =======================
105
+ * LIFECYCLE
106
+ * ======================= */
107
+
108
+ connectedCallback() {
109
+ super.connectedCallback();
110
+ this.setAttribute("tabindex", "0");
111
+ this.setAttribute("role", "separator");
112
+ this.updateAriaOrientation();
113
+ }
114
+
115
+ updated(changed: Map<string, unknown>) {
116
+ if (changed.has("orientation")) {
117
+ this.updateAriaOrientation();
118
+ }
119
+ }
120
+
121
+ /* =======================
122
+ * ARIA
123
+ * ======================= */
124
+
125
+ private updateAriaOrientation() {
126
+ this.setAttribute("aria-orientation", this.orientation);
127
+ }
128
+
129
+ public updateAriaValue(value: number) {
130
+ this.splitValue = value;
131
+ this.setAttribute("aria-valuenow", String(Math.round(value)));
132
+ this.setAttribute("aria-valuemin", "0");
133
+ this.setAttribute("aria-valuemax", "100");
134
+ }
135
+
136
+ /* =======================
137
+ * RENDER
138
+ * ======================= */
139
+
140
+ render() {
141
+ return html`<div class="grabber"></div>`;
142
+ }
143
+ }
144
+
145
+ declare global {
146
+ interface HTMLElementTagNameMap {
147
+ "rel-resizable-handle": RelResizableHandle;
148
+ }
149
+ }
@@ -0,0 +1,47 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+
4
+ @customElement("rel-resizable-panel")
5
+ export class RelResizablePanel extends LitElement {
6
+ /* =======================
7
+ * PUBLIC API
8
+ * ======================= */
9
+
10
+ @property({ type: Number, attribute: "defaultsize" }) defaultSize?: number;
11
+ @property({ type: Number, attribute: "minsize" }) minSize = 10;
12
+ @property({ type: Number, attribute: "maxsize" }) maxSize = 90;
13
+
14
+ /* =======================
15
+ * INTERNAL STATE
16
+ * ======================= */
17
+
18
+ /** Current computed size in percentage */
19
+ public currentSize = 0;
20
+
21
+ /* =======================
22
+ * STYLES
23
+ * ======================= */
24
+
25
+ static styles = css`
26
+ :host {
27
+ display: flex;
28
+ flex-direction: column;
29
+ overflow: auto;
30
+ box-sizing: border-box;
31
+ }
32
+ `;
33
+
34
+ /* =======================
35
+ * RENDER
36
+ * ======================= */
37
+
38
+ render() {
39
+ return html`<slot></slot>`;
40
+ }
41
+ }
42
+
43
+ declare global {
44
+ interface HTMLElementTagNameMap {
45
+ "rel-resizable-panel": RelResizablePanel;
46
+ }
47
+ }
@@ -0,0 +1,141 @@
1
+ # rel-segmented-button
2
+
3
+ ## Overview
4
+ A segmented button group for mutually exclusive selections. Displays multiple options as connected buttons with a single active selection. Supports size variants, alignment options, and individual option states (disabled).
5
+
6
+ ## Usage
7
+ ```html
8
+ <!-- Basic segmented button -->
9
+ <rel-segmented-button id="viewMode" active="grid"></rel-segmented-button>
10
+ <script>
11
+ const viewMode = document.getElementById('viewMode');
12
+ viewMode.options = [
13
+ { label: 'List', value: 'list' },
14
+ { label: 'Grid', value: 'grid' },
15
+ { label: 'Table', value: 'table' }
16
+ ];
17
+ </script>
18
+
19
+ <!-- Small size with alignment -->
20
+ <rel-segmented-button
21
+ id="textAlign"
22
+ size="small"
23
+ align="middle"
24
+ active="center">
25
+ </rel-segmented-button>
26
+ <script>
27
+ const textAlign = document.getElementById('textAlign');
28
+ textAlign.options = [
29
+ { label: 'Left', value: 'left' },
30
+ { label: 'Center', value: 'center' },
31
+ { label: 'Right', value: 'right' }
32
+ ];
33
+ </script>
34
+
35
+ <!-- With disabled option -->
36
+ <rel-segmented-button id="planSelector"></rel-segmented-button>
37
+ <script>
38
+ const planSelector = document.getElementById('planSelector');
39
+ planSelector.options = [
40
+ { label: 'Free', value: 'free' },
41
+ { label: 'Pro', value: 'pro' },
42
+ { label: 'Enterprise', value: 'enterprise', disabled: true }
43
+ ];
44
+ planSelector.active = 'free';
45
+ </script>
46
+
47
+ <!-- Listen for changes -->
48
+ <rel-segmented-button id="sortOrder"></rel-segmented-button>
49
+ <script>
50
+ const sortOrder = document.getElementById('sortOrder');
51
+ sortOrder.options = [
52
+ { label: 'Newest', value: 'newest' },
53
+ { label: 'Oldest', value: 'oldest' },
54
+ { label: 'Popular', value: 'popular' }
55
+ ];
56
+ sortOrder.addEventListener('changed', (e) => {
57
+ console.log('Selected:', e.detail.value);
58
+ });
59
+ </script>
60
+ ```
61
+
62
+ ## API
63
+
64
+ ### Attributes/Properties
65
+ | Name | Type | Default | Description |
66
+ |------|------|---------|-------------|
67
+ | `options` | `SegmentOption[]` | `[]` | Array of segment options (see SegmentOption interface below) |
68
+ | `active` | `string` | `""` | Value of the currently active segment |
69
+ | `size` | `"small" \| "medium"` | `"medium"` | Size variant |
70
+ | `align` | `"left" \| "middle" \| "right"` | `"left"` | Horizontal alignment of the button group |
71
+
72
+ ### SegmentOption Interface
73
+ ```typescript
74
+ interface SegmentOption {
75
+ label: string; // Display text for the button
76
+ value: string; // Unique value identifier
77
+ disabled?: boolean; // Optional: disable this specific segment
78
+ }
79
+ ```
80
+
81
+ ## Events
82
+ | Event | Detail | Description |
83
+ |-------|--------|-------------|
84
+ | `changed` | `{ value: string }` | Fired when a different segment is selected |
85
+
86
+ ## Slots
87
+ Default slot not used.
88
+
89
+ ## CSS Parts
90
+ None exposed.
91
+
92
+ ## Tokens / CSS Variables
93
+
94
+ ### Semantic Tokens
95
+ - `--rel-fontFamilies-primary` - Font family
96
+ - `--rel-fontSizes-xs`, `--rel-fontSizes-sm` - Font sizes for small and medium variants
97
+ - `--rel-fontWeights-medium` - Font weight for segment labels
98
+
99
+ ### Spacing Tokens
100
+ - `--rel-spacing-space-3xs`, `--rel-spacing-space-2xs` - Vertical padding for sizes
101
+ - `--rel-spacing-space-xs`, `--rel-spacing-space-sm` - Horizontal padding for sizes
102
+
103
+ ### Color Tokens
104
+ - `--rel-colors-white` - Default segment background
105
+ - `--rel-colors-gray-border` - Outer border and segment dividers
106
+ - `--rel-text-heading` - Active segment background and default text color
107
+ - `--rel-surface-surface` - Hover and disabled background
108
+ - `--rel-text-disabled` - Disabled text color
109
+ - `--rel-border-info` - Focus outline color
110
+
111
+ ### Border Tokens
112
+ - `--rel-borders-radius-border-radius-300` - Border radius for the group
113
+
114
+ ## Accessibility
115
+ - Uses native `<button>` elements for proper keyboard navigation
116
+ - Focus visible state with inner outline for keyboard users
117
+ - Disabled segments use `pointer-events: none` and reduced opacity
118
+ - Each segment can receive focus independently
119
+
120
+ ### Keyboard Interactions
121
+ - `Tab` - Move focus between segments
122
+ - `Space/Enter` - Activate focused segment
123
+ - `Arrow Left/Right` - Navigate between segments (browser default)
124
+
125
+ ### Focus Behavior
126
+ - Focus outline appears inside the button boundary with `-2px` offset
127
+ - Uses info border color for high contrast
128
+ - Active button has `z-index: 1` to ensure focus ring is visible over adjacent segments
129
+
130
+ ## Notes / Edge Cases
131
+ - The component is `display: inline-block` and sizes to fit content
132
+ - All segments are connected with shared borders (2px solid gray)
133
+ - The last segment has no right border to avoid doubling with the outer container border
134
+ - Active segment has inverted colors (dark background, white text)
135
+ - Hovering a non-active, non-disabled segment shows a light gray background
136
+ - Hovering the active segment does not change its appearance
137
+ - Disabled segments show reduced opacity (0.5), gray background, and are non-interactive
138
+ - The `align` property uses flexbox justify-content (left → flex-start, middle → center, right → flex-end)
139
+ - Size variants affect both font size and padding
140
+ - Smooth transitions (0.2s ease-in-out) apply to all visual changes
141
+ - Clicking a disabled option will not change the `active` value or dispatch events