@reliability-design/web 1.0.6 → 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 (60) hide show
  1. package/dist/components/rel-accordion/rel-accordion.ts +4 -0
  2. package/dist/components/rel-alert/rel-alert.ts +83 -13
  3. package/dist/components/rel-avatar/rel-avatar.ts +3 -2
  4. package/dist/components/rel-badge/rel-badge.ts +6 -2
  5. package/dist/components/rel-button/rel-button.ts +64 -8
  6. package/dist/components/rel-calendar/rel-calendar.ts +3 -3
  7. package/dist/components/rel-carousel/rel-carousel.ts +10 -11
  8. package/dist/components/rel-checkbox/rel-checkbox.ts +33 -2
  9. package/dist/components/rel-chip/rel-chip.ts +63 -5
  10. package/dist/components/rel-divider/rel-divider.ts +9 -0
  11. package/dist/components/rel-drawer/rel-drawer-footer.ts +29 -0
  12. package/dist/components/rel-drawer/rel-drawer.ts +17 -26
  13. package/dist/components/rel-menu/rel-menu.ts +53 -21
  14. package/dist/components/rel-menubar/rel-menubar.ts +52 -23
  15. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +240 -143
  16. package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -331
  17. package/dist/components/rel-navbar/rel-navbar.ts +13 -0
  18. package/dist/components/rel-pagination/rel-pagination.ts +11 -6
  19. package/dist/components/rel-popover/rel-popover.ts +16 -13
  20. package/dist/components/rel-radio/rel-radio.ts +18 -1
  21. package/dist/components/rel-resizable/rel-resizable-handle.ts +4 -8
  22. package/dist/components/rel-select/rel-select.ts +14 -0
  23. package/dist/components/rel-sheet/rel-sheet.ts +7 -6
  24. package/dist/components/rel-sidebar/rel-sidebar.ts +13 -5
  25. package/dist/components/rel-slider/rel-slider.ts +25 -2
  26. package/dist/components/rel-snackbar/rel-snackbar.ts +46 -16
  27. package/dist/components/rel-stepper/rel-stepper.ts +32 -21
  28. package/dist/components/rel-switch/rel-switch.ts +19 -6
  29. package/dist/components/rel-tab/rel-tab.ts +49 -15
  30. package/dist/components/rel-table/rel-table-body.ts +5 -0
  31. package/dist/components/rel-table/rel-table-caption.ts +6 -1
  32. package/dist/components/rel-table/rel-table-cell.ts +42 -49
  33. package/dist/components/rel-table/rel-table-footer.ts +5 -0
  34. package/dist/components/rel-table/rel-table-head.ts +13 -1
  35. package/dist/components/rel-table/rel-table-header.ts +5 -0
  36. package/dist/components/rel-table/rel-table-row.ts +7 -3
  37. package/dist/components/rel-table/rel-table.ts +8 -2
  38. package/dist/components/rel-textfield/rel-textfield.ts +20 -20
  39. package/dist/components/rel-toast/rel-toast.ts +31 -10
  40. package/dist/components/rel-tooltip/rel-tooltip.ts +123 -80
  41. package/dist/components/rel-topbar/rel-topbar-actions.ts +23 -0
  42. package/dist/components/rel-topbar/rel-topbar-brand.ts +46 -0
  43. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +33 -0
  44. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
  45. package/dist/components/rel-topbar/rel-topbar-item.ts +315 -0
  46. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +23 -0
  47. package/dist/components/rel-topbar/rel-topbar-profile.ts +248 -0
  48. package/dist/components/rel-topbar/rel-topbar-utilities.ts +23 -0
  49. package/dist/components/rel-topbar/rel-topbar.ts +329 -0
  50. package/dist/index.d.mts +278 -289
  51. package/dist/index.d.ts +278 -289
  52. package/dist/index.js +4498 -5727
  53. package/dist/index.js.map +1 -1
  54. package/dist/index.mjs +4535 -5756
  55. package/dist/index.mjs.map +1 -1
  56. package/dist/styles/vars.css +346 -294
  57. package/dist/{vars-ITL2K3KO.css → vars-TBB4BMSD.css} +346 -294
  58. package/package.json +2 -1
  59. package/dist/components/rel-quote-page/README.md +0 -166
  60. package/dist/components/rel-quote-page/rel-quote-page.ts +0 -816
