@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.
- package/dist/components/rel-accordion/README.md +132 -0
- package/dist/components/rel-accordion/rel-accordion.ts +61 -29
- package/dist/components/rel-alert/README.md +141 -0
- package/dist/components/rel-alert/rel-alert.ts +110 -67
- package/dist/components/rel-avatar/README.md +119 -0
- package/dist/components/rel-avatar/rel-avatar.ts +8 -15
- package/dist/components/rel-badge/README.md +86 -0
- package/dist/components/rel-badge/rel-badge.ts +28 -21
- package/dist/components/rel-breadcrumb/README.md +102 -0
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +24 -18
- package/dist/components/rel-button/README.md +130 -0
- package/dist/components/rel-button/rel-button.ts +39 -59
- package/dist/components/rel-calendar/README.md +109 -0
- package/dist/components/rel-calendar/index.ts +1 -0
- package/dist/components/rel-calendar/rel-calendar.ts +467 -0
- package/dist/components/rel-card/README.md +94 -0
- package/dist/components/rel-card/rel-card.ts +21 -11
- package/dist/components/rel-cardmedia/README.md +127 -0
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +22 -18
- package/dist/components/rel-carousel/README.md +166 -0
- package/dist/components/rel-carousel/index.ts +1 -0
- package/dist/components/rel-carousel/rel-carousel.ts +469 -0
- package/dist/components/rel-chart/README.md +286 -0
- package/dist/components/rel-chart/index.ts +1 -0
- package/dist/components/rel-chart/rel-chart.ts +736 -0
- package/dist/components/rel-checkbox/README.md +77 -0
- package/dist/components/rel-checkbox/rel-checkbox.ts +36 -45
- package/dist/components/rel-chip/README.md +114 -0
- package/dist/components/rel-chip/rel-chip.ts +47 -122
- package/dist/components/rel-container/README.md +99 -0
- package/dist/components/rel-container/rel-container.ts +1 -6
- package/dist/components/rel-date-picker/README.md +253 -0
- package/dist/components/rel-date-picker/index.ts +1 -0
- package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
- package/dist/components/rel-divider/README.md +100 -0
- package/dist/components/rel-divider/rel-divider.ts +37 -37
- package/dist/components/rel-drawer/README.md +200 -0
- package/dist/components/rel-drawer/index.ts +1 -0
- package/dist/components/rel-drawer/rel-drawer.ts +621 -0
- package/dist/components/rel-kbd/README.md +151 -0
- package/dist/components/rel-kbd/rel-kbd.ts +118 -0
- package/dist/components/rel-layout/README.md +88 -0
- package/dist/components/rel-layout/rel-layout.ts +12 -14
- package/dist/components/rel-menu/README.md +151 -0
- package/dist/components/rel-menu/rel-menu.ts +18 -10
- package/dist/components/rel-menubar/README.md +329 -0
- package/dist/components/rel-menubar/index.ts +2 -0
- package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
- package/dist/components/rel-menubar/rel-menubar.ts +576 -0
- package/dist/components/rel-nav-menu/README.md +289 -0
- package/dist/components/rel-nav-menu/index.ts +3 -0
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +143 -0
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +331 -0
- package/dist/components/rel-navbar/README.md +126 -0
- package/dist/components/rel-navbar/rel-navbar.ts +91 -0
- package/dist/components/rel-otp/README.md +440 -0
- package/dist/components/rel-otp/index.ts +1 -0
- package/dist/components/rel-otp/rel-otp.ts +504 -0
- package/dist/components/rel-pagination/README.md +410 -0
- package/dist/components/rel-pagination/index.ts +1 -0
- package/dist/components/rel-pagination/rel-pagination.ts +376 -0
- package/dist/components/rel-popover/README.md +568 -0
- package/dist/components/rel-popover/index.ts +2 -0
- package/dist/components/rel-popover/rel-popover-content.ts +55 -0
- package/dist/components/rel-popover/rel-popover.ts +327 -0
- package/dist/components/rel-progress/README.md +110 -0
- package/dist/components/rel-progress/rel-progress.ts +58 -20
- package/dist/components/rel-quote-page/README.md +166 -0
- package/dist/components/rel-quote-page/rel-quote-page.ts +816 -0
- package/dist/components/rel-radio/README.md +120 -0
- package/dist/components/rel-radio/rel-radio.ts +41 -73
- package/dist/components/rel-resizable/README.md +140 -0
- package/dist/components/rel-resizable/index.ts +3 -0
- package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
- package/dist/components/rel-resizable/rel-resizable-handle.ts +149 -0
- package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
- package/dist/components/rel-segmented-button/README.md +141 -0
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
- package/dist/components/rel-select/README.md +107 -0
- package/dist/components/rel-select/rel-select.ts +14 -14
- package/dist/components/rel-sheet/README.md +224 -0
- package/dist/components/rel-sheet/rel-sheet.ts +683 -0
- package/dist/components/rel-sidebar/README.md +209 -0
- package/dist/components/rel-sidebar/rel-sidebar.ts +1158 -0
- package/dist/components/rel-slider/README.md +135 -0
- package/dist/components/rel-slider/rel-slider.ts +19 -7
- package/dist/components/rel-snackbar/README.md +134 -0
- package/dist/components/rel-snackbar/rel-snackbar.ts +73 -2
- package/dist/components/rel-stepper/README.md +160 -0
- package/dist/components/rel-stepper/rel-stepper.ts +29 -8
- package/dist/components/rel-switch/README.md +102 -0
- package/dist/components/rel-switch/rel-switch.ts +22 -11
- package/dist/components/rel-tab/README.md +163 -0
- package/dist/components/rel-tab/rel-tab.ts +46 -22
- package/dist/components/rel-table/README.md +178 -0
- package/dist/components/rel-table/rel-table-body.ts +16 -0
- package/dist/components/rel-table/rel-table-caption.ts +22 -0
- package/dist/components/rel-table/rel-table-cell.ts +49 -0
- package/dist/components/rel-table/rel-table-footer.ts +18 -0
- package/dist/components/rel-table/rel-table-head.ts +33 -0
- package/dist/components/rel-table/rel-table-header.ts +16 -0
- package/dist/components/rel-table/rel-table-row.ts +32 -0
- package/dist/components/rel-table/rel-table.ts +47 -0
- package/dist/components/rel-textfield/README.md +90 -0
- package/dist/components/rel-textfield/rel-textfield.ts +37 -21
- package/dist/components/rel-toast/README.md +148 -0
- package/dist/components/rel-toast/rel-toast.ts +32 -14
- package/dist/components/rel-tooltip/README.md +103 -0
- package/dist/components/rel-tooltip/rel-tooltip.ts +9 -9
- package/dist/index.d.mts +846 -7
- package/dist/index.d.ts +846 -7
- package/dist/index.js +10860 -1803
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10820 -1809
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +293 -183
- package/dist/vars-ITL2K3KO.css +294 -0
- package/package.json +2 -2
- package/dist/components/rel-accordion/rel-accordion.stories.ts +0 -313
- package/dist/components/rel-alert/rel-alert.stories.ts +0 -412
- package/dist/components/rel-avatar/rel-avatar.stories.ts +0 -327
- package/dist/components/rel-badge/rel-badge.stories.ts +0 -350
- package/dist/components/rel-breadcrumb/rel-breadcrumb.stories.ts +0 -306
- package/dist/components/rel-button/rel-button.stories.ts +0 -356
- package/dist/components/rel-card/rel-card.stories.ts +0 -299
- package/dist/components/rel-cardmedia/rel-cardmedia.stories.ts +0 -368
- package/dist/components/rel-checkbox/rel-checkbox.stories.ts +0 -414
- package/dist/components/rel-chip/rel-chip.stories.ts +0 -552
- package/dist/components/rel-container/rel-container.stories.ts +0 -239
- package/dist/components/rel-divider/rel-divider.stories.ts +0 -229
- package/dist/components/rel-layout/rel-layout.stories.ts +0 -713
- package/dist/components/rel-menu/rel-menu.stories.ts +0 -910
- package/dist/components/rel-progress/rel-progress.stories.ts +0 -817
- package/dist/components/rel-radio/rel-radio.stories.ts +0 -1048
- package/dist/components/rel-segmented-button/rel-segmented-button.stories.ts +0 -1079
- package/dist/components/rel-select/rel-select.stories.ts +0 -1201
- package/dist/components/rel-slider/rel-slider.stories.ts +0 -730
- package/dist/components/rel-snackbar/rel-snackbar.stories.ts +0 -808
- package/dist/components/rel-stepper/rel-stepper.stories.ts +0 -752
- package/dist/components/rel-switch/rel-switch.stories.ts +0 -441
- package/dist/components/rel-tab/rel-tab.stories.ts +0 -492
- package/dist/components/rel-textfield/rel-textfield.stories.ts +0 -710
- package/dist/components/rel-toast/rel-toast.stories.ts +0 -394
- package/dist/components/rel-tooltip/rel-tooltip.stories.ts +0 -364
|
@@ -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-
|
|
154
|
+
color: var(--rel-text-primary, #333);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/* Horizontal text-below specific */
|
|
@@ -226,7 +226,10 @@ export class RelStepper extends LitElement {
|
|
|
226
226
|
display: none;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
.rel-stepper.vertical.text-right
|
|
229
|
+
.rel-stepper.vertical.text-right
|
|
230
|
+
.rel-step:last-child
|
|
231
|
+
.indicator-wrapper
|
|
232
|
+
.connector {
|
|
230
233
|
display: none;
|
|
231
234
|
}
|
|
232
235
|
|
|
@@ -246,9 +249,9 @@ export class RelStepper extends LitElement {
|
|
|
246
249
|
}
|
|
247
250
|
|
|
248
251
|
.rel-step.disabled .indicator {
|
|
249
|
-
background: var(--rel-colors-gray-
|
|
250
|
-
border-color: var(--rel-colors-gray-border);
|
|
251
|
-
color: var(--rel-
|
|
252
|
+
background: var(--rel-colors-gray-light, #fafafa);
|
|
253
|
+
border-color: var(--rel-colors-gray-border, #ddd);
|
|
254
|
+
color: var(--rel-text-disabled, #9e9e9e);
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
.rel-step.done .connector {
|
|
@@ -270,7 +273,7 @@ export class RelStepper extends LitElement {
|
|
|
270
273
|
}
|
|
271
274
|
|
|
272
275
|
.rel-step.disabled .step-label {
|
|
273
|
-
color: var(--rel-
|
|
276
|
+
color: var(--rel-text-disabled, #9e9e9e);
|
|
274
277
|
}
|
|
275
278
|
|
|
276
279
|
/********************************************
|
|
@@ -331,6 +334,21 @@ export class RelStepper extends LitElement {
|
|
|
331
334
|
return classes.join(" ");
|
|
332
335
|
}
|
|
333
336
|
|
|
337
|
+
private onStepClick(step: Step, index: number) {
|
|
338
|
+
if (step.status === "disabled") return;
|
|
339
|
+
|
|
340
|
+
this.dispatchEvent(
|
|
341
|
+
new CustomEvent("step-change", {
|
|
342
|
+
detail: {
|
|
343
|
+
index,
|
|
344
|
+
step,
|
|
345
|
+
},
|
|
346
|
+
bubbles: true,
|
|
347
|
+
composed: true,
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
334
352
|
private renderIndicator(step: Step, index: number) {
|
|
335
353
|
if (step.status === "done") {
|
|
336
354
|
return html`
|
|
@@ -344,7 +362,7 @@ export class RelStepper extends LitElement {
|
|
|
344
362
|
>
|
|
345
363
|
<path
|
|
346
364
|
d="M6.44479 14L0.5 7.36383L1.9862 5.70478L6.44479 10.6819L16.0138 0L17.5 1.65904L6.44479 14Z"
|
|
347
|
-
fill="
|
|
365
|
+
fill="currentColor"
|
|
348
366
|
/>
|
|
349
367
|
</svg>
|
|
350
368
|
`;
|
|
@@ -369,7 +387,10 @@ export class RelStepper extends LitElement {
|
|
|
369
387
|
<div class="${stepperClasses}">
|
|
370
388
|
${this.steps.map(
|
|
371
389
|
(step, idx) => html`
|
|
372
|
-
<div
|
|
390
|
+
<div
|
|
391
|
+
class="${this.getStepClasses(step)}"
|
|
392
|
+
@click="${() => this.onStepClick(step, idx)}"
|
|
393
|
+
>
|
|
373
394
|
${isVertical && isTextRight
|
|
374
395
|
? html`
|
|
375
396
|
<!-- Indicator with connector below -->
|
|
@@ -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
|
|
@@ -11,11 +11,11 @@ export class RelSwitch extends LitElement {
|
|
|
11
11
|
|
|
12
12
|
static styles = css`
|
|
13
13
|
:host {
|
|
14
|
-
--switch-track:
|
|
15
|
-
--switch-knob-on:
|
|
16
|
-
--switch-knob-off:
|
|
17
|
-
--switch-halo:
|
|
18
|
-
--switch-disabled:
|
|
14
|
+
--switch-track: var(--rel-text-placeholder);
|
|
15
|
+
--switch-knob-on: var(--rel-colors-black);
|
|
16
|
+
--switch-knob-off: var(--rel-colors-white);
|
|
17
|
+
--switch-halo: var(--rel-border-default);
|
|
18
|
+
--switch-disabled: var(--rel-text-disabled);
|
|
19
19
|
display: inline-block;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -77,7 +77,7 @@ export class RelSwitch extends LitElement {
|
|
|
77
77
|
|
|
78
78
|
/* Hover */
|
|
79
79
|
.switch-wrapper:hover:not(.switch-disabled) .switch-slider {
|
|
80
|
-
background-color:
|
|
80
|
+
background-color: var(--rel-text-dark-placeholder);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/* Focus halo */
|
|
@@ -100,14 +100,23 @@ export class RelSwitch extends LitElement {
|
|
|
100
100
|
.switch-disabled .switch-slider::before {
|
|
101
101
|
background-color: var(--switch-disabled);
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
@media (prefers-reduced-motion: reduce) {
|
|
105
|
+
.switch-slider,
|
|
106
|
+
.switch-slider::before {
|
|
107
|
+
transition: none;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
103
110
|
`;
|
|
104
111
|
|
|
105
|
-
private toggle() {
|
|
112
|
+
private toggle(ev: Event) {
|
|
106
113
|
if (this.disabled) return;
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
|
|
115
|
+
const input = ev.target as HTMLInputElement;
|
|
116
|
+
this.checked = input.checked;
|
|
117
|
+
|
|
109
118
|
this.dispatchEvent(
|
|
110
|
-
new CustomEvent("changed", {
|
|
119
|
+
new CustomEvent<boolean>("changed", {
|
|
111
120
|
detail: this.checked,
|
|
112
121
|
bubbles: true,
|
|
113
122
|
composed: true,
|
|
@@ -146,9 +155,11 @@ export class RelSwitch extends LitElement {
|
|
|
146
155
|
<label class="${wrapperClasses}">
|
|
147
156
|
<input
|
|
148
157
|
type="checkbox"
|
|
158
|
+
role="switch"
|
|
159
|
+
aria-checked="${this.checked}"
|
|
149
160
|
?disabled=${this.disabled}
|
|
150
161
|
.checked=${this.checked}
|
|
151
|
-
@change=${this.toggle}
|
|
162
|
+
@change=${(e: Event) => this.toggle(e)}
|
|
152
163
|
@focus=${this.handleFocus}
|
|
153
164
|
@blur=${this.handleBlur}
|
|
154
165
|
@mousedown=${this.handleMouseDown}
|
|
@@ -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
|
|
@@ -10,38 +10,44 @@ export interface TabItem {
|
|
|
10
10
|
@customElement("rel-tab")
|
|
11
11
|
export class RelTab extends LitElement {
|
|
12
12
|
@property({ type: Array }) tabs: TabItem[] = [];
|
|
13
|
-
@property({ type: Number }) activeIndex
|
|
13
|
+
@property({ type: Number, reflect: true }) activeIndex = 0;
|
|
14
14
|
|
|
15
15
|
static styles = css`
|
|
16
16
|
:host {
|
|
17
|
-
--tab-active-color:
|
|
18
|
-
--tab-text-default:
|
|
19
|
-
--tab-text-disabled:
|
|
20
|
-
--tab-text-hover:
|
|
17
|
+
--tab-active-color: var(--rel-colors-green-primary);
|
|
18
|
+
--tab-text-default: var(--rel-text-primary);
|
|
19
|
+
--tab-text-disabled: var(--rel-text-disabled);
|
|
20
|
+
--tab-text-hover: var(--rel-text-heading);
|
|
21
21
|
--tab-underline-height: 3px;
|
|
22
|
-
--tab-hover-color:
|
|
22
|
+
--tab-hover-color: var(--rel-surface-surface);
|
|
23
23
|
display: block;
|
|
24
24
|
font-family: var(--rel-fontFamilies-primary);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.tabs-wrapper {
|
|
28
28
|
display: flex;
|
|
29
|
-
border-bottom: 1px solid #
|
|
30
|
-
font-family: var(--rel-fontFamilies-primary);
|
|
29
|
+
border-bottom: 1px solid var(--rel-border-tab, #c4c4c4);
|
|
30
|
+
font-family: var(--rel-fontFamilies-primary, sans-serif);
|
|
31
31
|
user-select: none;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.tab {
|
|
35
35
|
position: relative;
|
|
36
|
-
padding:
|
|
36
|
+
padding: var(--rel-spacing-space-sm, 12px) var(--rel-spacing-space-md, 16px);
|
|
37
37
|
cursor: pointer;
|
|
38
38
|
color: var(--tab-text-default);
|
|
39
|
-
font-size:
|
|
40
|
-
font-weight: 500;
|
|
39
|
+
font-size: var(--rel-fontSizes-md, 14px);
|
|
40
|
+
font-weight: var(--rel-fontWeights-medium, 500);
|
|
41
41
|
transition: color 0.2s ease;
|
|
42
42
|
display: flex;
|
|
43
43
|
align-items: center;
|
|
44
|
-
gap:
|
|
44
|
+
gap: var(--rel-spacing-space-2xs, 4px);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.tab:focus-visible {
|
|
48
|
+
outline: 2px solid var(--rel-border-info, #0091ea);
|
|
49
|
+
outline-offset: -2px;
|
|
50
|
+
border-radius: var(--rel-borders-radius-border-radius-100, 2px);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
.tab:hover:not(.disabled) .tab-label {
|
|
@@ -77,8 +83,8 @@ export class RelTab extends LitElement {
|
|
|
77
83
|
display: inline-flex;
|
|
78
84
|
justify-content: center;
|
|
79
85
|
align-items: center;
|
|
80
|
-
background:
|
|
81
|
-
color:
|
|
86
|
+
background: var(--rel-colors-red-primary);
|
|
87
|
+
color: var(--rel-colors-white);
|
|
82
88
|
border-radius: 50%;
|
|
83
89
|
font-size: 11px;
|
|
84
90
|
font-weight: 600;
|
|
@@ -98,8 +104,8 @@ export class RelTab extends LitElement {
|
|
|
98
104
|
if (this.tabs[index]?.disabled) return;
|
|
99
105
|
this.activeIndex = index;
|
|
100
106
|
this.dispatchEvent(
|
|
101
|
-
new CustomEvent("tab-change", {
|
|
102
|
-
detail:
|
|
107
|
+
new CustomEvent<number>("tab-change", {
|
|
108
|
+
detail: index,
|
|
103
109
|
bubbles: true,
|
|
104
110
|
composed: true,
|
|
105
111
|
})
|
|
@@ -107,24 +113,42 @@ export class RelTab extends LitElement {
|
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
private onKeyDown(event: KeyboardEvent, index: number) {
|
|
110
|
-
if (this.tabs[index]?.disabled) return;
|
|
111
116
|
if (event.key === "Enter" || event.key === " ") {
|
|
117
|
+
if (this.tabs[index]?.disabled) return;
|
|
112
118
|
event.preventDefault();
|
|
113
119
|
this.selectTab(index);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
const dir = event.key === "ArrowRight" ? 1 : -1;
|
|
126
|
+
let next = index;
|
|
127
|
+
for (let i = 0; i < this.tabs.length; i++) {
|
|
128
|
+
next = (next + dir + this.tabs.length) % this.tabs.length;
|
|
129
|
+
if (!this.tabs[next]?.disabled) break;
|
|
130
|
+
}
|
|
131
|
+
this.selectTab(next);
|
|
132
|
+
this.updateComplete.then(() => {
|
|
133
|
+
const tabEls = this.shadowRoot?.querySelectorAll("[role=tab]");
|
|
134
|
+
(tabEls?.[next] as HTMLElement)?.focus();
|
|
135
|
+
});
|
|
114
136
|
}
|
|
115
137
|
}
|
|
116
138
|
|
|
117
139
|
render() {
|
|
118
140
|
return html`
|
|
119
|
-
<div class="tabs-wrapper">
|
|
141
|
+
<div class="tabs-wrapper" role="tablist">
|
|
120
142
|
${this.tabs.map(
|
|
121
143
|
(tab, i) => html`
|
|
122
144
|
<div
|
|
123
|
-
class="tab ${i === this.activeIndex
|
|
124
|
-
? "
|
|
125
|
-
: ""}"
|
|
145
|
+
class="tab ${i === this.activeIndex
|
|
146
|
+
? "active"
|
|
147
|
+
: ""} ${tab.disabled ? "disabled" : ""}"
|
|
126
148
|
role="tab"
|
|
127
|
-
|
|
149
|
+
aria-selected="${i === this.activeIndex}"
|
|
150
|
+
aria-disabled="${tab.disabled ?? false}"
|
|
151
|
+
tabindex="${tab.disabled ? -1 : i === this.activeIndex ? 0 : -1}"
|
|
128
152
|
@click="${() => this.selectTab(i)}"
|
|
129
153
|
@keydown="${(e: KeyboardEvent) => this.onKeyDown(e, i)}"
|
|
130
154
|
>
|