@reliability-design/web 1.0.3 → 1.1.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 (126) hide show
  1. package/dist/components/rel-accordion/README.md +132 -0
  2. package/dist/components/rel-accordion/rel-accordion.ts +47 -16
  3. package/dist/components/rel-alert/README.md +141 -0
  4. package/dist/components/rel-alert/rel-alert.ts +101 -20
  5. package/dist/components/rel-avatar/README.md +119 -0
  6. package/dist/components/rel-avatar/rel-avatar.ts +3 -2
  7. package/dist/components/rel-badge/README.md +86 -0
  8. package/dist/components/rel-badge/rel-badge.ts +11 -7
  9. package/dist/components/rel-breadcrumb/README.md +102 -0
  10. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +12 -6
  11. package/dist/components/rel-button/README.md +130 -0
  12. package/dist/components/rel-button/rel-button.ts +64 -8
  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 +12 -3
  18. package/dist/components/rel-cardmedia/README.md +127 -0
  19. package/dist/components/rel-cardmedia/rel-cardmedia.ts +14 -4
  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 +468 -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 +51 -12
  28. package/dist/components/rel-chip/README.md +114 -0
  29. package/dist/components/rel-chip/rel-chip.ts +83 -9
  30. package/dist/components/rel-container/README.md +99 -0
  31. package/dist/components/rel-date-picker/README.md +253 -0
  32. package/dist/components/rel-date-picker/index.ts +1 -0
  33. package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
  34. package/dist/components/rel-divider/README.md +100 -0
  35. package/dist/components/rel-divider/rel-divider.ts +9 -0
  36. package/dist/components/rel-drawer/README.md +200 -0
  37. package/dist/components/rel-drawer/index.ts +1 -0
  38. package/dist/components/rel-drawer/rel-drawer-footer.ts +29 -0
  39. package/dist/components/rel-drawer/rel-drawer.ts +612 -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-menu/README.md +151 -0
  44. package/dist/components/rel-menu/rel-menu.ts +64 -24
  45. package/dist/components/rel-menubar/README.md +329 -0
  46. package/dist/components/rel-menubar/index.ts +2 -0
  47. package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
  48. package/dist/components/rel-menubar/rel-menubar.ts +605 -0
  49. package/dist/components/rel-nav-menu/README.md +289 -0
  50. package/dist/components/rel-nav-menu/index.ts +3 -0
  51. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +240 -0
  52. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
  53. package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -0
  54. package/dist/components/rel-navbar/README.md +126 -0
  55. package/dist/components/rel-navbar/rel-navbar.ts +104 -0
  56. package/dist/components/rel-otp/README.md +440 -0
  57. package/dist/components/rel-otp/index.ts +1 -0
  58. package/dist/components/rel-otp/rel-otp.ts +504 -0
  59. package/dist/components/rel-pagination/README.md +410 -0
  60. package/dist/components/rel-pagination/index.ts +1 -0
  61. package/dist/components/rel-pagination/rel-pagination.ts +381 -0
  62. package/dist/components/rel-popover/README.md +568 -0
  63. package/dist/components/rel-popover/index.ts +2 -0
  64. package/dist/components/rel-popover/rel-popover-content.ts +55 -0
  65. package/dist/components/rel-popover/rel-popover.ts +330 -0
  66. package/dist/components/rel-progress/README.md +110 -0
  67. package/dist/components/rel-progress/rel-progress.ts +32 -5
  68. package/dist/components/rel-radio/README.md +120 -0
  69. package/dist/components/rel-radio/rel-radio.ts +31 -6
  70. package/dist/components/rel-resizable/README.md +140 -0
  71. package/dist/components/rel-resizable/index.ts +3 -0
  72. package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
  73. package/dist/components/rel-resizable/rel-resizable-handle.ts +145 -0
  74. package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
  75. package/dist/components/rel-segmented-button/README.md +141 -0
  76. package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
  77. package/dist/components/rel-select/README.md +107 -0
  78. package/dist/components/rel-select/rel-select.ts +28 -14
  79. package/dist/components/rel-sheet/README.md +224 -0
  80. package/dist/components/rel-sheet/rel-sheet.ts +684 -0
  81. package/dist/components/rel-sidebar/README.md +209 -0
  82. package/dist/components/rel-sidebar/rel-sidebar.ts +1166 -0
  83. package/dist/components/rel-slider/README.md +135 -0
  84. package/dist/components/rel-slider/rel-slider.ts +44 -9
  85. package/dist/components/rel-snackbar/README.md +134 -0
  86. package/dist/components/rel-snackbar/rel-snackbar.ts +109 -14
  87. package/dist/components/rel-stepper/README.md +160 -0
  88. package/dist/components/rel-stepper/rel-stepper.ts +38 -27
  89. package/dist/components/rel-switch/README.md +102 -0
  90. package/dist/components/rel-switch/rel-switch.ts +34 -12
  91. package/dist/components/rel-tab/README.md +163 -0
  92. package/dist/components/rel-tab/rel-tab.ts +79 -23
  93. package/dist/components/rel-table/README.md +178 -0
  94. package/dist/components/rel-table/rel-table-body.ts +21 -0
  95. package/dist/components/rel-table/rel-table-caption.ts +27 -0
  96. package/dist/components/rel-table/rel-table-cell.ts +42 -0
  97. package/dist/components/rel-table/rel-table-footer.ts +23 -0
  98. package/dist/components/rel-table/rel-table-head.ts +45 -0
  99. package/dist/components/rel-table/rel-table-header.ts +21 -0
  100. package/dist/components/rel-table/rel-table-row.ts +36 -0
  101. package/dist/components/rel-table/rel-table.ts +53 -0
  102. package/dist/components/rel-textfield/README.md +90 -0
  103. package/dist/components/rel-textfield/rel-textfield.ts +55 -39
  104. package/dist/components/rel-toast/README.md +148 -0
  105. package/dist/components/rel-toast/rel-toast.ts +63 -24
  106. package/dist/components/rel-tooltip/README.md +103 -0
  107. package/dist/components/rel-tooltip/rel-tooltip.ts +123 -80
  108. package/dist/components/rel-topbar/rel-topbar-actions.ts +23 -0
  109. package/dist/components/rel-topbar/rel-topbar-brand.ts +46 -0
  110. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +33 -0
  111. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
  112. package/dist/components/rel-topbar/rel-topbar-item.ts +315 -0
  113. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +23 -0
  114. package/dist/components/rel-topbar/rel-topbar-profile.ts +248 -0
  115. package/dist/components/rel-topbar/rel-topbar-utilities.ts +23 -0
  116. package/dist/components/rel-topbar/rel-topbar.ts +329 -0
  117. package/dist/index.d.mts +831 -7
  118. package/dist/index.d.ts +831 -7
  119. package/dist/index.js +9500 -1564
  120. package/dist/index.js.map +1 -1
  121. package/dist/index.mjs +9240 -1344
  122. package/dist/index.mjs.map +1 -1
  123. package/dist/styles/vars.css +187 -25
  124. package/dist/vars-TBB4BMSD.css +346 -0
  125. package/package.json +2 -1
  126. package/dist/vars-6ZIUPIU4.css +0 -184
