@reliability-design/web 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/components/rel-accordion/README.md +132 -0
  2. package/dist/components/rel-accordion/rel-accordion.ts +47 -16
  3. package/dist/components/rel-alert/README.md +141 -0
  4. package/dist/components/rel-alert/rel-alert.ts +101 -20
  5. package/dist/components/rel-avatar/README.md +119 -0
  6. package/dist/components/rel-avatar/rel-avatar.ts +3 -2
  7. package/dist/components/rel-badge/README.md +86 -0
  8. package/dist/components/rel-badge/rel-badge.ts +11 -7
  9. package/dist/components/rel-breadcrumb/README.md +102 -0
  10. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +12 -6
  11. package/dist/components/rel-button/README.md +130 -0
  12. package/dist/components/rel-button/rel-button.ts +64 -8
  13. package/dist/components/rel-calendar/README.md +109 -0
  14. package/dist/components/rel-calendar/index.ts +1 -0
  15. package/dist/components/rel-calendar/rel-calendar.ts +467 -0
  16. package/dist/components/rel-card/README.md +94 -0
  17. package/dist/components/rel-card/rel-card.ts +12 -3
  18. package/dist/components/rel-cardmedia/README.md +127 -0
  19. package/dist/components/rel-cardmedia/rel-cardmedia.ts +14 -4
  20. package/dist/components/rel-carousel/README.md +166 -0
  21. package/dist/components/rel-carousel/index.ts +1 -0
  22. package/dist/components/rel-carousel/rel-carousel.ts +468 -0
  23. package/dist/components/rel-chart/README.md +286 -0
  24. package/dist/components/rel-chart/index.ts +1 -0
  25. package/dist/components/rel-chart/rel-chart.ts +736 -0
  26. package/dist/components/rel-checkbox/README.md +77 -0
  27. package/dist/components/rel-checkbox/rel-checkbox.ts +51 -12
  28. package/dist/components/rel-chip/README.md +114 -0
  29. package/dist/components/rel-chip/rel-chip.ts +83 -9
  30. package/dist/components/rel-container/README.md +99 -0
  31. package/dist/components/rel-date-picker/README.md +253 -0
  32. package/dist/components/rel-date-picker/index.ts +1 -0
  33. package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
  34. package/dist/components/rel-divider/README.md +100 -0
  35. package/dist/components/rel-divider/rel-divider.ts +9 -0
  36. package/dist/components/rel-drawer/README.md +200 -0
  37. package/dist/components/rel-drawer/index.ts +1 -0
  38. package/dist/components/rel-drawer/rel-drawer-footer.ts +29 -0
  39. package/dist/components/rel-drawer/rel-drawer.ts +612 -0
  40. package/dist/components/rel-kbd/README.md +151 -0
  41. package/dist/components/rel-kbd/rel-kbd.ts +118 -0
  42. package/dist/components/rel-layout/README.md +88 -0
  43. package/dist/components/rel-menu/README.md +151 -0
  44. package/dist/components/rel-menu/rel-menu.ts +64 -24
  45. package/dist/components/rel-menubar/README.md +329 -0
  46. package/dist/components/rel-menubar/index.ts +2 -0
  47. package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
  48. package/dist/components/rel-menubar/rel-menubar.ts +605 -0
  49. package/dist/components/rel-nav-menu/README.md +289 -0
  50. package/dist/components/rel-nav-menu/index.ts +3 -0
  51. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +240 -0
  52. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
  53. package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -0
  54. package/dist/components/rel-navbar/README.md +126 -0
  55. package/dist/components/rel-navbar/rel-navbar.ts +104 -0
  56. package/dist/components/rel-otp/README.md +440 -0
  57. package/dist/components/rel-otp/index.ts +1 -0
  58. package/dist/components/rel-otp/rel-otp.ts +504 -0
  59. package/dist/components/rel-pagination/README.md +410 -0
  60. package/dist/components/rel-pagination/index.ts +1 -0
  61. package/dist/components/rel-pagination/rel-pagination.ts +381 -0
  62. package/dist/components/rel-popover/README.md +568 -0
  63. package/dist/components/rel-popover/index.ts +2 -0
  64. package/dist/components/rel-popover/rel-popover-content.ts +55 -0
  65. package/dist/components/rel-popover/rel-popover.ts +330 -0
  66. package/dist/components/rel-progress/README.md +110 -0
  67. package/dist/components/rel-progress/rel-progress.ts +32 -5
  68. package/dist/components/rel-radio/README.md +120 -0
  69. package/dist/components/rel-radio/rel-radio.ts +31 -6
  70. package/dist/components/rel-resizable/README.md +140 -0
  71. package/dist/components/rel-resizable/index.ts +3 -0
  72. package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
  73. package/dist/components/rel-resizable/rel-resizable-handle.ts +145 -0
  74. package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
  75. package/dist/components/rel-segmented-button/README.md +141 -0
  76. package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
  77. package/dist/components/rel-select/README.md +107 -0
  78. package/dist/components/rel-select/rel-select.ts +28 -14
  79. package/dist/components/rel-sheet/README.md +224 -0
  80. package/dist/components/rel-sheet/rel-sheet.ts +684 -0
  81. package/dist/components/rel-sidebar/README.md +209 -0
  82. package/dist/components/rel-sidebar/rel-sidebar.ts +1166 -0
  83. package/dist/components/rel-slider/README.md +135 -0
  84. package/dist/components/rel-slider/rel-slider.ts +44 -9
  85. package/dist/components/rel-snackbar/README.md +134 -0
  86. package/dist/components/rel-snackbar/rel-snackbar.ts +109 -14
  87. package/dist/components/rel-stepper/README.md +160 -0
  88. package/dist/components/rel-stepper/rel-stepper.ts +38 -27
  89. package/dist/components/rel-switch/README.md +102 -0
  90. package/dist/components/rel-switch/rel-switch.ts +34 -12
  91. package/dist/components/rel-tab/README.md +163 -0
  92. package/dist/components/rel-tab/rel-tab.ts +79 -23
  93. package/dist/components/rel-table/README.md +178 -0
  94. package/dist/components/rel-table/rel-table-body.ts +21 -0
  95. package/dist/components/rel-table/rel-table-caption.ts +27 -0
  96. package/dist/components/rel-table/rel-table-cell.ts +42 -0
  97. package/dist/components/rel-table/rel-table-footer.ts +23 -0
  98. package/dist/components/rel-table/rel-table-head.ts +45 -0
  99. package/dist/components/rel-table/rel-table-header.ts +21 -0
  100. package/dist/components/rel-table/rel-table-row.ts +36 -0
  101. package/dist/components/rel-table/rel-table.ts +53 -0
  102. package/dist/components/rel-textfield/README.md +90 -0
  103. package/dist/components/rel-textfield/rel-textfield.ts +55 -39
  104. package/dist/components/rel-toast/README.md +148 -0
  105. package/dist/components/rel-toast/rel-toast.ts +63 -24
  106. package/dist/components/rel-tooltip/README.md +103 -0
  107. package/dist/components/rel-tooltip/rel-tooltip.ts +123 -80
  108. package/dist/components/rel-topbar/rel-topbar-actions.ts +23 -0
  109. package/dist/components/rel-topbar/rel-topbar-brand.ts +46 -0
  110. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +33 -0
  111. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
  112. package/dist/components/rel-topbar/rel-topbar-item.ts +315 -0
  113. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +23 -0
  114. package/dist/components/rel-topbar/rel-topbar-profile.ts +248 -0
  115. package/dist/components/rel-topbar/rel-topbar-utilities.ts +23 -0
  116. package/dist/components/rel-topbar/rel-topbar.ts +329 -0
  117. package/dist/index.d.mts +831 -7
  118. package/dist/index.d.ts +831 -7
  119. package/dist/index.js +9500 -1564
  120. package/dist/index.js.map +1 -1
  121. package/dist/index.mjs +9240 -1344
  122. package/dist/index.mjs.map +1 -1
  123. package/dist/styles/vars.css +187 -25
  124. package/dist/vars-TBB4BMSD.css +346 -0
  125. package/package.json +2 -1
  126. package/dist/vars-6ZIUPIU4.css +0 -184
