@reliability-design/web 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/components/rel-accordion/rel-accordion.stories.ts +313 -0
  2. package/dist/components/rel-accordion/rel-accordion.ts +114 -0
  3. package/dist/components/rel-alert/rel-alert.stories.ts +412 -0
  4. package/dist/components/rel-alert/rel-alert.ts +194 -0
  5. package/dist/components/rel-avatar/rel-avatar.stories.ts +327 -0
  6. package/dist/components/rel-avatar/rel-avatar.ts +100 -0
  7. package/dist/components/rel-badge/rel-badge.stories.ts +350 -0
  8. package/dist/components/rel-badge/rel-badge.ts +70 -0
  9. package/dist/components/rel-breadcrumb/rel-breadcrumb.stories.ts +306 -0
  10. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +97 -0
  11. package/dist/components/rel-button/rel-button.stories.ts +356 -0
  12. package/dist/components/rel-button/rel-button.ts +204 -0
  13. package/dist/components/rel-card/rel-card.stories.ts +299 -0
  14. package/dist/components/rel-card/rel-card.ts +33 -0
  15. package/dist/components/rel-cardmedia/rel-cardmedia.stories.ts +368 -0
  16. package/dist/components/rel-cardmedia/rel-cardmedia.ts +80 -0
  17. package/dist/components/rel-checkbox/rel-checkbox.stories.ts +414 -0
  18. package/dist/components/rel-checkbox/rel-checkbox.ts +164 -0
  19. package/dist/components/rel-chip/rel-chip.stories.ts +552 -0
  20. package/dist/components/rel-chip/rel-chip.ts +261 -0
  21. package/dist/components/rel-container/rel-container.stories.ts +239 -0
  22. package/dist/components/rel-container/rel-container.ts +74 -0
  23. package/dist/components/rel-divider/rel-divider.stories.ts +229 -0
  24. package/dist/components/rel-divider/rel-divider.ts +44 -0
  25. package/dist/components/rel-layout/rel-layout.stories.ts +713 -0
  26. package/dist/components/rel-layout/rel-layout.ts +60 -0
  27. package/dist/components/rel-menu/rel-menu.stories.ts +910 -0
  28. package/dist/components/rel-menu/rel-menu.ts +319 -0
  29. package/dist/components/rel-progress/rel-progress.stories.ts +817 -0
  30. package/dist/components/rel-progress/rel-progress.ts +182 -0
  31. package/dist/components/rel-radio/rel-radio.stories.ts +1048 -0
  32. package/dist/components/rel-radio/rel-radio.ts +187 -0
  33. package/dist/components/rel-segmented-button/rel-segmented-button.stories.ts +1079 -0
  34. package/dist/components/rel-segmented-button/rel-segmented-button.ts +150 -0
  35. package/dist/components/rel-select/rel-select.stories.ts +1201 -0
  36. package/dist/components/rel-select/rel-select.ts +261 -0
  37. package/dist/components/rel-slider/rel-slider.stories.ts +730 -0
  38. package/dist/components/rel-slider/rel-slider.ts +440 -0
  39. package/dist/components/rel-snackbar/rel-snackbar.stories.ts +808 -0
  40. package/dist/components/rel-snackbar/rel-snackbar.ts +212 -0
  41. package/dist/components/rel-stepper/rel-stepper.stories.ts +752 -0
  42. package/dist/components/rel-stepper/rel-stepper.ts +425 -0
  43. package/dist/components/rel-switch/rel-switch.stories.ts +441 -0
  44. package/dist/components/rel-switch/rel-switch.ts +161 -0
  45. package/dist/components/rel-tab/rel-tab.stories.ts +492 -0
  46. package/dist/components/rel-tab/rel-tab.ts +145 -0
  47. package/dist/components/rel-textfield/rel-textfield.stories.ts +710 -0
  48. package/dist/components/rel-textfield/rel-textfield.ts +224 -0
  49. package/dist/components/rel-toast/rel-toast.stories.ts +394 -0
  50. package/dist/components/rel-toast/rel-toast.ts +154 -0
  51. package/dist/components/rel-tooltip/rel-tooltip.stories.ts +364 -0
  52. package/dist/components/rel-tooltip/rel-tooltip.ts +80 -0
  53. package/dist/index.d.mts +372 -0
  54. package/dist/index.d.ts +372 -0
  55. package/dist/index.js +4593 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/index.mjs +4544 -0
  58. package/dist/index.mjs.map +1 -0
  59. package/dist/styles/vars.css +184 -0
  60. package/package.json +32 -0