@@ -0,0 +1,160 @@
1
+ # rel-stepper
2
+
3
+ ## Overview
4
+ A comprehensive stepper component for visualizing multi-step processes. Supports horizontal and vertical orientations, text positioning (below or right of indicator), two size variants, and three step states (done, progress, disabled). Features clickable steps, automatic connector lines, and checkmark icons for completed steps.
5
+
6
+ ## Usage
7
+ ```html
8
+ <!-- Basic horizontal stepper -->
9
+ <rel-stepper id="checkout"></rel-stepper>
10
+ <script>
11
+ const stepper = document.getElementById('checkout');
12
+ stepper.steps = [
13
+ { title: 'Cart', status: 'done' },
14
+ { title: 'Shipping', status: 'progress' },
15
+ { title: 'Payment', status: 'disabled' },
16
+ { title: 'Confirmation', status: 'disabled' }
17
+ ];
18
+ </script>
19
+
20
+ <!-- Vertical stepper with text to the right -->
21
+ <rel-stepper
22
+ id="onboarding"
23
+ orientation="vertical"
24
+ textAlign="right">
25
+ </rel-stepper>
26
+ <script>
27
+ const onboarding = document.getElementById('onboarding');
28
+ onboarding.steps = [
29
+ { title: 'Create Account', status: 'done' },
30
+ { title: 'Verify Email', status: 'done' },
31
+ { title: 'Profile Setup', status: 'progress' },
32
+ { title: 'Complete', status: 'disabled' }
33
+ ];
34
+ </script>
35
+
36
+ <!-- Small size horizontal stepper -->
37
+ <rel-stepper
38
+ id="wizard"
39
+ size="small"
40
+ textAlign="below">
41
+ </rel-stepper>
42
+ <script>
43
+ const wizard = document.getElementById('wizard');
44
+ wizard.steps = [
45
+ { title: 'Select', status: 'done' },
46
+ { title: 'Configure', status: 'progress' },
47
+ { title: 'Review', status: 'disabled' }
48
+ ];
49
+ </script>
50
+
51
+ <!-- Listen for step clicks -->
52
+ <rel-stepper id="interactive"></rel-stepper>
53
+ <script>
54
+ const interactive = document.getElementById('interactive');
55
+ interactive.steps = [
56
+ { title: 'Step 1', status: 'done' },
57
+ { title: 'Step 2', status: 'progress' },
58
+ { title: 'Step 3', status: 'disabled' }
59
+ ];
60
+ interactive.addEventListener('step-change', (e) => {
61
+ console.log('Clicked step:', e.detail.index, e.detail.step);
62
+ // Update step statuses or navigate to step content
63
+ });
64
+ </script>
65
+ ```
66
+
67
+ ## API
68
+
69
+ ### Attributes/Properties
70
+ | Name | Type | Default | Description |
71
+ |------|------|---------|-------------|
72
+ | `steps` | `Step[]` | `[]` | Array of step items (see Step interface below) |
73
+ | `textAlign` | `"right" \| "below"` | `"below"` | Position of step labels relative to indicators |
74
+ | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction |
75
+ | `size` | `"small" \| "large"` | `"large"` | Size variant (controls connector line length and spacing) |
76
+
77
+ ### Step Interface
78
+ ```typescript
79
+ interface Step {
80
+ title: string; // Step label text
81
+ status: "done" | "progress" | "disabled"; // Step state
82
+ }
83
+ ```
84
+
85
+ ## Events
86
+ | Event | Detail | Description |
87
+ |-------|--------|-------------|
88
+ | `step-change` | `{ index: number, step: Step }` | Fired when a non-disabled step is clicked |
89
+
90
+ ## Slots
91
+ Default slot not used.
92
+
93
+ ## CSS Parts
94
+ None exposed.
95
+
96
+ ## Tokens / CSS Variables
97
+
98
+ ### Semantic Tokens
99
+ - `--rel-fontFamilies-primary` - Font family
100
+ - `--rel-fontSizes-sm` - Label font size
101
+ - `--rel-fontWeights-medium` - Default label font weight
102
+ - `--rel-fontWeights-semibold` - Active/done label font weight
103
+
104
+ ### Spacing Tokens
105
+ - `--rel-spacing-space-2xs` - Gap between indicators and labels (text-right mode)
106
+
107
+ ### Color Tokens
108
+ - `--rel-colors-white` - Indicator background (progress, disabled states)
109
+ - `--rel-colors-black` - Active/done indicator background, text, and connector color
110
+ - `--rel-colors-gray-border` - Disabled indicator border and inactive connector
111
+ - `--rel-colors-gray-light` - Disabled indicator background
112
+ - `--rel-text-disabled` - Disabled label text color
113
+ - `--rel-text-primary` - Default label text color
114
+
115
+ ## Accessibility
116
+ - Steps can be clicked unless in disabled state
117
+ - Checkmark SVG uses `currentColor` for proper theming
118
+ - Focus states could be enhanced with keyboard navigation (not currently implemented)
119
+
120
+ ### Keyboard Interactions
121
+ - Click-based interaction only (no keyboard navigation implemented)
122
+
123
+ ### Focus Behavior
124
+ - No specific focus handling (steps are div elements, not buttons)
125
+
126
+ ## Notes / Edge Cases
127
+ - **Indicator states**:
128
+ - **done**: Black circle with white checkmark SVG icon
129
+ - **progress**: White circle with black border and step number
130
+ - **disabled**: Light gray circle with gray border and step number
131
+
132
+ - **Connector lines**:
133
+ - Positioned absolutely relative to the indicator
134
+ - **Horizontal**: 2px height, extends from indicator to next step
135
+ - **Vertical**: 2px width, extends from indicator to next step
136
+ - **done/progress** connectors are black; **disabled** connectors are gray
137
+ - Last step has no connector
138
+
139
+ - **Size variants** (control connector length and gap):
140
+ - **large horizontal**: 86px connector, 86px gap
141
+ - **small horizontal**: 60px connector, 60px gap
142
+ - **large vertical**: 86px connector, 86px gap
143
+ - **small vertical**: 42px connector, 42px gap
144
+
145
+ - **Text positioning**:
146
+ - **below**: Labels appear centered below indicators
147
+ - **right**: Labels appear to the right of indicators with a small gap
148
+
149
+ - **Layout combinations**:
150
+ - **horizontal + text-below**: Steps arranged in a row, labels centered below
151
+ - **horizontal + text-right**: Steps in a row, labels to the right of each indicator
152
+ - **vertical + text-below**: Steps stacked vertically, labels centered below each indicator
153
+ - **vertical + text-right**: Steps stacked vertically, labels to the right with indicators on the left
154
+
155
+ - The indicator is 22px × 22px with a 2px border
156
+ - Step numbers are 1-indexed (displayed as `index + 1`)
157
+ - Clicking a disabled step does nothing (no event dispatched)
158
+ - All transitions use `0.2s ease`
159
+ - The component uses flexbox extensively for layout management
160
+ - The stepper is `display: block` and `width: 100%`
@@ -126,8 +126,8 @@ export class RelStepper extends LitElement {
126
126
  display: flex;
127
127
  align-items: center;
128
128
  justify-content: center;
129
- width: 22px;
130
- height: 22px;
129
+ width: var(--rel-stepper-indicator-size, 22px);
130
+ height: var(--rel-stepper-indicator-size, 22px);
131
131
  border: 2px solid var(--rel-colors-gray-border);
132
132
  background: var(--rel-colors-white);
133
133
  border-radius: 50%;
@@ -151,7 +151,7 @@ export class RelStepper extends LitElement {
151
151
  .step-label {
152
152
  white-space: nowrap;
153
153
  font-size: var(--rel-fontSizes-sm);
154
- color: var(--rel-colors-gray-paragraph);
154
+ color: var(--rel-text-primary, #333);
155
155
  }
156
156
 
157
157
  /* Horizontal text-below specific */
@@ -237,43 +237,54 @@ export class RelStepper extends LitElement {
237
237
  * Step States
238
238
  ********************************************/
239
239
  .rel-step.done .indicator {
240
- background: var(--rel-colors-black);
241
- border-color: var(--rel-colors-black);
242
- color: var(--rel-colors-white);
240
+ background: var(--rel-color-text-heading);
241
+ border-color: var(--rel-color-text-heading);
242
+ color: var(--rel-color-text-inverse);
243
243
  }
244
244
 
245
245
  .rel-step.progress .indicator {
246
- border-color: var(--rel-colors-black);
247
- color: var(--rel-colors-black);
248
- background: var(--rel-colors-white);
246
+ border-color: var(--rel-color-text-heading);
247
+ background: var(--rel-color-bg-primary);
248
+ color: var(--rel-color-text-heading);
249
249
  }
250
250
 
251
251
  .rel-step.disabled .indicator {
252
- background: var(--rel-colors-gray-bg-subtle);
253
- border-color: var(--rel-colors-gray-border);
254
- color: var(--rel-colors-gray-disabled);
252
+ background: var(--rel-colors-gray-light, #fafafa);
253
+ border-color: var(--rel-colors-gray-border, #ddd);
254
+ color: var(--rel-text-disabled, #9e9e9e);
255
255
  }
256
256
 
257
257
  .rel-step.done .connector {
258
- background: var(--rel-colors-black);
258
+ background: var(--rel-color-text-heading);
259
259
  }
260
260
 
261
261
  .rel-step.progress .connector {
262
- background: var(--rel-colors-black);
262
+ background: var(--rel-color-text-heading);
263
263
  }
264
264
 
265
265
  .rel-step.done .step-label {
266
- color: var(--rel-colors-black);
266
+ color: var(--rel-color-text-heading);
267
267
  font-weight: var(--rel-fontWeights-semibold);
268
268
  }
269
269
 
270
270
  .rel-step.progress .step-label {
271
- color: var(--rel-colors-black);
271
+ color: var(--rel-color-text-heading);
272
272
  font-weight: var(--rel-fontWeights-semibold);
273
273
  }
274
274
 
275
275
  .rel-step.disabled .step-label {
276
- color: var(--rel-colors-gray-disabled);
276
+ color: var(--rel-text-disabled, #9e9e9e);
277
+ }
278
+
279
+ /* Dark mode: ensure progress/done indicator and labels stay readable */
280
+ :host-context(:root[data-theme="dark"]) .rel-step.progress .step-label,
281
+ :host-context(.dark) .rel-step.progress .step-label {
282
+ color: var(--rel-color-text-primary);
283
+ }
284
+
285
+ :host-context(:root[data-theme="dark"]) .rel-step.progress .indicator,
286
+ :host-context(.dark) .rel-step.progress .indicator {
287
+ color: #ffffff;
277
288
  }
278
289
 
279
290
  /********************************************
@@ -281,36 +292,36 @@ export class RelStepper extends LitElement {
281
292
  ********************************************/
282
293
  /* Horizontal */
283
294
  .rel-stepper.large.horizontal {
284
- gap: 86px;
295
+ gap: var(--rel-stepper-connector-length-lg, 86px);
285
296
  }
286
297
 
287
298
  .rel-stepper.small.horizontal {
288
- gap: 60px;
299
+ gap: var(--rel-stepper-connector-length-md, 60px);
289
300
  }
290
301
 
291
302
  .rel-stepper.large.horizontal .connector {
292
- width: 86px;
303
+ width: var(--rel-stepper-connector-length-lg, 86px);
293
304
  }
294
305
 
295
306
  .rel-stepper.small.horizontal .connector {
296
- width: 60px;
307
+ width: var(--rel-stepper-connector-length-md, 60px);
297
308
  }
298
309
 
299
310
  /* Vertical */
300
311
  .rel-stepper.large.vertical .connector {
301
- height: 86px;
312
+ height: var(--rel-stepper-connector-length-lg, 86px);
302
313
  }
303
314
 
304
315
  .rel-stepper.large.vertical {
305
- gap: 86px;
316
+ gap: var(--rel-stepper-connector-length-lg, 86px);
306
317
  }
307
318
 
308
319
  .rel-stepper.small.vertical .connector {
309
- height: 42px;
320
+ height: var(--rel-stepper-connector-length-sm, 42px);
310
321
  }
311
322
 
312
323
  .rel-stepper.small.vertical {
313
- gap: 42px;
324
+ gap: var(--rel-stepper-connector-length-sm, 42px);
314
325
  }
315
326
  `;
316
327
 
@@ -338,7 +349,7 @@ export class RelStepper extends LitElement {
338
349
  if (step.status === "disabled") return;
339
350
 
340
351
  this.dispatchEvent(
341
- new CustomEvent("step-change", {
352
+ new CustomEvent("rel-step-change", {
342
353
  detail: {
343
354
  index,
344
355
  step,
@@ -362,7 +373,7 @@ export class RelStepper extends LitElement {
362
373
  >
363
374
  <path
364
375
  d="M6.44479 14L0.5 7.36383L1.9862 5.70478L6.44479 10.6819L16.0138 0L17.5 1.65904L6.44479 14Z"
365
- fill="white"
376
+ fill="currentColor"
366
377
  />
367
378
  </svg>
368
379
  `;
@@ -0,0 +1,102 @@
1
+ # rel-switch
2
+
3
+ ## Overview
4
+ A toggle switch component for binary on/off states. Features smooth animations, focus halo effects, pressed states, and respects user motion preferences. Built with custom visual styling while maintaining native checkbox accessibility.
5
+
6
+ ## Usage
7
+ ```html
8
+ <!-- Basic switch -->
9
+ <rel-switch></rel-switch>
10
+
11
+ <!-- Pre-checked switch -->
12
+ <rel-switch checked></rel-switch>
13
+
14
+ <!-- Disabled switch -->
15
+ <rel-switch disabled></rel-switch>
16
+ <rel-switch checked disabled></rel-switch>
17
+
18
+ <!-- With event listener -->
19
+ <rel-switch id="notifications"></rel-switch>
20
+ <script>
21
+ const toggle = document.getElementById('notifications');
22
+ toggle.addEventListener('changed', (e) => {
23
+ console.log('Switch state:', e.detail); // boolean: true/false
24
+ });
25
+ </script>
26
+
27
+ <!-- Programmatically control -->
28
+ <rel-switch id="darkMode"></rel-switch>
29
+ <script>
30
+ const darkModeSwitch = document.getElementById('darkMode');
31
+ darkModeSwitch.checked = true;
32
+ darkModeSwitch.disabled = false;
33
+ </script>
34
+ ```
35
+
36
+ ## API
37
+
38
+ ### Attributes/Properties
39
+ | Name | Type | Default | Description |
40
+ |------|------|---------|-------------|
41
+ | `checked` | `boolean` | `false` | Whether the switch is in the ON position |
42
+ | `disabled` | `boolean` | `false` | Disables the switch and reduces opacity |
43
+
44
+ ## Events
45
+ | Event | Detail | Description |
46
+ |-------|--------|-------------|
47
+ | `changed` | `boolean` (the new checked state) | Fired when the switch is toggled by user interaction |
48
+
49
+ ## Slots
50
+ Default slot not used.
51
+
52
+ ## CSS Parts
53
+ None exposed.
54
+
55
+ ## Tokens / CSS Variables
56
+
57
+ ### Component-Level Custom Properties
58
+ The component defines these CSS custom properties on `:host` which can be overridden:
59
+
60
+ - `--switch-track` - Track color when unchecked (default: `--rel-text-placeholder`)
61
+ - `--switch-knob-on` - Knob color when checked (default: `--rel-colors-black`)
62
+ - `--switch-knob-off` - Knob color when unchecked (default: `--rel-colors-white`)
63
+ - `--switch-halo` - Focus halo color (default: `--rel-border-default`)
64
+ - `--switch-disabled` - Color for disabled state (default: `--rel-text-disabled`)
65
+
66
+ ### Design Token References
67
+ - `--rel-text-placeholder` - Default track color
68
+ - `--rel-text-dark-placeholder` - Hover track color
69
+ - `--rel-colors-black` - ON knob color
70
+ - `--rel-colors-white` - OFF knob color
71
+ - `--rel-border-default` - Focus halo
72
+ - `--rel-text-disabled` - Disabled state color
73
+ - `--rel-surface-surface` - Used in disabled styling
74
+
75
+ ## Accessibility
76
+ - Uses native checkbox input with `role="switch"` and `aria-checked`
77
+ - Visually hidden checkbox maintains keyboard and screen reader support
78
+ - Focus visible state shows prominent halo around knob
79
+ - Disabled state prevents interaction and reduces opacity to 0.5
80
+ - Smooth transitions respect `prefers-reduced-motion` media query
81
+
82
+ ### Keyboard Interactions
83
+ - `Space` - Toggle switch on/off
84
+ - `Tab` - Move focus to/from switch
85
+
86
+ ### Focus Behavior
87
+ - Focus ring appears as a 6px halo around the knob using `--switch-halo` color
88
+ - Halo uses box-shadow for smooth visual feedback
89
+
90
+ ### Motion Support
91
+ - Animations disabled when `prefers-reduced-motion: reduce` is set
92
+ - Transitions apply to track color, knob position, knob color, and box-shadow
93
+
94
+ ## Notes / Edge Cases
95
+ - The switch is 42px wide × 22px tall with a 12px tall track and 18px diameter knob
96
+ - Visual knob animates 22px horizontally when toggling from OFF to ON
97
+ - Hover effect only applies when not disabled (track darkens slightly)
98
+ - Pressed state applies a `scale(0.9)` transform to the knob for tactile feedback
99
+ - The knob includes a subtle drop shadow (`0 1px 3px rgba(0,0,0,0.3)`) for depth
100
+ - When disabled, both track and knob use `--switch-disabled` color
101
+ - The component is `display: inline-block` by default
102
+ - Focus halo appears on keyboard focus only (`:focus-visible`), not on mouse click
@@ -5,17 +5,21 @@ import { customElement, property, state } from "lit/decorators.js";
5
5
  export class RelSwitch extends LitElement {
6
6
  @property({ type: Boolean }) checked = false;
7
7
  @property({ type: Boolean }) disabled = false;
8
+ @property({ type: String }) value = "";
9
+
10
+ private _onChange: (value: boolean) => void = () => {};
11
+ private _onTouched: () => void = () => {};
8
12
 
9
13
  @state() private isFocused = false;
10
14
  @state() private isPressed = false;
11
15
 
12
16
  static styles = css`
13
17
  :host {
14
- --switch-track: #7e8186;
15
- --switch-knob-on: #000000;
16
- --switch-knob-off: #ffffff;
17
- --switch-halo: rgba(224, 224, 224, 0.8);
18
- --switch-disabled: #d1d5db;
18
+ --switch-track: var(--rel-text-placeholder);
19
+ --switch-knob-on: var(--rel-colors-black);
20
+ --switch-knob-off: var(--rel-colors-white);
21
+ --switch-halo: var(--rel-border-default);
22
+ --switch-disabled: var(--rel-text-disabled);
19
23
  display: inline-block;
20
24
  }
21
25
 
@@ -23,8 +27,8 @@ export class RelSwitch extends LitElement {
23
27
  .switch-wrapper {
24
28
  position: relative;
25
29
  display: inline-block;
26
- width: 42px;
27
- height: 22px;
30
+ width: var(--rel-switch-track-width, 42px);
31
+ height: var(--rel-switch-track-height, 22px);
28
32
  cursor: pointer;
29
33
  }
30
34
 
@@ -60,8 +64,8 @@ export class RelSwitch extends LitElement {
60
64
  top: 50%;
61
65
  left: -2px;
62
66
  transform: translateY(-50%);
63
- height: 18px;
64
- width: 18px;
67
+ height: var(--rel-switch-thumb-size, 18px);
68
+ width: var(--rel-switch-thumb-size, 18px);
65
69
  background-color: var(--switch-knob-off);
66
70
  border-radius: 50%;
67
71
  transition: transform 0.25s ease, background-color 0.25s ease,
@@ -77,7 +81,7 @@ export class RelSwitch extends LitElement {
77
81
 
78
82
  /* Hover */
79
83
  .switch-wrapper:hover:not(.switch-disabled) .switch-slider {
80
- background-color: #666;
84
+ background-color: var(--rel-text-dark-placeholder);
81
85
  }
82
86
 
83
87
  /* Focus halo */
@@ -100,6 +104,13 @@ export class RelSwitch extends LitElement {
100
104
  .switch-disabled .switch-slider::before {
101
105
  background-color: var(--switch-disabled);
102
106
  }
107
+
108
+ @media (prefers-reduced-motion: reduce) {
109
+ .switch-slider,
110
+ .switch-slider::before {
111
+ transition: none;
112
+ }
113
+ }
103
114
  `;
104
115
 
105
116
  private toggle(ev: Event) {
@@ -109,14 +120,22 @@ export class RelSwitch extends LitElement {
109
120
  this.checked = input.checked;
110
121
 
111
122
  this.dispatchEvent(
112
- new CustomEvent<boolean>("changed", {
113
- detail: this.checked,
123
+ new CustomEvent("rel-change", {
124
+ detail: {
125
+ checked: this.checked,
126
+ value: this.value,
127
+ },
114
128
  bubbles: true,
115
129
  composed: true,
116
130
  })
117
131
  );
132
+ this._onChange(this.checked);
118
133
  }
119
134
 
135
+ registerOnChange(fn: (value: boolean) => void): void { this._onChange = fn; }
136
+ registerOnTouched(fn: () => void): void { this._onTouched = fn; }
137
+ writeValue(checked: boolean): void { this.checked = checked; }
138
+
120
139
  private handleFocus() {
121
140
  this.isFocused = true;
122
141
  }
@@ -124,6 +143,7 @@ export class RelSwitch extends LitElement {
124
143
  private handleBlur() {
125
144
  this.isFocused = false;
126
145
  this.isPressed = false;
146
+ this._onTouched();
127
147
  }
128
148
 
129
149
  private handleMouseDown() {
@@ -148,6 +168,8 @@ export class RelSwitch extends LitElement {
148
168
  <label class="${wrapperClasses}">
149
169
  <input
150
170
  type="checkbox"
171
+ role="switch"
172
+ aria-checked="${this.checked}"
151
173
  ?disabled=${this.disabled}
152
174
  .checked=${this.checked}
153
175
  @change=${(e: Event) => this.toggle(e)}
@@ -0,0 +1,163 @@
1
+ # rel-tab
2
+
3
+ ## Overview
4
+ A tabbed interface component with automatic underline indicator, keyboard navigation, optional badge counts, and disabled states. Features ARIA-compliant navigation with roving tabindex and arrow key support.
5
+
6
+ ## Usage
7
+ ```html
8
+ <!-- Basic tabs -->
9
+ <rel-tab id="basicTabs"></rel-tab>
10
+ <script>
11
+ const tabs = document.getElementById('basicTabs');
12
+ tabs.tabs = [
13
+ { label: 'Overview' },
14
+ { label: 'Details' },
15
+ { label: 'Settings' }
16
+ ];
17
+ tabs.activeIndex = 0;
18
+ </script>
19
+
20
+ <!-- With badges (notification counts) -->
21
+ <rel-tab id="badgeTabs"></rel-tab>
22
+ <script>
23
+ const badgeTabs = document.getElementById('badgeTabs');
24
+ badgeTabs.tabs = [
25
+ { label: 'Inbox', badge: 5 },
26
+ { label: 'Drafts', badge: 2 },
27
+ { label: 'Sent' }
28
+ ];
29
+ </script>
30
+
31
+ <!-- With disabled tab -->
32
+ <rel-tab id="disabledTabs"></rel-tab>
33
+ <script>
34
+ const disabledTabs = document.getElementById('disabledTabs');
35
+ disabledTabs.tabs = [
36
+ { label: 'Available' },
37
+ { label: 'Coming Soon', disabled: true },
38
+ { label: 'Active' }
39
+ ];
40
+ </script>
41
+
42
+ <!-- Listen for tab changes -->
43
+ <rel-tab id="tabsWithListener"></rel-tab>
44
+ <script>
45
+ const listener = document.getElementById('tabsWithListener');
46
+ listener.tabs = [
47
+ { label: 'Tab 1' },
48
+ { label: 'Tab 2' },
49
+ { label: 'Tab 3' }
50
+ ];
51
+ listener.addEventListener('tab-change', (e) => {
52
+ console.log('Active tab index:', e.detail);
53
+ });
54
+ </script>
55
+
56
+ <!-- Tab content controlled externally -->
57
+ <rel-tab id="contentTabs" activeIndex="0"></rel-tab>
58
+ <div id="tabContent">
59
+ <div class="panel" data-index="0">Content for Tab 1</div>
60
+ <div class="panel" data-index="1" style="display:none;">Content for Tab 2</div>
61
+ <div class="panel" data-index="2" style="display:none;">Content for Tab 3</div>
62
+ </div>
63
+ <script>
64
+ const contentTabs = document.getElementById('contentTabs');
65
+ contentTabs.tabs = [
66
+ { label: 'Tab 1' },
67
+ { label: 'Tab 2' },
68
+ { label: 'Tab 3' }
69
+ ];
70
+ contentTabs.addEventListener('tab-change', (e) => {
71
+ document.querySelectorAll('#tabContent .panel').forEach((panel, i) => {
72
+ panel.style.display = i === e.detail ? 'block' : 'none';
73
+ });
74
+ });
75
+ </script>
76
+ ```
77
+
78
+ ## API
79
+
80
+ ### Attributes/Properties
81
+ | Name | Type | Default | Description |
82
+ |------|------|---------|-------------|
83
+ | `tabs` | `TabItem[]` | `[]` | Array of tab items (see TabItem interface below) |
84
+ | `activeIndex` (reflected) | `number` | `0` | Index of the currently active tab |
85
+
86
+ ### TabItem Interface
87
+ ```typescript
88
+ interface TabItem {
89
+ label: string; // Display text
90
+ badge?: number; // Optional notification count
91
+ disabled?: boolean; // Disables interaction
92
+ }
93
+ ```
94
+
95
+ ## Events
96
+ | Event | Detail | Description |
97
+ |-------|--------|-------------|
98
+ | `tab-change` | `number` (the new active index) | Fired when a different tab is selected |
99
+
100
+ ## Slots
101
+ | Slot | Description |
102
+ |------|-------------|
103
+ | (default) | Tab panel content (rendered in `.tab-content` wrapper below tabs) |
104
+
105
+ ## CSS Parts
106
+ None exposed.
107
+
108
+ ## Tokens / CSS Variables
109
+
110
+ ### Component-Level Custom Properties
111
+ The component defines CSS custom properties on `:host`:
112
+
113
+ - `--tab-active-color` - Active tab underline color (default: `--rel-colors-green-primary`)
114
+ - `--tab-text-default` - Default tab text color (default: `--rel-text-primary`)
115
+ - `--tab-text-disabled` - Disabled tab text color (default: `--rel-text-disabled`)
116
+ - `--tab-text-hover` - Hover tab text color (default: `--rel-text-heading`)
117
+ - `--tab-underline-height` - Height of active underline (default: `3px`)
118
+ - `--tab-hover-color` - Background color on hover (default: `--rel-surface-surface`)
119
+
120
+ ### Design Token References
121
+ - `--rel-fontFamilies-primary` - Font family
122
+ - `--rel-fontSizes-md` - Font size
123
+ - `--rel-fontWeights-medium` - Font weight
124
+ - `--rel-spacing-space-sm`, `--rel-spacing-space-md` - Tab padding
125
+ - `--rel-spacing-space-2xs` - Gap between label and badge
126
+ - `--rel-border-tab` - Bottom border color (default: `#c4c4c4`)
127
+ - `--rel-border-info` - Focus outline color
128
+ - `--rel-borders-radius-border-radius-100` - Focus outline border radius
129
+ - `--rel-colors-red-primary` - Badge background color
130
+ - `--rel-colors-white` - Badge text color
131
+
132
+ ## Accessibility
133
+ - Uses `role="tablist"` on container and `role="tab"` on each tab
134
+ - Tabs have `aria-selected` and `aria-disabled` attributes
135
+ - Implements roving tabindex (only active tab is focusable via Tab key)
136
+ - Arrow key navigation between tabs
137
+ - Focus visible states with blue outline
138
+
139
+ ### Keyboard Interactions
140
+ - `Tab` - Focus the active tab (or first non-disabled tab)
141
+ - `Arrow Right` - Navigate to next tab (wraps to first, skips disabled)
142
+ - `Arrow Left` - Navigate to previous tab (wraps to last, skips disabled)
143
+ - `Enter / Space` - Activate focused tab
144
+
145
+ ### Focus Behavior
146
+ - Focus outline: 2px blue with -2px offset (inside boundary)
147
+ - Border radius applied to focus outline
148
+ - Focus automatically moves when navigating with arrow keys
149
+
150
+ ## Notes / Edge Cases
151
+ - The **active tab** has a colored underline (3px high by default) at the bottom
152
+ - **Hover effect** (non-active, non-disabled): light gray background
153
+ - **Badges** are circular (16px height, min 16px width) with red background and white text
154
+ - Clicking a disabled tab does nothing (no event dispatched, no index change)
155
+ - Arrow key navigation wraps around (after last tab, goes to first, and vice versa)
156
+ - Arrow key navigation skips disabled tabs
157
+ - When a tab is activated via arrow keys, focus is programmatically moved to that tab
158
+ - The component includes a `.tab-content` wrapper (with 1rem vertical padding) that renders slotted content
159
+ - The `.tab-content` is always visible; consumers must manually show/hide panel content based on `activeIndex`
160
+ - Tabs have a bottom border (1px solid) via `border-bottom` on `.tabs-wrapper`
161
+ - Active tab underline (`position: absolute`, `bottom: 0`) overlays the border
162
+ - Transitions: color (0.2s), background-color (0.2s), transform (0.2s) for smooth interactions
163
+ - The `activeIndex` property is reflected in HTML as an attribute