@@ -0,0 +1,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" | "focused" | "error" | "disabled";
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: TextFieldState = "default";
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: 0.85rem;
38
- font-weight: 500;
39
- color: #111;
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 #ccc;
48
- border-radius: 6px;
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: 0.9rem;
64
- padding: 0.5rem 0.75rem;
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: #999;
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: #7e8186;
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: 0.5rem 1rem;
91
- font-size: 0.9rem;
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: 0.688rem 1rem;
96
- font-size: 1rem;
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: 0.875rem 1rem;
101
- font-size: 1.1rem;
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: 2.5rem;
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: 2.5rem;
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: #111;
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: #d50000;
132
+ border-color: var(--rel-border-error, #cc0000);
120
133
  }
121
134
 
122
135
  .textfield-wrapper.disabled .textfield-container {
123
- border-color: #d7d9db;
136
+ border-color: var(--rel-border-default, #ddd);
124
137
  }
125
138
 
126
139
  /* Supporting text */
127
140
  .textfield-support {
128
- font-size: 0.75rem;
129
- color: #666;
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: #d50000;
146
+ color: var(--rel-text-error, #cc0000);
134
147
  }
135
- `;
136
148
 
137
- private get currentState(): TextFieldState {
138
- return this.state !== "default" ? this.state : this.internalState;
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" && this.state !== "error") {
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>("input-change", {
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.currentState,
170
- ].join(" ");
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 timeoutId?: number;
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: 1000;
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: #111;
36
- color: #fff;
37
- font-family: var(--rel-fontFamilies-primary);
38
- font-size: 0.9rem;
39
- border-radius: 8px;
40
- padding: 1rem 1rem;
41
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
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: #d50000;
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: #0edb90;
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: 0;
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
- show() {
105
- this.visible = true;
106
-
107
- if (this.duration > 0) {
108
- this.timeoutId = window.setTimeout(() => this.close(), this.duration);
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
  }