@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,90 @@
|
|
|
1
|
+
# rel-textfield
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A text input field component with support for labels, icons, validation states, and supporting text. Used for single-line text entry in forms and user interfaces.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<!-- Basic usage -->
|
|
11
|
+
<rel-textfield label="Email" placeholder="Enter your email"></rel-textfield>
|
|
12
|
+
|
|
13
|
+
<!-- With icons and supporting text -->
|
|
14
|
+
<rel-textfield
|
|
15
|
+
label="Password"
|
|
16
|
+
type="password"
|
|
17
|
+
leftIcon="🔒"
|
|
18
|
+
supportingText="Must be at least 8 characters">
|
|
19
|
+
</rel-textfield>
|
|
20
|
+
|
|
21
|
+
<!-- Setting value via JavaScript -->
|
|
22
|
+
<rel-textfield id="myField" label="Username"></rel-textfield>
|
|
23
|
+
<script>
|
|
24
|
+
const field = document.getElementById('myField');
|
|
25
|
+
field.value = 'john.doe';
|
|
26
|
+
</script>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## API
|
|
30
|
+
|
|
31
|
+
### Attributes/Properties
|
|
32
|
+
|
|
33
|
+
| Name | Type | Default | Description |
|
|
34
|
+
|------|------|---------|-------------|
|
|
35
|
+
| `label` | `string` | - | Label text displayed above the input |
|
|
36
|
+
| `placeholder` | `string` | `""` | Placeholder text |
|
|
37
|
+
| `type` | `string` | `"text"` | Input type (text, email, password, etc.) |
|
|
38
|
+
| `value` | `string` | `""` | Current input value |
|
|
39
|
+
| `leftIcon` | `string` | - | Icon content on the left side |
|
|
40
|
+
| `rightIcon` | `string` | - | Icon content on the right side |
|
|
41
|
+
| `supportingText` | `string` | - | Helper or error text below input |
|
|
42
|
+
| `state` | `"default" \| "focused" \| "error" \| "disabled"` | `"default"` | Visual state |
|
|
43
|
+
| `size` | `"small" \| "medium" \| "large"` | `"medium"` | Size variant |
|
|
44
|
+
|
|
45
|
+
## Events
|
|
46
|
+
|
|
47
|
+
| Event | Detail | Description |
|
|
48
|
+
|-------|--------|-------------|
|
|
49
|
+
| `input-change` | `string` (the current value) | Fires when the input value changes through user interaction |
|
|
50
|
+
|
|
51
|
+
## Slots
|
|
52
|
+
|
|
53
|
+
Default slot not used. All content controlled via properties.
|
|
54
|
+
|
|
55
|
+
## CSS Parts
|
|
56
|
+
|
|
57
|
+
None exposed.
|
|
58
|
+
|
|
59
|
+
## Tokens / CSS Variables
|
|
60
|
+
|
|
61
|
+
- `--rel-fontFamilies-primary`: Font family
|
|
62
|
+
- `--rel-fontSizes-sm`, `--rel-fontSizes-md`: Font sizes
|
|
63
|
+
- `--rel-fontWeights-medium`: Font weight for label
|
|
64
|
+
- `--rel-text-heading`: Label color
|
|
65
|
+
- `--rel-text-primary`: Input text color
|
|
66
|
+
- `--rel-text-placeholder`: Placeholder and icon color
|
|
67
|
+
- `--rel-text-disabled`: Disabled text color
|
|
68
|
+
- `--rel-border-default`: Default border color
|
|
69
|
+
- `--rel-colors-primary-500`: Focus border color
|
|
70
|
+
- `--rel-border-error`: Error state border color
|
|
71
|
+
- `--rel-colors-white`: Input background (light mode)
|
|
72
|
+
- `--rel-surface-surface`: Disabled background
|
|
73
|
+
- `--rel-spacing-space-*`: Spacing tokens
|
|
74
|
+
- `--rel-borders-radius-border-radius-200`: Border radius
|
|
75
|
+
|
|
76
|
+
## Accessibility
|
|
77
|
+
|
|
78
|
+
- Associates `<label>` with input via `for`/`id`
|
|
79
|
+
- Supports native HTML5 input attributes (`required`, `disabled`, `readonly`, `maxlength`)
|
|
80
|
+
- Focus ring visible on keyboard navigation (`:focus-visible`)
|
|
81
|
+
- Error state communicated visually via border color
|
|
82
|
+
- Supporting text can provide `aria-describedby` context
|
|
83
|
+
|
|
84
|
+
## Notes / Edge Cases
|
|
85
|
+
|
|
86
|
+
- The `state` prop overrides internal focus state if set explicitly
|
|
87
|
+
- Icons are decorative (text content, not `<img>` elements)
|
|
88
|
+
- Input type `password` does not auto-toggle visibility (use `rightIcon` for toggle button)
|
|
89
|
+
- Error state must be set programmatically; no built-in validation
|
|
90
|
+
- Disabled inputs cannot be focused or edited
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LitElement, html, css } from "lit";
|
|
2
2
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
3
|
|
|
4
|
-
export type TextFieldState = "default" | "
|
|
4
|
+
export type TextFieldState = "default" | "error" | "disabled";
|
|
5
5
|
export type TextFieldSize = "small" | "medium" | "large";
|
|
6
6
|
|
|
7
7
|
@customElement("rel-textfield")
|
|
@@ -16,27 +16,29 @@ export class RelTextfield extends LitElement {
|
|
|
16
16
|
@property({ type: String }) state: TextFieldState = "default";
|
|
17
17
|
@property({ type: String }) size: TextFieldSize = "medium";
|
|
18
18
|
|
|
19
|
-
@state() private internalState:
|
|
19
|
+
@state() private internalState: "default" | "focused" = "default";
|
|
20
|
+
|
|
21
|
+
private uniqueId = `textfield-${Math.random().toString(36).slice(2)}`;
|
|
20
22
|
|
|
21
23
|
static styles = css`
|
|
22
24
|
:host {
|
|
23
25
|
display: block;
|
|
24
|
-
font-family: var(--rel-fontFamilies-primary);
|
|
26
|
+
font-family: var(--rel-fontFamilies-primary, sans-serif);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/* Wrapper */
|
|
28
30
|
.textfield-wrapper {
|
|
29
31
|
display: flex;
|
|
30
32
|
flex-direction: column;
|
|
31
|
-
gap: 4px;
|
|
33
|
+
gap: var(--rel-spacing-space-2xs, 4px);
|
|
32
34
|
width: 100%;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/* Label */
|
|
36
38
|
.textfield-label {
|
|
37
|
-
font-size:
|
|
38
|
-
font-weight: 500;
|
|
39
|
-
color: #
|
|
39
|
+
font-size: var(--rel-fontSizes-sm, 12px);
|
|
40
|
+
font-weight: var(--rel-fontWeights-medium, 500);
|
|
41
|
+
color: var(--rel-text-heading, #2b2b2b);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/* Container */
|
|
@@ -44,14 +46,14 @@ export class RelTextfield extends LitElement {
|
|
|
44
46
|
display: flex;
|
|
45
47
|
align-items: center;
|
|
46
48
|
position: relative;
|
|
47
|
-
border: 1px solid #
|
|
48
|
-
border-radius:
|
|
49
|
-
background: white;
|
|
49
|
+
border: 1px solid var(--rel-border-default, #ddd);
|
|
50
|
+
border-radius: var(--rel-borders-radius-border-radius-200, 8px);
|
|
51
|
+
background: var(--rel-colors-white, #fff);
|
|
50
52
|
transition: border 0.2s;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
.textfield-container.disabled {
|
|
54
|
-
background: #f5f5f5;
|
|
56
|
+
background: var(--rel-surface-surface, #f5f5f5);
|
|
55
57
|
cursor: not-allowed;
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -60,22 +62,23 @@ export class RelTextfield extends LitElement {
|
|
|
60
62
|
flex: 1;
|
|
61
63
|
border: none;
|
|
62
64
|
outline: none;
|
|
63
|
-
font-size:
|
|
64
|
-
padding:
|
|
65
|
+
font-size: var(--rel-fontSizes-md, 14px);
|
|
66
|
+
padding: var(--rel-spacing-space-xs, 8px) var(--rel-spacing-space-sm, 12px);
|
|
65
67
|
background: transparent;
|
|
66
|
-
font-family: var(--rel-fontFamilies-primary);
|
|
68
|
+
font-family: var(--rel-fontFamilies-primary, sans-serif);
|
|
69
|
+
color: var(--rel-text-primary, #333);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
.textfield-input:disabled {
|
|
70
73
|
cursor: not-allowed;
|
|
71
|
-
color: #
|
|
74
|
+
color: var(--rel-text-disabled, #9e9e9e);
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
.icon {
|
|
75
78
|
position: absolute;
|
|
76
79
|
display: flex;
|
|
77
80
|
pointer-events: none;
|
|
78
|
-
color: #
|
|
81
|
+
color: var(--rel-text-placeholder, #a9a9a9);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
.icon.left {
|
|
@@ -87,59 +90,70 @@ export class RelTextfield extends LitElement {
|
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
.textfield-wrapper.small .textfield-input {
|
|
90
|
-
padding:
|
|
91
|
-
font-size:
|
|
93
|
+
padding: var(--rel-spacing-space-xs, 8px) var(--rel-spacing-space-sm, 12px);
|
|
94
|
+
font-size: var(--rel-fontSizes-sm, 12px);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
.textfield-wrapper.medium .textfield-input {
|
|
95
|
-
padding:
|
|
96
|
-
font-size:
|
|
98
|
+
padding: var(--rel-spacing-space-sm, 12px) var(--rel-spacing-space-md, 16px);
|
|
99
|
+
font-size: var(--rel-fontSizes-md, 14px);
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
.textfield-wrapper.large .textfield-input {
|
|
100
|
-
padding:
|
|
101
|
-
font-size:
|
|
103
|
+
padding: var(--rel-spacing-space-md, 16px) var(--rel-spacing-space-md, 16px);
|
|
104
|
+
font-size: var(--rel-fontSizes-lg, 20px);
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
/* Adjust padding when icons are present */
|
|
105
108
|
.textfield-wrapper .textfield-input.has-left-icon {
|
|
106
|
-
padding-left:
|
|
109
|
+
padding-left: var(--rel-spacing-space-2xl, 32px);
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
.textfield-wrapper .textfield-input.has-right-icon {
|
|
110
|
-
padding-right:
|
|
113
|
+
padding-right: var(--rel-spacing-space-2xl, 32px);
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
/* States */
|
|
114
117
|
.textfield-wrapper.focused .textfield-container {
|
|
115
|
-
border-color: #
|
|
118
|
+
border-color: var(--rel-colors-primary-500, #0091ea);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.textfield-input:focus-visible {
|
|
122
|
+
outline: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.textfield-wrapper.focused .textfield-container,
|
|
126
|
+
.textfield-container:focus-within {
|
|
127
|
+
border-color: var(--rel-colors-primary-500, #0091ea);
|
|
128
|
+
box-shadow: 0 0 0 1px var(--rel-colors-primary-500, #0091ea);
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
.textfield-wrapper.error .textfield-container {
|
|
119
|
-
border-color: #
|
|
132
|
+
border-color: var(--rel-border-error, #cc0000);
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
.textfield-wrapper.disabled .textfield-container {
|
|
123
|
-
border-color: #
|
|
136
|
+
border-color: var(--rel-border-default, #ddd);
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
/* Supporting text */
|
|
127
140
|
.textfield-support {
|
|
128
|
-
font-size:
|
|
129
|
-
color: #
|
|
141
|
+
font-size: var(--rel-fontSizes-xs, 10px);
|
|
142
|
+
color: var(--rel-text-placeholder, #a9a9a9);
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
.textfield-support.error {
|
|
133
|
-
color: #
|
|
146
|
+
color: var(--rel-text-error, #cc0000);
|
|
134
147
|
}
|
|
135
|
-
`;
|
|
136
148
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
149
|
+
/* Placeholder */
|
|
150
|
+
.textfield-input::placeholder {
|
|
151
|
+
color: var(--rel-text-placeholder, #a9a9a9);
|
|
152
|
+
}
|
|
153
|
+
`;
|
|
140
154
|
|
|
141
155
|
private handleFocus() {
|
|
142
|
-
if (this.state !== "disabled"
|
|
156
|
+
if (this.state !== "disabled") {
|
|
143
157
|
this.internalState = "focused";
|
|
144
158
|
}
|
|
145
159
|
}
|
|
@@ -154,7 +168,7 @@ export class RelTextfield extends LitElement {
|
|
|
154
168
|
const input = e.target as HTMLInputElement;
|
|
155
169
|
this.value = input.value;
|
|
156
170
|
this.dispatchEvent(
|
|
157
|
-
new CustomEvent<string>("
|
|
171
|
+
new CustomEvent<string>("rel-change", {
|
|
158
172
|
detail: this.value,
|
|
159
173
|
bubbles: true,
|
|
160
174
|
composed: true,
|
|
@@ -166,8 +180,9 @@ export class RelTextfield extends LitElement {
|
|
|
166
180
|
const wrapperClasses = [
|
|
167
181
|
"textfield-wrapper",
|
|
168
182
|
this.size,
|
|
169
|
-
this.
|
|
170
|
-
|
|
183
|
+
this.state,
|
|
184
|
+
this.internalState === "focused" ? "focused" : "",
|
|
185
|
+
].filter(Boolean).join(" ");
|
|
171
186
|
|
|
172
187
|
const inputClasses = [
|
|
173
188
|
"textfield-input",
|
|
@@ -180,7 +195,7 @@ export class RelTextfield extends LitElement {
|
|
|
180
195
|
return html`
|
|
181
196
|
<div class="${wrapperClasses}">
|
|
182
197
|
${this.label
|
|
183
|
-
? html`<label class="textfield-label">${this.label}</label>`
|
|
198
|
+
? html`<label class="textfield-label" for="${this.uniqueId}">${this.label}</label>`
|
|
184
199
|
: ""}
|
|
185
200
|
|
|
186
201
|
<div
|
|
@@ -194,6 +209,7 @@ export class RelTextfield extends LitElement {
|
|
|
194
209
|
|
|
195
210
|
<input
|
|
196
211
|
type="${this.type}"
|
|
212
|
+
id="${this.uniqueId}"
|
|
197
213
|
class="${inputClasses}"
|
|
198
214
|
placeholder="${this.placeholder}"
|
|
199
215
|
.value="${this.value}"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# rel-toast
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
A fixed-position toast notification component for brief feedback messages. Features default and error type variants, optional action buttons, auto-dismiss functionality, and slide-up animation. Positioned at the bottom-center of the viewport with `z-index: 1000`.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
```html
|
|
8
|
+
<!-- Basic toast (must be shown programmatically) -->
|
|
9
|
+
<rel-toast id="toast1" message="File saved successfully" type="default"></rel-toast>
|
|
10
|
+
<script>
|
|
11
|
+
const toast = document.getElementById('toast1');
|
|
12
|
+
toast.show(); // Show the toast
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<!-- Error toast -->
|
|
16
|
+
<rel-toast id="errorToast" message="Failed to upload file" type="error"></rel-toast>
|
|
17
|
+
<script>
|
|
18
|
+
document.getElementById('errorToast').show();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<!-- With action button -->
|
|
22
|
+
<rel-toast
|
|
23
|
+
id="actionToast"
|
|
24
|
+
message="Connection lost"
|
|
25
|
+
actionLabel="RETRY"
|
|
26
|
+
type="default">
|
|
27
|
+
</rel-toast>
|
|
28
|
+
<script>
|
|
29
|
+
const actionToast = document.getElementById('actionToast');
|
|
30
|
+
actionToast.addEventListener('action', () => {
|
|
31
|
+
console.log('Retry clicked');
|
|
32
|
+
// Perform retry logic
|
|
33
|
+
});
|
|
34
|
+
actionToast.show();
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<!-- Custom duration (7 seconds) -->
|
|
38
|
+
<rel-toast
|
|
39
|
+
id="customDuration"
|
|
40
|
+
message="Download complete"
|
|
41
|
+
duration="7000">
|
|
42
|
+
</rel-toast>
|
|
43
|
+
<script>
|
|
44
|
+
document.getElementById('customDuration').show();
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<!-- No auto-dismiss -->
|
|
48
|
+
<rel-toast
|
|
49
|
+
id="persist"
|
|
50
|
+
message="Manual dismiss required"
|
|
51
|
+
duration="0">
|
|
52
|
+
</rel-toast>
|
|
53
|
+
<script>
|
|
54
|
+
const persist = document.getElementById('persist');
|
|
55
|
+
persist.show();
|
|
56
|
+
// Manually close later: persist.close();
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<!-- Listen for dismiss -->
|
|
60
|
+
<rel-toast id="notif" message="Update available"></rel-toast>
|
|
61
|
+
<script>
|
|
62
|
+
const notif = document.getElementById('notif');
|
|
63
|
+
notif.addEventListener('dismissed', () => {
|
|
64
|
+
console.log('Toast was dismissed');
|
|
65
|
+
});
|
|
66
|
+
notif.show();
|
|
67
|
+
</script>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## API
|
|
71
|
+
|
|
72
|
+
### Attributes/Properties
|
|
73
|
+
| Name | Type | Default | Description |
|
|
74
|
+
|------|------|---------|-------------|
|
|
75
|
+
| `message` | `string` | `""` | Toast message text |
|
|
76
|
+
| `type` | `"default" \| "error"` | `"default"` | Visual style (default: dark gray, error: red) |
|
|
77
|
+
| `actionLabel` | `string` | - | Optional action button label (e.g., "UNDO", "RETRY") |
|
|
78
|
+
| `duration` | `number` | `4000` | Auto-dismiss duration in milliseconds (0 = no auto-dismiss) |
|
|
79
|
+
|
|
80
|
+
### Methods
|
|
81
|
+
| Method | Description |
|
|
82
|
+
|--------|-------------|
|
|
83
|
+
| `show()` | Display the toast and start auto-dismiss timer |
|
|
84
|
+
| `close()` | Manually dismiss the toast and clear timer |
|
|
85
|
+
|
|
86
|
+
## Events
|
|
87
|
+
| Event | Detail | Description |
|
|
88
|
+
|-------|--------|-------------|
|
|
89
|
+
| `action` | None | Fired when the action button is clicked (toast auto-closes after) |
|
|
90
|
+
| `dismissed` | None | Fired when toast is dismissed (via action, close button, or auto-dismiss) |
|
|
91
|
+
|
|
92
|
+
## Slots
|
|
93
|
+
Default slot not used.
|
|
94
|
+
|
|
95
|
+
## CSS Parts
|
|
96
|
+
None exposed.
|
|
97
|
+
|
|
98
|
+
## Tokens / CSS Variables
|
|
99
|
+
|
|
100
|
+
### Semantic Tokens
|
|
101
|
+
- `--rel-fontFamilies-primary` - Font family
|
|
102
|
+
- `--rel-fontSizes-md` - Font size
|
|
103
|
+
|
|
104
|
+
### Spacing Tokens
|
|
105
|
+
- `--rel-spacing-space-sm` - Gap between elements
|
|
106
|
+
- `--rel-spacing-space-md` - Padding
|
|
107
|
+
- `--rel-spacing-space-2xs` - Close button padding and margin
|
|
108
|
+
|
|
109
|
+
### Color Tokens
|
|
110
|
+
- `--rel-surface-toast` - Default background color (dark gray)
|
|
111
|
+
- `--rel-colors-red-secondary` - Error background color
|
|
112
|
+
- `--rel-colors-white` - Text and focus outline color
|
|
113
|
+
- `--rel-colors-green-primary` - Action button text color
|
|
114
|
+
|
|
115
|
+
### Shadow Tokens
|
|
116
|
+
- `--rel-shadows-shadow-300` - Drop shadow
|
|
117
|
+
|
|
118
|
+
### Border Tokens
|
|
119
|
+
- `--rel-borders-radius-border-radius-200` - Border radius
|
|
120
|
+
|
|
121
|
+
## Accessibility
|
|
122
|
+
- Uses `role="status"` and `aria-live="polite"` for screen reader announcements
|
|
123
|
+
- Focus styles on action and close buttons
|
|
124
|
+
- Animation respects `prefers-reduced-motion` media query
|
|
125
|
+
- Close button has `aria-label="Dismiss"`
|
|
126
|
+
|
|
127
|
+
### Keyboard Interactions
|
|
128
|
+
- `Tab` - Focus action button or close button
|
|
129
|
+
- `Space/Enter` - Activate focused button
|
|
130
|
+
|
|
131
|
+
### Focus Behavior
|
|
132
|
+
- Buttons show 2px white outline with 2px offset when focused
|
|
133
|
+
- Border radius applied to maintain visual consistency
|
|
134
|
+
|
|
135
|
+
## Notes / Edge Cases
|
|
136
|
+
- **Important:** Toasts are hidden by default; you must call `show()` to display them
|
|
137
|
+
- The component uses `:host` with `position: fixed`, `bottom: 24px`, `left: 50%`, `transform: translateX(-50%)` for bottom-center positioning
|
|
138
|
+
- `z-index: 1000` ensures toasts appear above most content
|
|
139
|
+
- The toast container is `display: inline-flex` with `pointer-events: all` (while`:host` has `pointer-events: none`)
|
|
140
|
+
- Auto-dismiss timer starts when `show()` is called
|
|
141
|
+
- Calling `close()` sets `visible = false`, dispatches `dismissed`, and clears the timeout
|
|
142
|
+
- Clicking the action button dispatches `action` then automatically calls `close()`
|
|
143
|
+
- Setting `duration="0"` disables auto-dismiss
|
|
144
|
+
- The component includes a `fadeInUp` animation (20px translateY → 0, opacity 0 → 1) on appearance
|
|
145
|
+
- Animation disabled when `prefers-reduced-motion: reduce` is set
|
|
146
|
+
- When `visible` is false, `hidden` class applies `display: none`
|
|
147
|
+
- **Type variants**: `default` uses `--rel-surface-toast`, `error` uses `--rel-colors-red-secondary`
|
|
148
|
+
- The toast appears inline-flex, so it sizes to content width
|
|
@@ -9,9 +9,10 @@ export class RelToast extends LitElement {
|
|
|
9
9
|
@property({ type: String }) type: ToastType = "default";
|
|
10
10
|
@property({ type: String }) actionLabel?: string;
|
|
11
11
|
@property({ type: Number }) duration: number = 4000;
|
|
12
|
+
@property({ type: Boolean, reflect: true }) show = false;
|
|
12
13
|
|
|
13
14
|
@state() private visible: boolean = false;
|
|
14
|
-
private
|
|
15
|
+
private _dismissTimer: ReturnType<typeof setTimeout> | null = null;
|
|
15
16
|
|
|
16
17
|
static styles = css`
|
|
17
18
|
:host {
|
|
@@ -23,7 +24,7 @@ export class RelToast extends LitElement {
|
|
|
23
24
|
justify-content: center;
|
|
24
25
|
width: 100%;
|
|
25
26
|
pointer-events: none;
|
|
26
|
-
z-index:
|
|
27
|
+
z-index: var(--rel-z-toast, 9000);
|
|
27
28
|
font-family: var(--rel-fontFamilies-primary);
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -31,20 +32,20 @@ export class RelToast extends LitElement {
|
|
|
31
32
|
.toast {
|
|
32
33
|
display: inline-flex;
|
|
33
34
|
align-items: center;
|
|
34
|
-
gap: 12px;
|
|
35
|
-
background-color: #
|
|
36
|
-
color: #fff;
|
|
37
|
-
font-family: var(--rel-fontFamilies-primary);
|
|
38
|
-
font-size:
|
|
39
|
-
border-radius: 8px;
|
|
40
|
-
padding:
|
|
41
|
-
box-shadow:
|
|
35
|
+
gap: var(--rel-spacing-space-sm, 12px);
|
|
36
|
+
background-color: var(--rel-surface-toast, #404040);
|
|
37
|
+
color: var(--rel-colors-white, #fff);
|
|
38
|
+
font-family: var(--rel-fontFamilies-primary, sans-serif);
|
|
39
|
+
font-size: var(--rel-fontSizes-md, 14px);
|
|
40
|
+
border-radius: var(--rel-borders-radius-border-radius-200, 8px);
|
|
41
|
+
padding: var(--rel-spacing-space-md, 16px);
|
|
42
|
+
box-shadow: var(--rel-shadows-shadow-300);
|
|
42
43
|
pointer-events: all;
|
|
43
44
|
animation: fadeInUp 0.25s ease;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
.toast.error {
|
|
47
|
-
background-color:
|
|
48
|
+
background-color: var(--rel-colors-red-secondary);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
.toast.hidden {
|
|
@@ -60,7 +61,7 @@ export class RelToast extends LitElement {
|
|
|
60
61
|
.toast-action {
|
|
61
62
|
background: transparent;
|
|
62
63
|
border: none;
|
|
63
|
-
color:
|
|
64
|
+
color: var(--rel-colors-green-primary);
|
|
64
65
|
font-weight: 600;
|
|
65
66
|
font-size: 0.85rem;
|
|
66
67
|
cursor: pointer;
|
|
@@ -72,6 +73,12 @@ export class RelToast extends LitElement {
|
|
|
72
73
|
opacity: 0.75;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
.toast-action:focus-visible {
|
|
77
|
+
outline: 2px solid var(--rel-colors-white, #fff);
|
|
78
|
+
outline-offset: 2px;
|
|
79
|
+
border-radius: 2px;
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
/* Close button */
|
|
76
83
|
.toast-close {
|
|
77
84
|
background: transparent;
|
|
@@ -80,14 +87,26 @@ export class RelToast extends LitElement {
|
|
|
80
87
|
font-size: 1rem;
|
|
81
88
|
cursor: pointer;
|
|
82
89
|
line-height: 1;
|
|
83
|
-
padding:
|
|
84
|
-
margin-left: 4px;
|
|
90
|
+
padding: var(--rel-spacing-space-2xs, 4px);
|
|
91
|
+
margin-left: var(--rel-spacing-space-2xs, 4px);
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
.toast-close:hover {
|
|
88
95
|
opacity: 0.75;
|
|
89
96
|
}
|
|
90
97
|
|
|
98
|
+
.toast-close:focus-visible {
|
|
99
|
+
outline: 2px solid var(--rel-colors-white, #fff);
|
|
100
|
+
outline-offset: 2px;
|
|
101
|
+
border-radius: 2px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@media (prefers-reduced-motion: reduce) {
|
|
105
|
+
.toast {
|
|
106
|
+
animation: none;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
91
110
|
/* Animations */
|
|
92
111
|
@keyframes fadeInUp {
|
|
93
112
|
from {
|
|
@@ -101,25 +120,45 @@ export class RelToast extends LitElement {
|
|
|
101
120
|
}
|
|
102
121
|
`;
|
|
103
122
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
willUpdate(changed: Map<string, unknown>) {
|
|
124
|
+
if (!changed.has('show')) return;
|
|
125
|
+
if (this.show) {
|
|
126
|
+
this.visible = true;
|
|
127
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
128
|
+
if (this.duration > 0) {
|
|
129
|
+
this._dismissTimer = setTimeout(() => this.close(), this.duration);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
this.visible = false;
|
|
133
|
+
if (this._dismissTimer) {
|
|
134
|
+
clearTimeout(this._dismissTimer);
|
|
135
|
+
this._dismissTimer = null;
|
|
136
|
+
}
|
|
109
137
|
}
|
|
110
138
|
}
|
|
111
139
|
|
|
140
|
+
disconnectedCallback() {
|
|
141
|
+
super.disconnectedCallback();
|
|
142
|
+
if (this._dismissTimer) clearTimeout(this._dismissTimer);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
open() {
|
|
146
|
+
this.show = true;
|
|
147
|
+
}
|
|
148
|
+
|
|
112
149
|
close() {
|
|
150
|
+
this.show = false;
|
|
113
151
|
this.visible = false;
|
|
152
|
+
if (this._dismissTimer) {
|
|
153
|
+
clearTimeout(this._dismissTimer);
|
|
154
|
+
this._dismissTimer = null;
|
|
155
|
+
}
|
|
114
156
|
this.dispatchEvent(
|
|
115
157
|
new CustomEvent("dismissed", {
|
|
116
158
|
bubbles: true,
|
|
117
159
|
composed: true,
|
|
118
160
|
})
|
|
119
161
|
);
|
|
120
|
-
if (this.timeoutId) {
|
|
121
|
-
clearTimeout(this.timeoutId);
|
|
122
|
-
}
|
|
123
162
|
}
|
|
124
163
|
|
|
125
164
|
private handleActionClick(event: Event) {
|
|
@@ -135,7 +174,7 @@ export class RelToast extends LitElement {
|
|
|
135
174
|
|
|
136
175
|
render() {
|
|
137
176
|
return html`
|
|
138
|
-
<div class="toast ${this.type} ${this.visible ? "" : "hidden"}">
|
|
177
|
+
<div class="toast ${this.type} ${this.visible ? "" : "hidden"}" role="status" aria-live="polite">
|
|
139
178
|
<span class="toast-message">${this.message}</span>
|
|
140
179
|
|
|
141
180
|
${this.actionLabel
|
|
@@ -147,7 +186,7 @@ export class RelToast extends LitElement {
|
|
|
147
186
|
</button>`
|
|
148
187
|
: ""}
|
|
149
188
|
|
|
150
|
-
<button class="toast-close" @click="${this.close}">✕</button>
|
|
189
|
+
<button class="toast-close" @click="${this.close}" aria-label="Dismiss">✕</button>
|
|
151
190
|
</div>
|
|
152
191
|
`;
|
|
153
192
|
}
|