@@ -0,0 +1,319 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+
4
+ export type MenuType = "default" | "checkbox" | "arrow";
5
+
6
+ export interface MenuItem {
7
+ label: string;
8
+ active?: boolean;
9
+ checked?: boolean;
10
+ disabled?: boolean;
11
+ submenu?: MenuItem[];
12
+ open?: boolean;
13
+ }
14
+
15
+ @customElement("rel-menu")
16
+ export class RelMenu extends LitElement {
17
+ @property({ type: String }) type: MenuType = "default";
18
+
19
+ @property({ type: Array }) items: MenuItem[] = [];
20
+
21
+ @state() private focusedIndex: number = 0;
22
+
23
+ static styles = css`
24
+ :host {
25
+ display: inline-block;
26
+ }
27
+
28
+ .rel-menu {
29
+ position: relative;
30
+ display: inline-block;
31
+ background: var(--rel-colors-white);
32
+ border-radius: var(--rel-borders-radius-border-radius-300);
33
+ box-shadow: var(--rel-shadows-shadow-300);
34
+ min-width: 220px;
35
+ font-family: var(--rel-fontFamilies-primary);
36
+ font-size: var(--rel-fontSizes-sm);
37
+ padding: var(--rel-spacing-space-3xs) 0;
38
+ outline: none;
39
+ }
40
+
41
+ .rel-menu-item {
42
+ position: relative;
43
+ display: flex;
44
+ align-items: center;
45
+ gap: var(--rel-spacing-space-xs);
46
+ padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-sm);
47
+ line-height: var(--rel-lineHeights-sm);
48
+ cursor: pointer;
49
+ transition: background 0.15s ease, color 0.15s ease;
50
+ user-select: none;
51
+ }
52
+
53
+ .rel-menu-item:hover {
54
+ background: var(--rel-colors-gray-border);
55
+ }
56
+
57
+ .rel-menu-item:active {
58
+ background: var(--rel-colors-gray-tertiary);
59
+ }
60
+
61
+ .rel-menu-item.active {
62
+ background: var(--rel-colors-gray-tertiary);
63
+ }
64
+
65
+ .rel-menu-item.disabled {
66
+ background-color: var(--rel-colors-gray-light);
67
+ color: var(--rel-colors-gray-placeholder);
68
+ cursor: not-allowed;
69
+ }
70
+
71
+ .rel-menu-item[data-focused="true"] {
72
+ border-top: var(--rel-borders-width-stroke-200) solid var(--rel-colors-gray-primary);
73
+ border-bottom: var(--rel-borders-width-stroke-200) solid var(--rel-colors-gray-primary);
74
+ }
75
+
76
+ .menu-check-row {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ gap: var(--rel-spacing-space-2xs);
80
+ width: 100%;
81
+ }
82
+
83
+ .menu-checkbox {
84
+ width: 16px;
85
+ height: 16px;
86
+ cursor: pointer;
87
+ }
88
+
89
+ .menu-checkbox:disabled {
90
+ cursor: not-allowed;
91
+ }
92
+
93
+ .menu-label {
94
+ flex: 1 1 auto;
95
+ }
96
+
97
+ .chevron {
98
+ font-size: var(--rel-fontSizes-lg);
99
+ opacity: 0.75;
100
+ transform: translateY(1px);
101
+ }
102
+
103
+ .rel-submenu {
104
+ position: absolute;
105
+ top: calc(-1 * var(--rel-spacing-space-3xs));
106
+ left: calc(100% + var(--rel-spacing-space-2xs));
107
+ min-width: 200px;
108
+ background: var(--rel-colors-white);
109
+ border-radius: var(--rel-borders-radius-border-radius-300);
110
+ box-shadow: var(--rel-shadows-shadow-300);
111
+ padding: var(--rel-spacing-space-3xs) 0;
112
+ display: none;
113
+ z-index: 20;
114
+ }
115
+
116
+ .rel-submenu.visible {
117
+ display: block;
118
+ }
119
+
120
+ .rel-submenu .rel-menu-item {
121
+ padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-sm);
122
+ }
123
+ `;
124
+
125
+ connectedCallback() {
126
+ super.connectedCallback();
127
+ document.addEventListener("click", this.handleDocumentClick);
128
+ }
129
+
130
+ disconnectedCallback() {
131
+ super.disconnectedCallback();
132
+ document.removeEventListener("click", this.handleDocumentClick);
133
+ }
134
+
135
+ private handleDocumentClick = (e: MouseEvent) => {
136
+ const path = e.composedPath() as HTMLElement[];
137
+ const hit = path.some((n) => n?.classList?.contains("rel-menu"));
138
+ if (!hit) {
139
+ this.items.forEach((i) => (i.open = false));
140
+ this.requestUpdate();
141
+ }
142
+ };
143
+
144
+ private onItemEnter(item: MenuItem) {
145
+ if (this.type === "arrow" && item.submenu?.length) {
146
+ this.items.forEach((i) => {
147
+ if (i !== item) i.open = false;
148
+ });
149
+ item.open = true;
150
+ this.requestUpdate();
151
+ }
152
+ }
153
+
154
+ private onItemLeave(_item: MenuItem) {
155
+ // Keep open so user can move into submenu
156
+ }
157
+
158
+ private onItemClick(item: MenuItem, index: number, ev?: Event) {
159
+ ev?.stopPropagation();
160
+ if (item.disabled) return;
161
+
162
+ this.focusedIndex = index;
163
+
164
+ if (this.type === "checkbox") {
165
+ item.checked = !item.checked;
166
+ this.dispatchEvent(
167
+ new CustomEvent("item-checked", {
168
+ detail: { item, checked: item.checked },
169
+ bubbles: true,
170
+ composed: true,
171
+ })
172
+ );
173
+ this.requestUpdate();
174
+ return;
175
+ }
176
+
177
+ if (this.type === "arrow" && item.submenu?.length) {
178
+ const next = !item.open;
179
+ this.items.forEach((i) => (i.open = false));
180
+ item.open = next;
181
+ this.requestUpdate();
182
+ return;
183
+ }
184
+
185
+ // Default: set active
186
+ this.items.forEach((i) => (i.active = false));
187
+ item.active = true;
188
+ this.dispatchEvent(
189
+ new CustomEvent("item-selected", {
190
+ detail: { item },
191
+ bubbles: true,
192
+ composed: true,
193
+ })
194
+ );
195
+ this.requestUpdate();
196
+ }
197
+
198
+ private onSubClick(parent: MenuItem, sub: MenuItem, ev?: Event) {
199
+ ev?.stopPropagation();
200
+ this.items.forEach((i) => {
201
+ i.active = false;
202
+ i.submenu?.forEach((s) => (s.active = false));
203
+ });
204
+ sub.active = true;
205
+ parent.open = false;
206
+ this.dispatchEvent(
207
+ new CustomEvent("submenu-selected", {
208
+ detail: { parent, sub },
209
+ bubbles: true,
210
+ composed: true,
211
+ })
212
+ );
213
+ this.requestUpdate();
214
+ }
215
+
216
+ private toggleCheckbox(item: MenuItem, ev: Event) {
217
+ ev.stopPropagation();
218
+ if (!item.disabled) {
219
+ item.checked = !item.checked;
220
+ this.dispatchEvent(
221
+ new CustomEvent("item-checked", {
222
+ detail: { item, checked: item.checked },
223
+ bubbles: true,
224
+ composed: true,
225
+ })
226
+ );
227
+ this.requestUpdate();
228
+ }
229
+ }
230
+
231
+ private onKeydown(ev: KeyboardEvent) {
232
+ const last = this.items.length - 1;
233
+
234
+ if (ev.key === "ArrowDown") {
235
+ this.focusedIndex = Math.min(last, this.focusedIndex + 1);
236
+ ev.preventDefault();
237
+ } else if (ev.key === "ArrowUp") {
238
+ this.focusedIndex = Math.max(0, this.focusedIndex - 1);
239
+ ev.preventDefault();
240
+ } else if (ev.key === "ArrowRight") {
241
+ const item = this.items[this.focusedIndex];
242
+ if (this.type === "arrow" && item?.submenu?.length) {
243
+ item.open = true;
244
+ this.requestUpdate();
245
+ }
246
+ } else if (ev.key === "ArrowLeft" || ev.key === "Escape") {
247
+ this.items.forEach((i) => (i.open = false));
248
+ this.requestUpdate();
249
+ } else if (ev.key === "Enter" || ev.key === " ") {
250
+ const item = this.items[this.focusedIndex];
251
+ this.onItemClick(item, this.focusedIndex);
252
+ ev.preventDefault();
253
+ }
254
+ }
255
+
256
+ private renderMenuItem(item: MenuItem, index: number) {
257
+ const isFocused = this.focusedIndex === index;
258
+
259
+ return html`
260
+ <div
261
+ class="rel-menu-item ${item.active ? "active" : ""} ${item.disabled ? "disabled" : ""}"
262
+ role="menuitem"
263
+ ?data-focused="${isFocused}"
264
+ @mouseenter="${() => this.onItemEnter(item)}"
265
+ @mouseleave="${() => this.onItemLeave(item)}"
266
+ @click="${(e: Event) => this.onItemClick(item, index, e)}">
267
+ ${this.type === "checkbox"
268
+ ? html`
269
+ <div class="menu-check-row">
270
+ <input
271
+ class="menu-checkbox"
272
+ type="checkbox"
273
+ .checked="${item.checked || false}"
274
+ ?disabled="${item.disabled}"
275
+ @click="${(e: Event) => this.toggleCheckbox(item, e)}" />
276
+ <span class="menu-label">${item.label}</span>
277
+ </div>
278
+ `
279
+ : html`
280
+ <span class="menu-label">${item.label}</span>
281
+ ${this.type === "arrow" && item.submenu?.length
282
+ ? html`<span class="chevron">▸</span>`
283
+ : ""}
284
+ `}
285
+ ${this.type === "arrow" && item.submenu?.length
286
+ ? html`
287
+ <div
288
+ class="rel-submenu ${item.open ? "visible" : ""}"
289
+ @click="${(e: Event) => e.stopPropagation()}">
290
+ ${item.submenu.map(
291
+ (sub) => html`
292
+ <div
293
+ class="rel-menu-item ${sub.active ? "active" : ""}"
294
+ role="menuitem"
295
+ @click="${(e: Event) => this.onSubClick(item, sub, e)}">
296
+ <span class="menu-label">${sub.label}</span>
297
+ </div>
298
+ `
299
+ )}
300
+ </div>
301
+ `
302
+ : ""}
303
+ </div>
304
+ `;
305
+ }
306
+
307
+ render() {
308
+ return html`
309
+ <div
310
+ class="rel-menu"
311
+ role="menu"
312
+ tabindex="0"
313
+ @keydown="${this.onKeydown}"
314
+ @click="${(e: Event) => e.stopPropagation()}">
315
+ ${this.items.map((item, index) => this.renderMenuItem(item, index))}
316
+ </div>
317
+ `;
318
+ }
319
+ }