@@ -35,6 +35,7 @@ export class RelPopover extends LitElement {
35
35
 
36
36
  private triggerEl: HTMLElement | null = null;
37
37
  private returnFocusEl: HTMLElement | null = null;
38
+ private uniqueId = `popover-${Math.random().toString(36).slice(2)}`;
38
39
 
39
40
  /* =======================
40
41
  * STYLES
@@ -81,10 +82,8 @@ export class RelPopover extends LitElement {
81
82
  }
82
83
 
83
84
  firstUpdated() {
84
- // Resolve trigger once from light DOM
85
- this.triggerEl = this.querySelector(
86
- '[slot="trigger"]',
87
- ) as HTMLElement | null;
85
+ // triggerEl is resolved via onTriggerSlotChange; call aria in case slot
86
+ // content was already assigned before firstUpdated
88
87
  this.applyTriggerAria();
89
88
  }
90
89
 
@@ -134,18 +133,13 @@ export class RelPopover extends LitElement {
134
133
 
135
134
  const path = e.composedPath();
136
135
 
137
- // Toggle ONLY if pointerdown originated from trigger
138
- const triggerNode = path.find(
139
- (n) => n instanceof HTMLElement && n.getAttribute?.("slot") === "trigger",
140
- ) as HTMLElement | undefined;
141
-
142
- if (!triggerNode) return;
136
+ // Toggle ONLY if pointerdown originated from the trigger element
137
+ if (!this.triggerEl || !path.includes(this.triggerEl)) return;
143
138
 
144
139
  // Prevent this interaction from becoming an "outside click"
145
140
  e.preventDefault();
146
141
  e.stopPropagation();
147
142
 
148
- this.triggerEl = triggerNode;
149
143
  this.setOpen(!this.activeOpen);
150
144
  };
151
145
 
@@ -295,10 +289,18 @@ export class RelPopover extends LitElement {
295
289
  * ARIA
296
290
  * ======================= */
297
291
 
292
+ private onTriggerSlotChange(e: Event) {
293
+ const slot = e.target as HTMLSlotElement;
294
+ const first = slot.assignedElements({ flatten: true })[0] as HTMLElement | undefined;
295
+ this.triggerEl = first ?? null;
296
+ this.applyTriggerAria();
297
+ }
298
+
298
299
  private applyTriggerAria() {
299
300
  if (!this.triggerEl) return;
300
301
  this.triggerEl.setAttribute("aria-haspopup", "dialog");
301
302
  this.triggerEl.setAttribute("aria-expanded", String(this.activeOpen));
303
+ this.triggerEl.setAttribute("aria-controls", this.uniqueId);
302
304
  }
303
305
 
304
306
  /* =======================
@@ -307,14 +309,15 @@ export class RelPopover extends LitElement {
307
309
 
308
310
  render() {
309
311
  return html`
310
- <slot name="trigger"></slot>
312
+ <slot @slotchange=${this.onTriggerSlotChange}></slot>
311
313
 
312
314
  <div
315
+ id="${this.uniqueId}"
313
316
  class="content-wrapper"
314
317
  style="${this.contentStyle}"
315
318
  ?open="${this.activeOpen}"
316
319
  >
317
- <slot></slot>
320
+ <slot name="content"></slot>
318
321
  </div>
319
322
  `;
320
323
  }
@@ -21,6 +21,13 @@ export class RelRadio extends LitElement {
21
21
  @property({ type: Boolean })
22
22
  disabled = false;
23
23
 
24
+ @property({ type: String }) value = "";
25
+
26
+ checked = false;
27
+
28
+ private _onChange: (value: string) => void = () => {};
29
+ private _onTouched: () => void = () => {};
30
+
24
31
  static styles = css`
25
32
  :host {
26
33
  display: block;
@@ -105,13 +112,22 @@ export class RelRadio extends LitElement {
105
112
 
106
113
  this.selectedValue = value;
107
114
  this.dispatchEvent(
108
- new CustomEvent("changed", {
115
+ new CustomEvent("rel-change", {
116
+ detail: {
117
+ checked: this.selectedValue === value,
118
+ value,
119
+ },
109
120
  bubbles: true,
110
121
  composed: true,
111
122
  })
112
123
  );
124
+ this._onChange(this.value);
113
125
  }
114
126
 
127
+ registerOnChange(fn: (value: string) => void): void { this._onChange = fn; }
128
+ registerOnTouched(fn: () => void): void { this._onTouched = fn; }
129
+ writeValue(value: string): void { this.checked = value === this.value; }
130
+
115
131
  private renderOption(option: RadioOption) {
116
132
  const isChecked = this.selectedValue === option.value;
117
133
  const isDisabled = this.disabled || option.disabled;
@@ -133,6 +149,7 @@ export class RelRadio extends LitElement {
133
149
  .checked="${isChecked}"
134
150
  ?disabled="${isDisabled}"
135
151
  @change="${() => this.onChange(option.value)}"
152
+ @blur="${() => this._onTouched()}"
136
153
  />
137
154
  <span class="radio-custom"></span>
138
155
  <span class="radio-label">${option.label}</span>
@@ -36,19 +36,15 @@ export class RelResizableHandle extends LitElement {
36
36
  }
37
37
 
38
38
  :host([orientation="horizontal"]) {
39
- width: var(--rel-borders-width-stroke-100);
40
- min-width: var(--rel-borders-width-stroke-100);
39
+ width: 8px;
40
+ min-width: 8px;
41
41
  cursor: col-resize;
42
- padding: 0 var(--rel-spacing-space-xs);
43
- margin: 0 calc(var(--rel-spacing-space-xs) * -1);
44
42
  }
45
43
 
46
44
  :host([orientation="vertical"]) {
47
- height: var(--rel-borders-width-stroke-100);
48
- min-height: var(--rel-borders-width-stroke-100);
45
+ height: 8px;
46
+ min-height: 8px;
49
47
  cursor: row-resize;
50
- padding: var(--rel-spacing-space-xs) 0;
51
- margin: calc(var(--rel-spacing-space-xs) * -1) 0;
52
48
  }
53
49
 
54
50
  :host(:hover),
@@ -176,6 +176,20 @@ export class RelSelect extends LitElement {
176
176
  .error-text {
177
177
  color: var(--rel-colors-red-secondary);
178
178
  }
179
+
180
+ /* Dark mode — flip select background, text, border, and chevron icon to white */
181
+ :host-context([data-theme="dark"]) select,
182
+ :host-context(.dark) select {
183
+ background-color: var(--rel-color-bg-secondary);
184
+ color: #ffffff;
185
+ border-color: var(--rel-color-border-default);
186
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ffffff' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
187
+ }
188
+
189
+ :host-context([data-theme="dark"]) .select-label,
190
+ :host-context(.dark) .select-label {
191
+ color: var(--rel-color-text-primary);
192
+ }
179
193
  `;
180
194
 
181
195
  private onChange(event: Event) {
@@ -166,7 +166,7 @@ export class RelSheetContent extends LitElement {
166
166
  }
167
167
 
168
168
  .sheet-container.open .overlay {
169
- opacity: 1;
169
+ opacity: var(--rel-sheet-scrim-opacity, 1);
170
170
  }
171
171
 
172
172
  .overlay.hidden {
@@ -354,9 +354,10 @@ export class RelSheetContent extends LitElement {
354
354
  this.lockScroll();
355
355
 
356
356
  if (this.sheet?.modal) {
357
- setTimeout(() => {
358
- this.focusPanel();
359
- }, 50);
357
+ this.updateComplete.then(() => {
358
+ const focusTarget = this.shadowRoot?.querySelector<HTMLElement>("[autofocus], .close-button, .panel");
359
+ focusTarget?.focus();
360
+ });
360
361
  }
361
362
 
362
363
  document.addEventListener("keydown", this.handleKeydown);
@@ -367,10 +368,10 @@ export class RelSheetContent extends LitElement {
367
368
  document.removeEventListener("keydown", this.handleKeydown);
368
369
 
369
370
  if (this.previousFocus && this.previousFocus.focus) {
370
- setTimeout(() => {
371
+ this.updateComplete.then(() => {
371
372
  this.previousFocus?.focus();
372
373
  this.previousFocus = null;
373
- }, 50);
374
+ });
374
375
  }
375
376
  }
376
377
 
@@ -1,5 +1,6 @@
1
- import { LitElement, html, css } from "lit";
1
+ import { LitElement, html, css, nothing } from "lit";
2
2
  import { customElement, property, state, query } from "lit/decorators.js";
3
+ import "@reliability-design/icons/rel-icon";
3
4
 
4
5
  export type SidebarVariant = "sidebar" | "floating" | "inset";
5
6
  export type SidebarSide = "left" | "right";
@@ -31,6 +32,11 @@ export class RelSidebarProvider extends LitElement {
31
32
 
32
33
  private mediaQuery: MediaQueryList | null = null;
33
34
  private isInitialized = false;
35
+ private _uid = Math.random().toString(36).slice(2);
36
+
37
+ private get _storageKey(): string {
38
+ return `rel-sidebar:${this.id || this._uid}`;
39
+ }
34
40
 
35
41
  static styles = css`
36
42
  :host {
@@ -154,7 +160,7 @@ export class RelSidebarProvider extends LitElement {
154
160
 
155
161
  private getPersistedState(): { open?: boolean; collapsed?: boolean } | null {
156
162
  try {
157
- const stored = localStorage.getItem(this.persistKey);
163
+ const stored = localStorage.getItem(this._storageKey);
158
164
  if (stored) {
159
165
  return JSON.parse(stored);
160
166
  }
@@ -184,7 +190,7 @@ export class RelSidebarProvider extends LitElement {
184
190
  : { open: this.activeOpen, collapsed: this.activeCollapsed };
185
191
 
186
192
  try {
187
- localStorage.setItem(this.persistKey, JSON.stringify(stateToPersist));
193
+ localStorage.setItem(this._storageKey, JSON.stringify(stateToPersist));
188
194
  } catch (e) {
189
195
  // Silently fail if localStorage is unavailable
190
196
  }
@@ -309,7 +315,8 @@ export class RelSidebar extends LitElement {
309
315
  left: 0;
310
316
  right: 0;
311
317
  bottom: 0;
312
- background: var(--rel-overlay-scrim, rgba(0, 0, 0, 0.5));
318
+ /* TODO: replace scrim fallback with a proper dark-aware token in vars.css */
319
+ background: var(--rel-overlay-scrim, rgba(0, 0, 0, 0.6));
313
320
  z-index: 999;
314
321
  opacity: 0;
315
322
  pointer-events: none;
@@ -982,6 +989,7 @@ export class RelSidebarMenuButton extends LitElement {
982
989
  @property({ type: Boolean }) active = false;
983
990
  @property({ type: Boolean }) disabled = false;
984
991
  @property({ type: String }) tooltip = "";
992
+ @property({ type: String }) icon: string = "";
985
993
 
986
994
  @state() private isCollapsed = false;
987
995
 
@@ -1147,7 +1155,7 @@ export class RelSidebarMenuButton extends LitElement {
1147
1155
  part="button"
1148
1156
  >
1149
1157
  <span class="icon" part="icon">
1150
- <slot name="icon"></slot>
1158
+ ${this.icon ? html`<rel-icon name="${this.icon}"></rel-icon>` : nothing}
1151
1159
  </span>
1152
1160
  <span class="label" part="label">
1153
1161
  <slot></slot>
@@ -26,6 +26,9 @@ export class RelSlider extends LitElement {
26
26
 
27
27
  @state() private ticks: number[] = [];
28
28
 
29
+ private _onChange: (value: number) => void = () => {};
30
+ private _onTouched: () => void = () => {};
31
+
29
32
  static styles = css`
30
33
  :host {
31
34
  --track-height: 5px;
@@ -112,6 +115,12 @@ export class RelSlider extends LitElement {
112
115
  border: none;
113
116
  }
114
117
 
118
+ .slider-input::slider-track {
119
+ background: transparent;
120
+ height: 100%;
121
+ border: none;
122
+ }
123
+
115
124
  /* Hide native thumb */
116
125
  .slider-input::-webkit-slider-thumb {
117
126
  -webkit-appearance: none;
@@ -131,6 +140,14 @@ export class RelSlider extends LitElement {
131
140
  cursor: pointer;
132
141
  }
133
142
 
143
+ .slider-input::slider-thumb {
144
+ width: 1px;
145
+ height: 1px;
146
+ background: transparent;
147
+ border: none;
148
+ cursor: pointer;
149
+ }
150
+
134
151
  .slider-input:focus-visible ~ .thumb-container .custom-thumb {
135
152
  box-shadow: 0 0 0 var(--thumb-stroke) var(--thumb-halo-color),
136
153
  var(--shadow), var(--halo);
@@ -330,14 +347,19 @@ export class RelSlider extends LitElement {
330
347
  this.value = next;
331
348
  this.updateActiveWidth();
332
349
  this.dispatchEvent(
333
- new CustomEvent("valueChange", {
350
+ new CustomEvent("rel-change", {
334
351
  detail: { value: this.value },
335
352
  bubbles: true,
336
353
  composed: true,
337
354
  })
338
355
  );
356
+ this._onChange(this.value);
339
357
  }
340
358
 
359
+ registerOnChange(fn: (value: number) => void): void { this._onChange = fn; }
360
+ registerOnTouched(fn: () => void): void { this._onTouched = fn; }
361
+ writeValue(value: number): void { this.value = value; }
362
+
341
363
  private updateActiveWidth() {
342
364
  const activeEl = this.shadowRoot?.querySelector(".track-active") as HTMLDivElement;
343
365
  if (activeEl) {
@@ -441,7 +463,8 @@ export class RelSlider extends LitElement {
441
463
  .step="${this.type === 'discrete' ? String(this.step) : '1'}"
442
464
  .value="${String(this.value)}"
443
465
  ?disabled="${this.disabled}"
444
- @input="${this.onInput}" />
466
+ @input="${this.onInput}"
467
+ @blur="${() => this._onTouched()}" />
445
468
 
446
469
  ${this.renderThumbContainer()}
447
470
  ${this.renderTicks()}
@@ -19,10 +19,11 @@ export class RelSnackbar extends LitElement {
19
19
  @property({ type: Number }) duration: number = 4000;
20
20
 
21
21
  @property({ type: String }) type: SnackbarType = "default";
22
+ @property({ type: Boolean, reflect: true }) show = false;
22
23
 
23
- @state() private visible: boolean = true;
24
+ @state() private visible: boolean = false;
24
25
 
25
- private timeout: number | null = null;
26
+ private _dismissTimer: ReturnType<typeof setTimeout> | null = null;
26
27
 
27
28
  static styles = css`
28
29
  :host {
@@ -56,17 +57,30 @@ export class RelSnackbar extends LitElement {
56
57
  display: none;
57
58
  }
58
59
 
59
- /* Type variants */
60
+ /* Type variants — border accent using semantic DS tokens */
60
61
  .snackbar.type-success {
61
- border-left-color: var(--rel-colors-green-secondary, #4ade80);
62
+ border-left-color: var(--rel-colors-green-600, #16a34a);
62
63
  }
63
64
 
64
65
  .snackbar.type-error {
65
- border-left-color: var(--rel-colors-red-primary, #f87171);
66
+ border-left-color: var(--rel-colors-red-600, #dc2626);
66
67
  }
67
68
 
68
69
  .snackbar.type-warning {
69
- border-left-color: var(--rel-colors-amber-primary, #fbbf24);
70
+ border-left-color: var(--rel-colors-amber-600, #d97706);
71
+ }
72
+
73
+ /* Icon color matches type accent */
74
+ .snackbar.type-success .snackbar-icon {
75
+ color: var(--rel-colors-green-400, #4ade80);
76
+ }
77
+
78
+ .snackbar.type-error .snackbar-icon {
79
+ color: var(--rel-colors-red-400, #f87171);
80
+ }
81
+
82
+ .snackbar.type-warning .snackbar-icon {
83
+ color: var(--rel-colors-amber-400, #fbbf24);
70
84
  }
71
85
 
72
86
  .snackbar-icon {
@@ -112,7 +126,7 @@ export class RelSnackbar extends LitElement {
112
126
  .snackbar-action {
113
127
  background: none;
114
128
  border: none;
115
- color: var(--rel-colors-green-secondary);
129
+ color: var(--rel-colors-green-400, #4ade80);
116
130
  font-weight: var(--rel-fontWeights-semibold);
117
131
  font-size: var(--rel-fontSizes-sm);
118
132
  font-family: var(--rel-fontFamilies-primary);
@@ -164,6 +178,11 @@ export class RelSnackbar extends LitElement {
164
178
  border-radius: 2px;
165
179
  }
166
180
 
181
+ /* Prevent any inherited transform from firing on button click */
182
+ .snackbar button:active {
183
+ transform: none;
184
+ }
185
+
167
186
  @media (prefers-reduced-motion: reduce) {
168
187
  .snackbar {
169
188
  animation: none;
@@ -184,32 +203,42 @@ export class RelSnackbar extends LitElement {
184
203
 
185
204
  connectedCallback() {
186
205
  super.connectedCallback();
187
- this.setAutoDismiss();
206
+ }
207
+
208
+ willUpdate(changed: Map<string, unknown>) {
209
+ if (!changed.has('show')) return;
210
+ if (this.show) {
211
+ this.visible = true;
212
+ this.setAutoDismiss();
213
+ } else {
214
+ this.visible = false;
215
+ this.clearDismissTimer();
216
+ }
188
217
  }
189
218
 
190
219
  updated(changed: Map<string, unknown>) {
191
- if (changed.has("duration") || changed.has("longerAction")) {
220
+ if ((changed.has("duration") || changed.has("longerAction")) && this.show) {
192
221
  this.setAutoDismiss();
193
222
  }
194
223
  }
195
224
 
196
225
  disconnectedCallback() {
197
226
  super.disconnectedCallback();
198
- this.clearTimeout();
227
+ this.clearDismissTimer();
199
228
  }
200
229
 
201
230
  private setAutoDismiss() {
202
231
  if (this.duration > 0) {
203
- this.clearTimeout();
232
+ this.clearDismissTimer();
204
233
  const delay = this.longerAction ? this.duration * 2 : this.duration;
205
- this.timeout = window.setTimeout(() => this.dismiss(), delay);
234
+ this._dismissTimer = setTimeout(() => this.dismiss(), delay);
206
235
  }
207
236
  }
208
237
 
209
- private clearTimeout() {
210
- if (this.timeout !== null) {
211
- window.clearTimeout(this.timeout);
212
- this.timeout = null;
238
+ private clearDismissTimer() {
239
+ if (this._dismissTimer !== null) {
240
+ clearTimeout(this._dismissTimer);
241
+ this._dismissTimer = null;
213
242
  }
214
243
  }
215
244
 
@@ -224,6 +253,7 @@ export class RelSnackbar extends LitElement {
224
253
  }
225
254
 
226
255
  private dismiss() {
256
+ this.show = false;
227
257
  this.visible = false;
228
258
  this.dispatchEvent(
229
259
  new CustomEvent("close", {
@@ -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%;
@@ -237,15 +237,15 @@ 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 {
@@ -255,20 +255,20 @@ export class RelStepper extends LitElement {
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
 
@@ -276,41 +276,52 @@ export class RelStepper extends LitElement {
276
276
  color: var(--rel-text-disabled, #9e9e9e);
277
277
  }
278
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;
288
+ }
289
+
279
290
  /********************************************
280
291
  * Size Property — controls connector length
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,
@@ -5,6 +5,10 @@ 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;
@@ -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,
@@ -116,14 +120,22 @@ export class RelSwitch extends LitElement {
116
120
  this.checked = input.checked;
117
121
 
118
122
  this.dispatchEvent(
119
- new CustomEvent<boolean>("changed", {
120
- detail: this.checked,
123
+ new CustomEvent("rel-change", {
124
+ detail: {
125
+ checked: this.checked,
126
+ value: this.value,
127
+ },
121
128
  bubbles: true,
122
129
  composed: true,
123
130
  })
124
131
  );
132
+ this._onChange(this.checked);
125
133
  }
126
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
+
127
139
  private handleFocus() {
128
140
  this.isFocused = true;
129
141
  }
@@ -131,6 +143,7 @@ export class RelSwitch extends LitElement {
131
143
  private handleBlur() {
132
144
  this.isFocused = false;
133
145
  this.isPressed = false;
146
+ this._onTouched();
134
147
  }
135
148
 
136
149
  private handleMouseDown() {