@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.
- package/dist/components/rel-accordion/README.md +132 -0
- package/dist/components/rel-accordion/rel-accordion.ts +47 -16
- package/dist/components/rel-alert/README.md +141 -0
- package/dist/components/rel-alert/rel-alert.ts +101 -20
- package/dist/components/rel-avatar/README.md +119 -0
- package/dist/components/rel-avatar/rel-avatar.ts +3 -2
- package/dist/components/rel-badge/README.md +86 -0
- package/dist/components/rel-badge/rel-badge.ts +11 -7
- package/dist/components/rel-breadcrumb/README.md +102 -0
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +12 -6
- package/dist/components/rel-button/README.md +130 -0
- package/dist/components/rel-button/rel-button.ts +64 -8
- 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 +12 -3
- package/dist/components/rel-cardmedia/README.md +127 -0
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +14 -4
- 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 +468 -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 +51 -12
- package/dist/components/rel-chip/README.md +114 -0
- package/dist/components/rel-chip/rel-chip.ts +83 -9
- package/dist/components/rel-container/README.md +99 -0
- 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 +9 -0
- 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-footer.ts +29 -0
- package/dist/components/rel-drawer/rel-drawer.ts +612 -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-menu/README.md +151 -0
- package/dist/components/rel-menu/rel-menu.ts +64 -24
- 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 +605 -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 +240 -0
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -0
- package/dist/components/rel-navbar/README.md +126 -0
- package/dist/components/rel-navbar/rel-navbar.ts +104 -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 +381 -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 +330 -0
- package/dist/components/rel-progress/README.md +110 -0
- package/dist/components/rel-progress/rel-progress.ts +32 -5
- package/dist/components/rel-radio/README.md +120 -0
- package/dist/components/rel-radio/rel-radio.ts +31 -6
- 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 +145 -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 +28 -14
- package/dist/components/rel-sheet/README.md +224 -0
- package/dist/components/rel-sheet/rel-sheet.ts +684 -0
- package/dist/components/rel-sidebar/README.md +209 -0
- package/dist/components/rel-sidebar/rel-sidebar.ts +1166 -0
- package/dist/components/rel-slider/README.md +135 -0
- package/dist/components/rel-slider/rel-slider.ts +44 -9
- package/dist/components/rel-snackbar/README.md +134 -0
- package/dist/components/rel-snackbar/rel-snackbar.ts +109 -14
- package/dist/components/rel-stepper/README.md +160 -0
- package/dist/components/rel-stepper/rel-stepper.ts +38 -27
- package/dist/components/rel-switch/README.md +102 -0
- package/dist/components/rel-switch/rel-switch.ts +34 -12
- package/dist/components/rel-tab/README.md +163 -0
- package/dist/components/rel-tab/rel-tab.ts +79 -23
- package/dist/components/rel-table/README.md +178 -0
- package/dist/components/rel-table/rel-table-body.ts +21 -0
- package/dist/components/rel-table/rel-table-caption.ts +27 -0
- package/dist/components/rel-table/rel-table-cell.ts +42 -0
- package/dist/components/rel-table/rel-table-footer.ts +23 -0
- package/dist/components/rel-table/rel-table-head.ts +45 -0
- package/dist/components/rel-table/rel-table-header.ts +21 -0
- package/dist/components/rel-table/rel-table-row.ts +36 -0
- package/dist/components/rel-table/rel-table.ts +53 -0
- package/dist/components/rel-textfield/README.md +90 -0
- package/dist/components/rel-textfield/rel-textfield.ts +55 -39
- package/dist/components/rel-toast/README.md +148 -0
- package/dist/components/rel-toast/rel-toast.ts +63 -24
- package/dist/components/rel-tooltip/README.md +103 -0
- package/dist/components/rel-tooltip/rel-tooltip.ts +123 -80
- package/dist/components/rel-topbar/rel-topbar-actions.ts +23 -0
- package/dist/components/rel-topbar/rel-topbar-brand.ts +46 -0
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +33 -0
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
- package/dist/components/rel-topbar/rel-topbar-item.ts +315 -0
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +23 -0
- package/dist/components/rel-topbar/rel-topbar-profile.ts +248 -0
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +23 -0
- package/dist/components/rel-topbar/rel-topbar.ts +329 -0
- package/dist/index.d.mts +831 -7
- package/dist/index.d.ts +831 -7
- package/dist/index.js +9500 -1564
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9240 -1344
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +187 -25
- package/dist/vars-TBB4BMSD.css +346 -0
- package/package.json +2 -1
- package/dist/vars-6ZIUPIU4.css +0 -184
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# rel-slider
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
A highly customizable range slider component supporting continuous and discrete modes. Features include tick marks, value bubble display, solid/dotted track styles, custom theming via CSS properties, and full accessibility support. Automatically snaps to steps in discrete mode.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
```html
|
|
8
|
+
<!-- Basic continuous slider -->
|
|
9
|
+
<rel-slider value="50" min="0" max="100"></rel-slider>
|
|
10
|
+
|
|
11
|
+
<!-- Discrete slider with ticks -->
|
|
12
|
+
<rel-slider
|
|
13
|
+
type="discrete"
|
|
14
|
+
value="25"
|
|
15
|
+
min="0"
|
|
16
|
+
max="100"
|
|
17
|
+
step="25">
|
|
18
|
+
</rel-slider>
|
|
19
|
+
|
|
20
|
+
<!-- Discrete slider with tick labels -->
|
|
21
|
+
<rel-slider
|
|
22
|
+
type="discrete"
|
|
23
|
+
value="3"
|
|
24
|
+
min="1"
|
|
25
|
+
max="5"
|
|
26
|
+
step="1"
|
|
27
|
+
showTickLabels>
|
|
28
|
+
</rel-slider>
|
|
29
|
+
|
|
30
|
+
<!-- Dotted track without value bubble -->
|
|
31
|
+
<rel-slider
|
|
32
|
+
value="60"
|
|
33
|
+
min="0"
|
|
34
|
+
max="100"
|
|
35
|
+
track="dotted"
|
|
36
|
+
showBubble="false">
|
|
37
|
+
</rel-slider>
|
|
38
|
+
|
|
39
|
+
<!-- Disabled slider -->
|
|
40
|
+
<rel-slider value="30" disabled></rel-slider>
|
|
41
|
+
|
|
42
|
+
<!-- Listen for value changes -->
|
|
43
|
+
<rel-slider id="volumeSlider" value="75" min="0" max="100"></rel-slider>
|
|
44
|
+
<script>
|
|
45
|
+
const slider = document.getElementById('volumeSlider');
|
|
46
|
+
slider.addEventListener('valueChange', (e) => {
|
|
47
|
+
console.log('New value:', e.detail.value);
|
|
48
|
+
});
|
|
49
|
+
</script>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## API
|
|
53
|
+
|
|
54
|
+
### Attributes/Properties
|
|
55
|
+
| Name | Type | Default | Description |
|
|
56
|
+
|------|------|---------|-------------|
|
|
57
|
+
| `type` | `"continuous" \| "discrete"` | `"continuous"` | Slider mode: continuous allows any value, discrete snaps to steps |
|
|
58
|
+
| `min` | `number` | `0` | Minimum value |
|
|
59
|
+
| `max` | `number` | `100` | Maximum value |
|
|
60
|
+
| `step` | `number` | `1` | Step increment (used in discrete mode for snapping and ticks) |
|
|
61
|
+
| `value` | `number` | `0` | Current slider value |
|
|
62
|
+
| `disabled` | `boolean` | `false` | Disables the slider |
|
|
63
|
+
| `track` | `"solid" \| "dotted"` | `"solid"` | Visual style of the active track |
|
|
64
|
+
| `showTickLabels` | `boolean` | `false` | Show numeric labels at each tick (discrete mode only) |
|
|
65
|
+
| `showBubble` | `boolean` | `true` | Show value bubble marker above the thumb |
|
|
66
|
+
|
|
67
|
+
## Events
|
|
68
|
+
| Event | Detail | Description |
|
|
69
|
+
|-------|--------|-------------|
|
|
70
|
+
| `valueChange` | `{ value: number }` | Fired when the slider value changes through user interaction |
|
|
71
|
+
|
|
72
|
+
## Slots
|
|
73
|
+
Default slot not used.
|
|
74
|
+
|
|
75
|
+
## CSS Parts
|
|
76
|
+
None exposed.
|
|
77
|
+
|
|
78
|
+
## Tokens / CSS Variables
|
|
79
|
+
|
|
80
|
+
### Component-Level Custom Properties
|
|
81
|
+
The component defines these CSS custom properties on `:host` which can be overridden:
|
|
82
|
+
|
|
83
|
+
- `--track-height` - Height of the slider track (default: `5px`)
|
|
84
|
+
- `--thumb-size` - Diameter of the thumb (default: `10px`)
|
|
85
|
+
- `--thumb-stroke` - Thickness of focus/active halo around thumb (default: `7px`)
|
|
86
|
+
- `--thumb-halo-color` - Color of thumb halo (default: `--rel-border-default`)
|
|
87
|
+
- `--tick-size` - Diameter of tick marks (default: `4px`)
|
|
88
|
+
- `--shadow` - Thumb drop shadow (default: `0 2px 4px rgba(0,0,0,0.25)`)
|
|
89
|
+
- `--halo` - Additional halo shadow (default: `0 2px 0 var(--rel-surface-surface), 0 6px 10px rgba(0,0,0,0.07)`)
|
|
90
|
+
- `--marker-width` - Width of value bubble (default: `24px`)
|
|
91
|
+
- `--marker-height` - Height of value bubble (default: `28px`)
|
|
92
|
+
|
|
93
|
+
### Design Token References
|
|
94
|
+
- `--rel-fontFamilies-primary` - Font family
|
|
95
|
+
- `--rel-colors-gray-border` - Inactive track color
|
|
96
|
+
- `--rel-colors-black` - Active track and thumb color
|
|
97
|
+
- `--rel-colors-white` - Used in dotted track and tick marks
|
|
98
|
+
- `--rel-colors-gray-primary` - Tick mark color (inactive)
|
|
99
|
+
- `--rel-colors-gray-heading` - Tick label color
|
|
100
|
+
- `--rel-colors-gray-placeholder` - Disabled active track and tick colors
|
|
101
|
+
- `--rel-border-default` - Default thumb halo color
|
|
102
|
+
- `--rel-surface-surface` - Disabled track background, used in dotted pattern
|
|
103
|
+
- `--rel-fontSizes-xs` - Tick label font size
|
|
104
|
+
|
|
105
|
+
## Accessibility
|
|
106
|
+
- Uses native `<input type="range">` for full keyboard and screen reader support
|
|
107
|
+
- Native input is transparent overlay, custom visuals beneath are purely decorative
|
|
108
|
+
- Focus visible state shows halo around custom thumb
|
|
109
|
+
- Disabled state prevents interaction with styled cursor and reduced contrast
|
|
110
|
+
- Respects `prefers-reduced-motion` media query (disables transitions)
|
|
111
|
+
|
|
112
|
+
### Keyboard Interactions
|
|
113
|
+
- `Arrow Left/Down` - Decrease value
|
|
114
|
+
- `Arrow Right/Up` - Increase value
|
|
115
|
+
- `Home` - Jump to minimum value
|
|
116
|
+
- `End` - Jump to maximum value
|
|
117
|
+
- `Page Up/Down` - Larger increments (browser-dependent)
|
|
118
|
+
|
|
119
|
+
### Focus Behavior
|
|
120
|
+
- Focus shows halo around custom thumb using `--thumb-halo-color`
|
|
121
|
+
- Active (mouse down) state also shows the halo
|
|
122
|
+
|
|
123
|
+
## Notes / Edge Cases
|
|
124
|
+
- In **discrete mode**, the slider automatically snaps to the nearest step value as the user drags
|
|
125
|
+
- Tick marks are generated automatically in discrete mode based on `min`, `max`, and `step`
|
|
126
|
+
- If the range doesn't divide evenly by step, a final tick is added at `max`
|
|
127
|
+
- Ticks change color (become white) when the value passes them (active state)
|
|
128
|
+
- **showTickLabels** only has effect in discrete mode
|
|
129
|
+
- **showBubble** displays a pin-drop shaped SVG marker with the current value as text
|
|
130
|
+
- The value bubble uses the "Poppins" font (you may need to load this separately or it will fall back)
|
|
131
|
+
- **Dotted track** uses a `radial-gradient` pattern with white dots over the active color
|
|
132
|
+
- The component has a fixed height of `60px` to accommodate the value bubble
|
|
133
|
+
- When disabled, all interactive colors become muted gray tones
|
|
134
|
+
- Active track width updates dynamically based on the percentage value
|
|
135
|
+
- The component respects `prefers-reduced-motion` and disables all transitions when requested
|
|
@@ -26,20 +26,23 @@ 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;
|
|
32
35
|
--thumb-size: 10px;
|
|
33
36
|
--thumb-stroke: 7px;
|
|
34
|
-
--thumb-halo-color:
|
|
37
|
+
--thumb-halo-color: var(--rel-border-default);
|
|
35
38
|
--tick-size: 4px;
|
|
36
39
|
--shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
|
|
37
|
-
--halo: 0 2px 0
|
|
40
|
+
--halo: 0 2px 0 var(--rel-surface-surface), 0 6px 10px rgba(0, 0, 0, 0.07);
|
|
38
41
|
--marker-width: 24px;
|
|
39
42
|
--marker-height: 28px;
|
|
40
43
|
|
|
41
44
|
display: block;
|
|
42
|
-
font-family: var(--rel-fontFamilies-primary);
|
|
45
|
+
font-family: var(--rel-fontFamilies-primary, sans-serif);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
.rel-slider {
|
|
@@ -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,26 @@ 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
|
+
|
|
151
|
+
.slider-input:focus-visible ~ .thumb-container .custom-thumb {
|
|
152
|
+
box-shadow: 0 0 0 var(--thumb-stroke) var(--thumb-halo-color),
|
|
153
|
+
var(--shadow), var(--halo);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@media (prefers-reduced-motion: reduce) {
|
|
157
|
+
.custom-thumb,
|
|
158
|
+
.tick {
|
|
159
|
+
transition: none;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
134
163
|
/* Custom Thumb Container */
|
|
135
164
|
.thumb-container {
|
|
136
165
|
position: absolute;
|
|
@@ -225,7 +254,7 @@ export class RelSlider extends LitElement {
|
|
|
225
254
|
.rel-slider.disabled .track-active.dotted {
|
|
226
255
|
background: radial-gradient(
|
|
227
256
|
circle,
|
|
228
|
-
|
|
257
|
+
var(--rel-surface-surface) 0 1px,
|
|
229
258
|
transparent 1px
|
|
230
259
|
)
|
|
231
260
|
center/12px 4px repeat-x,
|
|
@@ -246,7 +275,7 @@ export class RelSlider extends LitElement {
|
|
|
246
275
|
|
|
247
276
|
.rel-slider.disabled .custom-thumb {
|
|
248
277
|
background: var(--rel-colors-gray-placeholder);
|
|
249
|
-
box-shadow: 0 0 0 var(--thumb-stroke)
|
|
278
|
+
box-shadow: 0 0 0 var(--thumb-stroke) var(--rel-surface-surface);
|
|
250
279
|
}
|
|
251
280
|
|
|
252
281
|
.rel-slider.disabled .value-marker svg path {
|
|
@@ -318,14 +347,19 @@ export class RelSlider extends LitElement {
|
|
|
318
347
|
this.value = next;
|
|
319
348
|
this.updateActiveWidth();
|
|
320
349
|
this.dispatchEvent(
|
|
321
|
-
new CustomEvent("
|
|
350
|
+
new CustomEvent("rel-change", {
|
|
322
351
|
detail: { value: this.value },
|
|
323
352
|
bubbles: true,
|
|
324
353
|
composed: true,
|
|
325
354
|
})
|
|
326
355
|
);
|
|
356
|
+
this._onChange(this.value);
|
|
327
357
|
}
|
|
328
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
|
+
|
|
329
363
|
private updateActiveWidth() {
|
|
330
364
|
const activeEl = this.shadowRoot?.querySelector(".track-active") as HTMLDivElement;
|
|
331
365
|
if (activeEl) {
|
|
@@ -347,12 +381,12 @@ export class RelSlider extends LitElement {
|
|
|
347
381
|
<svg viewBox="0 0 24 32" xmlns="http://www.w3.org/2000/svg">
|
|
348
382
|
<path
|
|
349
383
|
d="M12 0C18.6274 0 24 5.37258 24 12C24 21 12 32 12 32C12 32 0 21 0 12C0 5.37258 5.37258 0 12 0Z"
|
|
350
|
-
fill="black" />
|
|
384
|
+
fill="var(--rel-colors-black)" />
|
|
351
385
|
<text
|
|
352
386
|
x="12"
|
|
353
387
|
y="16"
|
|
354
388
|
text-anchor="middle"
|
|
355
|
-
fill="white"
|
|
389
|
+
fill="var(--rel-colors-white)"
|
|
356
390
|
font-size="12"
|
|
357
391
|
font-family="Poppins"
|
|
358
392
|
font-weight="600">
|
|
@@ -429,7 +463,8 @@ export class RelSlider extends LitElement {
|
|
|
429
463
|
.step="${this.type === 'discrete' ? String(this.step) : '1'}"
|
|
430
464
|
.value="${String(this.value)}"
|
|
431
465
|
?disabled="${this.disabled}"
|
|
432
|
-
@input="${this.onInput}"
|
|
466
|
+
@input="${this.onInput}"
|
|
467
|
+
@blur="${() => this._onTouched()}" />
|
|
433
468
|
|
|
434
469
|
${this.renderThumbContainer()}
|
|
435
470
|
${this.renderTicks()}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# rel-snackbar
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
A bottom-anchored notification component for brief messages with optional action buttons. Supports single/double line layouts, configurable auto-dismiss duration, longer action mode (2x duration), and optional close icon. Features slide-up animation respecting user motion preferences.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
```html
|
|
8
|
+
<!-- Basic snackbar -->
|
|
9
|
+
<rel-snackbar message="File uploaded successfully"></rel-snackbar>
|
|
10
|
+
|
|
11
|
+
<!-- With action button -->
|
|
12
|
+
<rel-snackbar
|
|
13
|
+
id="undoSnackbar"
|
|
14
|
+
message="Item deleted"
|
|
15
|
+
action="UNDO">
|
|
16
|
+
</rel-snackbar>
|
|
17
|
+
<script>
|
|
18
|
+
const snackbar = document.getElementById('undoSnackbar');
|
|
19
|
+
snackbar.addEventListener('actionClick', () => {
|
|
20
|
+
console.log('Undo action clicked');
|
|
21
|
+
// Perform undo logic
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<!-- Two-line layout -->
|
|
26
|
+
<rel-snackbar
|
|
27
|
+
message="Your message is too long to fit on a single line, so we'll use two."
|
|
28
|
+
lines="double"
|
|
29
|
+
action="DISMISS">
|
|
30
|
+
</rel-snackbar>
|
|
31
|
+
|
|
32
|
+
<!-- Longer action (2x duration) -->
|
|
33
|
+
<rel-snackbar
|
|
34
|
+
message="Changes saved"
|
|
35
|
+
action="VIEW"
|
|
36
|
+
longerAction>
|
|
37
|
+
</rel-snackbar>
|
|
38
|
+
|
|
39
|
+
<!-- Without close icon -->
|
|
40
|
+
<rel-snackbar
|
|
41
|
+
message="Processing..."
|
|
42
|
+
iconClose="false"
|
|
43
|
+
duration="0">
|
|
44
|
+
</rel-snackbar>
|
|
45
|
+
|
|
46
|
+
<!-- Custom duration (10 seconds) -->
|
|
47
|
+
<rel-snackbar
|
|
48
|
+
message="Important notification"
|
|
49
|
+
duration="10000"
|
|
50
|
+
action="GOT IT">
|
|
51
|
+
</rel-snackbar>
|
|
52
|
+
|
|
53
|
+
<!-- Listen for close event -->
|
|
54
|
+
<rel-snackbar id="notif" message="Update available"></rel-snackbar>
|
|
55
|
+
<script>
|
|
56
|
+
const notif = document.getElementById('notif');
|
|
57
|
+
notif.addEventListener('close', () => {
|
|
58
|
+
console.log('Snackbar closed');
|
|
59
|
+
});
|
|
60
|
+
</script>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API
|
|
64
|
+
|
|
65
|
+
### Attributes/Properties
|
|
66
|
+
| Name | Type | Default | Description |
|
|
67
|
+
|------|------|---------|-------------|
|
|
68
|
+
| `message` | `string` | `"This is a snackbar message"` | Primary message text |
|
|
69
|
+
| `action` | `string` | - | Optional action button label (e.g., "UNDO", "RETRY") |
|
|
70
|
+
| `longerAction` | `boolean` | `false` | If true, doubles the auto-dismiss duration |
|
|
71
|
+
| `iconClose` | `boolean` | `true` | Whether to show the close (✖) icon button |
|
|
72
|
+
| `lines` | `"single" \| "double"` | `"single"` | Layout mode (single-line flex-row, double-line flex-column) |
|
|
73
|
+
| `duration` | `number` | `4000` | Auto-dismiss duration in milliseconds (0 = no auto-dismiss) |
|
|
74
|
+
|
|
75
|
+
## Events
|
|
76
|
+
| Event | Detail | Description |
|
|
77
|
+
|-------|--------|-------------|
|
|
78
|
+
| `actionClick` | None | Fired when the action button is clicked |
|
|
79
|
+
| `close` | None | Fired when snackbar is closed (via action, close button, or auto-dismiss) |
|
|
80
|
+
|
|
81
|
+
## Slots
|
|
82
|
+
Default slot not used.
|
|
83
|
+
|
|
84
|
+
## CSS Parts
|
|
85
|
+
None exposed.
|
|
86
|
+
|
|
87
|
+
## Tokens / CSS Variables
|
|
88
|
+
|
|
89
|
+
### Semantic Tokens
|
|
90
|
+
- `--rel-fontFamilies-primary` - Font family
|
|
91
|
+
- `--rel-fontSizes-sm` - Font size
|
|
92
|
+
- `--rel-fontWeights-semibold` - Action button font weight
|
|
93
|
+
- `--rel-lineHeights-lineheight-200` - Message line height
|
|
94
|
+
|
|
95
|
+
### Spacing Tokens
|
|
96
|
+
- `--rel-spacing-space-2xs` - Action button gap, close button padding
|
|
97
|
+
|
|
98
|
+
### Color Tokens
|
|
99
|
+
- `--rel-colors-black` - Background color
|
|
100
|
+
- `--rel-colors-white` - Text and focus outline color
|
|
101
|
+
- `--rel-colors-green-secondary` - Action button text color
|
|
102
|
+
|
|
103
|
+
### Shadow Tokens
|
|
104
|
+
- `--rel-shadows-shadow-300` - Drop shadow
|
|
105
|
+
|
|
106
|
+
### Border Tokens
|
|
107
|
+
- `--rel-borders-radius-border-radius-200` - Border radius
|
|
108
|
+
|
|
109
|
+
## Accessibility
|
|
110
|
+
- Snackbars are informational and do not interrupt workflows
|
|
111
|
+
- Close and action buttons have proper focus styles
|
|
112
|
+
- Animation respects `prefers-reduced-motion` media query
|
|
113
|
+
|
|
114
|
+
### Keyboard Interactions
|
|
115
|
+
- `Tab` - Focus action button or close button
|
|
116
|
+
- `Space/Enter` - Activate focused button
|
|
117
|
+
|
|
118
|
+
### Focus Behavior
|
|
119
|
+
- Buttons show 2px white outline with 2px offset when focused
|
|
120
|
+
- Border radius applied to maintain visual consistency
|
|
121
|
+
|
|
122
|
+
## Notes / Edge Cases
|
|
123
|
+
- Snackbar has a minimum width of `280px` and maximum width of `500px`
|
|
124
|
+
- **Single-line mode** (`lines="single"`): message and actions are flex-row with `justify-content: space-between`
|
|
125
|
+
- **Double-line mode** (`lines="double"`): layout switches to flex-column with actions aligned to the right
|
|
126
|
+
- Auto-dismiss timer starts in `connectedCallback()` and resets when `duration` or `longerAction` changes
|
|
127
|
+
- If `longerAction` is true, the effective duration is `duration * 2`
|
|
128
|
+
- Setting `duration="0"` disables auto-dismiss
|
|
129
|
+
- Clicking the action button automatically closes the snackbar after dispatching `actionClick`
|
|
130
|
+
- Clicking the close icon dispatches `close` and hides the snackbar (`visible = false`)
|
|
131
|
+
- When `visible` is false, `hidden` class applies `display: none`
|
|
132
|
+
- The component includes a `slide-up` animation (50px translateY → 0) on appearance
|
|
133
|
+
- Animation disabled when `prefers-reduced-motion: reduce` is set
|
|
134
|
+
- Snackbar is `display: block` by default (should be positioned by parent or global styles)
|
|
@@ -2,6 +2,7 @@ import { LitElement, html, css } from "lit";
|
|
|
2
2
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
3
|
|
|
4
4
|
export type SnackbarLines = "single" | "double";
|
|
5
|
+
export type SnackbarType = "default" | "success" | "error" | "warning";
|
|
5
6
|
|
|
6
7
|
@customElement("rel-snackbar")
|
|
7
8
|
export class RelSnackbar extends LitElement {
|
|
@@ -17,9 +18,12 @@ export class RelSnackbar extends LitElement {
|
|
|
17
18
|
|
|
18
19
|
@property({ type: Number }) duration: number = 4000;
|
|
19
20
|
|
|
20
|
-
@
|
|
21
|
+
@property({ type: String }) type: SnackbarType = "default";
|
|
22
|
+
@property({ type: Boolean, reflect: true }) show = false;
|
|
21
23
|
|
|
22
|
-
private
|
|
24
|
+
@state() private visible: boolean = false;
|
|
25
|
+
|
|
26
|
+
private _dismissTimer: ReturnType<typeof setTimeout> | null = null;
|
|
23
27
|
|
|
24
28
|
static styles = css`
|
|
25
29
|
:host {
|
|
@@ -41,6 +45,7 @@ export class RelSnackbar extends LitElement {
|
|
|
41
45
|
font-family: var(--rel-fontFamilies-primary);
|
|
42
46
|
box-shadow: var(--rel-shadows-shadow-300);
|
|
43
47
|
animation: slide-up 0.3s ease-out;
|
|
48
|
+
border-left: 4px solid transparent;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
.snackbar.two-line {
|
|
@@ -52,6 +57,47 @@ export class RelSnackbar extends LitElement {
|
|
|
52
57
|
display: none;
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
/* Type variants — border accent using semantic DS tokens */
|
|
61
|
+
.snackbar.type-success {
|
|
62
|
+
border-left-color: var(--rel-colors-green-600, #16a34a);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.snackbar.type-error {
|
|
66
|
+
border-left-color: var(--rel-colors-red-600, #dc2626);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.snackbar.type-warning {
|
|
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);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.snackbar-icon {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
margin-right: 10px;
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
font-size: 14px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.snackbar-body {
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
flex: 1;
|
|
98
|
+
min-width: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
55
101
|
.snackbar-message {
|
|
56
102
|
font-size: var(--rel-fontSizes-sm);
|
|
57
103
|
line-height: var(--rel-lineHeights-lineheight-200);
|
|
@@ -80,7 +126,7 @@ export class RelSnackbar extends LitElement {
|
|
|
80
126
|
.snackbar-action {
|
|
81
127
|
background: none;
|
|
82
128
|
border: none;
|
|
83
|
-
color: var(--rel-colors-green-
|
|
129
|
+
color: var(--rel-colors-green-400, #4ade80);
|
|
84
130
|
font-weight: var(--rel-fontWeights-semibold);
|
|
85
131
|
font-size: var(--rel-fontSizes-sm);
|
|
86
132
|
font-family: var(--rel-fontFamilies-primary);
|
|
@@ -97,10 +143,16 @@ export class RelSnackbar extends LitElement {
|
|
|
97
143
|
opacity: 0.6;
|
|
98
144
|
}
|
|
99
145
|
|
|
146
|
+
.snackbar-action:focus-visible {
|
|
147
|
+
outline: 2px solid var(--rel-colors-white, #fff);
|
|
148
|
+
outline-offset: 2px;
|
|
149
|
+
border-radius: 2px;
|
|
150
|
+
}
|
|
151
|
+
|
|
100
152
|
.snackbar-close {
|
|
101
153
|
background: none;
|
|
102
154
|
border: none;
|
|
103
|
-
color: var(--rel-colors-white);
|
|
155
|
+
color: var(--rel-colors-white, #fff);
|
|
104
156
|
cursor: pointer;
|
|
105
157
|
font-size: 16px;
|
|
106
158
|
padding: 0;
|
|
@@ -120,6 +172,23 @@ export class RelSnackbar extends LitElement {
|
|
|
120
172
|
opacity: 0.6;
|
|
121
173
|
}
|
|
122
174
|
|
|
175
|
+
.snackbar-close:focus-visible {
|
|
176
|
+
outline: 2px solid var(--rel-colors-white, #fff);
|
|
177
|
+
outline-offset: 2px;
|
|
178
|
+
border-radius: 2px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Prevent any inherited transform from firing on button click */
|
|
182
|
+
.snackbar button:active {
|
|
183
|
+
transform: none;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@media (prefers-reduced-motion: reduce) {
|
|
187
|
+
.snackbar {
|
|
188
|
+
animation: none;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
123
192
|
@keyframes slide-up {
|
|
124
193
|
from {
|
|
125
194
|
transform: translateY(50px);
|
|
@@ -134,32 +203,42 @@ export class RelSnackbar extends LitElement {
|
|
|
134
203
|
|
|
135
204
|
connectedCallback() {
|
|
136
205
|
super.connectedCallback();
|
|
137
|
-
|
|
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
|
+
}
|
|
138
217
|
}
|
|
139
218
|
|
|
140
219
|
updated(changed: Map<string, unknown>) {
|
|
141
|
-
if (changed.has("duration") || changed.has("longerAction")) {
|
|
220
|
+
if ((changed.has("duration") || changed.has("longerAction")) && this.show) {
|
|
142
221
|
this.setAutoDismiss();
|
|
143
222
|
}
|
|
144
223
|
}
|
|
145
224
|
|
|
146
225
|
disconnectedCallback() {
|
|
147
226
|
super.disconnectedCallback();
|
|
148
|
-
this.
|
|
227
|
+
this.clearDismissTimer();
|
|
149
228
|
}
|
|
150
229
|
|
|
151
230
|
private setAutoDismiss() {
|
|
152
231
|
if (this.duration > 0) {
|
|
153
|
-
this.
|
|
232
|
+
this.clearDismissTimer();
|
|
154
233
|
const delay = this.longerAction ? this.duration * 2 : this.duration;
|
|
155
|
-
this.
|
|
234
|
+
this._dismissTimer = setTimeout(() => this.dismiss(), delay);
|
|
156
235
|
}
|
|
157
236
|
}
|
|
158
237
|
|
|
159
|
-
private
|
|
160
|
-
if (this.
|
|
161
|
-
|
|
162
|
-
this.
|
|
238
|
+
private clearDismissTimer() {
|
|
239
|
+
if (this._dismissTimer !== null) {
|
|
240
|
+
clearTimeout(this._dismissTimer);
|
|
241
|
+
this._dismissTimer = null;
|
|
163
242
|
}
|
|
164
243
|
}
|
|
165
244
|
|
|
@@ -174,6 +253,7 @@ export class RelSnackbar extends LitElement {
|
|
|
174
253
|
}
|
|
175
254
|
|
|
176
255
|
private dismiss() {
|
|
256
|
+
this.show = false;
|
|
177
257
|
this.visible = false;
|
|
178
258
|
this.dispatchEvent(
|
|
179
259
|
new CustomEvent("close", {
|
|
@@ -183,18 +263,33 @@ export class RelSnackbar extends LitElement {
|
|
|
183
263
|
);
|
|
184
264
|
}
|
|
185
265
|
|
|
266
|
+
private getIcon(): string {
|
|
267
|
+
switch (this.type) {
|
|
268
|
+
case "success": return "✓";
|
|
269
|
+
case "error": return "✕";
|
|
270
|
+
case "warning": return "⚠";
|
|
271
|
+
default: return "";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
186
275
|
render() {
|
|
187
276
|
const snackbarClasses = [
|
|
188
277
|
"snackbar",
|
|
189
278
|
this.lines === "double" ? "two-line" : "",
|
|
190
279
|
!this.visible ? "hidden" : "",
|
|
280
|
+
this.type !== "default" ? `type-${this.type}` : "",
|
|
191
281
|
]
|
|
192
282
|
.filter(Boolean)
|
|
193
283
|
.join(" ");
|
|
194
284
|
|
|
285
|
+
const icon = this.getIcon();
|
|
286
|
+
|
|
195
287
|
return html`
|
|
196
288
|
<div class="${snackbarClasses}">
|
|
197
|
-
<div class="snackbar-
|
|
289
|
+
<div class="snackbar-body">
|
|
290
|
+
${icon ? html`<span class="snackbar-icon">${icon}</span>` : ""}
|
|
291
|
+
<div class="snackbar-message">${this.message}</div>
|
|
292
|
+
</div>
|
|
198
293
|
|
|
199
294
|
<div class="snackbar-actions">
|
|
200
295
|
${this.action
|