@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,441 @@
1
+ import type { Meta, StoryObj } from "@storybook/web-components";
2
+ import { html } from "lit";
3
+ import "./rel-switch";
4
+
5
+ const meta: Meta = {
6
+ title: "Components/Switch",
7
+ component: "rel-switch",
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ checked: {
11
+ control: "boolean",
12
+ description: "Whether the switch is checked",
13
+ table: {
14
+ type: { summary: "boolean" },
15
+ defaultValue: { summary: "false" },
16
+ },
17
+ },
18
+ disabled: {
19
+ control: "boolean",
20
+ description: "Whether the switch is disabled",
21
+ table: {
22
+ type: { summary: "boolean" },
23
+ defaultValue: { summary: "false" },
24
+ },
25
+ },
26
+ },
27
+ };
28
+
29
+ export default meta;
30
+ type Story = StoryObj;
31
+
32
+ export const Default: Story = {
33
+ args: {
34
+ checked: false,
35
+ disabled: false,
36
+ },
37
+ render: (args) => html`
38
+ <rel-switch
39
+ ?checked=${args.checked}
40
+ ?disabled=${args.disabled}
41
+ ></rel-switch>
42
+ `,
43
+ };
44
+
45
+ export const Checked: Story = {
46
+ args: {
47
+ checked: true,
48
+ disabled: false,
49
+ },
50
+ render: (args) => html`
51
+ <rel-switch
52
+ ?checked=${args.checked}
53
+ ?disabled=${args.disabled}
54
+ ></rel-switch>
55
+ `,
56
+ };
57
+
58
+ export const Disabled: Story = {
59
+ args: {
60
+ checked: false,
61
+ disabled: true,
62
+ },
63
+ render: (args) => html`
64
+ <rel-switch
65
+ ?checked=${args.checked}
66
+ ?disabled=${args.disabled}
67
+ ></rel-switch>
68
+ `,
69
+ };
70
+
71
+ export const DisabledChecked: Story = {
72
+ args: {
73
+ checked: true,
74
+ disabled: true,
75
+ },
76
+ render: (args) => html`
77
+ <rel-switch
78
+ ?checked=${args.checked}
79
+ ?disabled=${args.disabled}
80
+ ></rel-switch>
81
+ `,
82
+ };
83
+
84
+ export const Interactive: Story = {
85
+ render: () => html`
86
+ <div style="display: flex; flex-direction: column; gap: 20px;">
87
+ <div style="display: flex; align-items: center; gap: 12px;">
88
+ <rel-switch></rel-switch>
89
+ <span>Default (Unchecked)</span>
90
+ </div>
91
+ <div style="display: flex; align-items: center; gap: 12px;">
92
+ <rel-switch checked></rel-switch>
93
+ <span>Checked</span>
94
+ </div>
95
+ <div style="display: flex; align-items: center; gap: 12px;">
96
+ <rel-switch disabled></rel-switch>
97
+ <span>Disabled (Unchecked)</span>
98
+ </div>
99
+ <div style="display: flex; align-items: center; gap: 12px;">
100
+ <rel-switch checked disabled></rel-switch>
101
+ <span>Disabled (Checked)</span>
102
+ </div>
103
+ </div>
104
+ `,
105
+ };
106
+
107
+ export const WithEventListener: Story = {
108
+ render: () => {
109
+ const handleChange = (e: CustomEvent) => {
110
+ console.log("Switch changed:", e.detail);
111
+ const output = document.getElementById("switch-output");
112
+ if (output) {
113
+ output.textContent = `Switch is now: ${e.detail ? "ON" : "OFF"}`;
114
+ }
115
+ };
116
+
117
+ return html`
118
+ <div style="display: flex; flex-direction: column; gap: 12px;">
119
+ <div style="display: flex; align-items: center; gap: 12px;">
120
+ <rel-switch @changed=${handleChange}></rel-switch>
121
+ <span>Toggle me and check the output below</span>
122
+ </div>
123
+ <div
124
+ id="switch-output"
125
+ style="padding: 8px; background: #f0f0f0; border-radius: 4px;"
126
+ >
127
+ Switch is now: OFF
128
+ </div>
129
+ </div>
130
+ `;
131
+ },
132
+ };
133
+
134
+ export const WithLabels: Story = {
135
+ render: () => html`
136
+ <div style="display: flex; flex-direction: column; gap: 20px;">
137
+ <div style="display: flex; align-items: center; gap: 12px;">
138
+ <label style="font-size: 14px; font-weight: 500;">Enable Notifications</label>
139
+ <rel-switch checked></rel-switch>
140
+ </div>
141
+ <div style="display: flex; align-items: center; gap: 12px;">
142
+ <label style="font-size: 14px; font-weight: 500;">Dark Mode</label>
143
+ <rel-switch></rel-switch>
144
+ </div>
145
+ <div style="display: flex; align-items: center; gap: 12px;">
146
+ <rel-switch checked></rel-switch>
147
+ <label style="font-size: 14px; font-weight: 500;">Auto-save (Right Label)</label>
148
+ </div>
149
+ </div>
150
+ `,
151
+ };
152
+
153
+ export const WithDescriptiveLabels: Story = {
154
+ render: () => html`
155
+ <div style="display: flex; flex-direction: column; gap: 24px; max-width: 400px;">
156
+ <div style="display: flex; align-items: flex-start; gap: 12px;">
157
+ <div style="flex: 1;">
158
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Email Notifications</div>
159
+ <div style="font-size: 12px; color: #666;">Receive email updates about your account activity</div>
160
+ </div>
161
+ <rel-switch checked></rel-switch>
162
+ </div>
163
+ <div style="display: flex; align-items: flex-start; gap: 12px;">
164
+ <div style="flex: 1;">
165
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Two-Factor Authentication</div>
166
+ <div style="font-size: 12px; color: #666;">Add an extra layer of security to your account</div>
167
+ </div>
168
+ <rel-switch></rel-switch>
169
+ </div>
170
+ <div style="display: flex; align-items: flex-start; gap: 12px;">
171
+ <div style="flex: 1;">
172
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Marketing Communications</div>
173
+ <div style="font-size: 12px; color: #666;">Receive promotional emails and product updates</div>
174
+ </div>
175
+ <rel-switch disabled></rel-switch>
176
+ </div>
177
+ </div>
178
+ `,
179
+ };
180
+
181
+ export const InFormContext: Story = {
182
+ render: () => {
183
+ const handleSubmit = (e: Event) => {
184
+ e.preventDefault();
185
+ const formData = new FormData(e.target as HTMLFormElement);
186
+ const notifications = (e.target as HTMLFormElement).querySelector('[name="notifications"]') as any;
187
+ const autoSave = (e.target as HTMLFormElement).querySelector('[name="autoSave"]') as any;
188
+ const darkMode = (e.target as HTMLFormElement).querySelector('[name="darkMode"]') as any;
189
+
190
+ console.log('Form submitted:', {
191
+ notifications: notifications?.checked || false,
192
+ autoSave: autoSave?.checked || false,
193
+ darkMode: darkMode?.checked || false,
194
+ });
195
+
196
+ alert(`Form Values:\nNotifications: ${notifications?.checked || false}\nAuto-save: ${autoSave?.checked || false}\nDark Mode: ${darkMode?.checked || false}`);
197
+ };
198
+
199
+ return html`
200
+ <form @submit=${handleSubmit} style="display: flex; flex-direction: column; gap: 20px; max-width: 400px;">
201
+ <h3 style="margin: 0 0 8px 0;">User Preferences</h3>
202
+
203
+ <div style="display: flex; align-items: center; justify-content: space-between;">
204
+ <label style="font-size: 14px; font-weight: 500;">Enable Notifications</label>
205
+ <rel-switch name="notifications" checked></rel-switch>
206
+ </div>
207
+
208
+ <div style="display: flex; align-items: center; justify-content: space-between;">
209
+ <label style="font-size: 14px; font-weight: 500;">Auto-save</label>
210
+ <rel-switch name="autoSave" checked></rel-switch>
211
+ </div>
212
+
213
+ <div style="display: flex; align-items: center; justify-content: space-between;">
214
+ <label style="font-size: 14px; font-weight: 500;">Dark Mode</label>
215
+ <rel-switch name="darkMode"></rel-switch>
216
+ </div>
217
+
218
+ <button type="submit" style="padding: 8px 16px; background: #000; color: #fff; border: none; border-radius: 4px; cursor: pointer; margin-top: 8px;">
219
+ Save Preferences
220
+ </button>
221
+ </form>
222
+ `;
223
+ },
224
+ };
225
+
226
+ export const ToggleGroup: Story = {
227
+ render: () => html`
228
+ <div style="display: flex; flex-direction: column; gap: 16px; max-width: 400px;">
229
+ <h4 style="margin: 0 0 8px 0; font-size: 16px;">Privacy Settings</h4>
230
+
231
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
232
+ <span style="font-size: 14px;">Profile Visibility</span>
233
+ <rel-switch checked></rel-switch>
234
+ </div>
235
+
236
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
237
+ <span style="font-size: 14px;">Show Online Status</span>
238
+ <rel-switch checked></rel-switch>
239
+ </div>
240
+
241
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
242
+ <span style="font-size: 14px;">Allow Messages from Anyone</span>
243
+ <rel-switch></rel-switch>
244
+ </div>
245
+
246
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
247
+ <span style="font-size: 14px;">Share Activity Status</span>
248
+ <rel-switch></rel-switch>
249
+ </div>
250
+ </div>
251
+ `,
252
+ };
253
+
254
+ export const NotificationSettings: Story = {
255
+ render: () => {
256
+ const handleChange = (e: CustomEvent, name: string) => {
257
+ console.log(`${name} changed to:`, e.detail);
258
+ };
259
+
260
+ return html`
261
+ <div style="display: flex; flex-direction: column; gap: 0; max-width: 500px; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden;">
262
+ <div style="padding: 16px; background: #f5f5f5; border-bottom: 1px solid #e0e0e0;">
263
+ <h3 style="margin: 0; font-size: 18px;">Notification Preferences</h3>
264
+ </div>
265
+
266
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 16px; border-bottom: 1px solid #e0e0e0;">
267
+ <div>
268
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Push Notifications</div>
269
+ <div style="font-size: 12px; color: #666;">Receive push notifications on your device</div>
270
+ </div>
271
+ <rel-switch checked @changed=${(e: CustomEvent) => handleChange(e, 'Push Notifications')}></rel-switch>
272
+ </div>
273
+
274
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 16px; border-bottom: 1px solid #e0e0e0;">
275
+ <div>
276
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Email Digest</div>
277
+ <div style="font-size: 12px; color: #666;">Daily summary of important updates</div>
278
+ </div>
279
+ <rel-switch checked @changed=${(e: CustomEvent) => handleChange(e, 'Email Digest')}></rel-switch>
280
+ </div>
281
+
282
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 16px; border-bottom: 1px solid #e0e0e0;">
283
+ <div>
284
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">SMS Alerts</div>
285
+ <div style="font-size: 12px; color: #666;">Critical alerts via text message</div>
286
+ </div>
287
+ <rel-switch @changed=${(e: CustomEvent) => handleChange(e, 'SMS Alerts')}></rel-switch>
288
+ </div>
289
+
290
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 16px;">
291
+ <div>
292
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Marketing Updates</div>
293
+ <div style="font-size: 12px; color: #666;">News about products and features</div>
294
+ </div>
295
+ <rel-switch @changed=${(e: CustomEvent) => handleChange(e, 'Marketing Updates')}></rel-switch>
296
+ </div>
297
+ </div>
298
+ `;
299
+ },
300
+ };
301
+
302
+ export const AccessibilitySettings: Story = {
303
+ render: () => html`
304
+ <div style="display: flex; flex-direction: column; gap: 0; max-width: 500px; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden;">
305
+ <div style="padding: 16px; background: #f5f5f5; border-bottom: 1px solid #e0e0e0;">
306
+ <h3 style="margin: 0; font-size: 18px;">Accessibility</h3>
307
+ <p style="margin: 8px 0 0 0; font-size: 13px; color: #666;">Customize your experience</p>
308
+ </div>
309
+
310
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 16px; border-bottom: 1px solid #e0e0e0;">
311
+ <div style="flex: 1;">
312
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">High Contrast Mode</div>
313
+ <div style="font-size: 12px; color: #666;">Increase contrast for better visibility</div>
314
+ </div>
315
+ <rel-switch></rel-switch>
316
+ </div>
317
+
318
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 16px; border-bottom: 1px solid #e0e0e0;">
319
+ <div style="flex: 1;">
320
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Reduce Motion</div>
321
+ <div style="font-size: 12px; color: #666;">Minimize animations and transitions</div>
322
+ </div>
323
+ <rel-switch></rel-switch>
324
+ </div>
325
+
326
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 16px; border-bottom: 1px solid #e0e0e0;">
327
+ <div style="flex: 1;">
328
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Screen Reader Support</div>
329
+ <div style="font-size: 12px; color: #666;">Enhanced support for screen readers</div>
330
+ </div>
331
+ <rel-switch checked></rel-switch>
332
+ </div>
333
+
334
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 16px;">
335
+ <div style="flex: 1;">
336
+ <div style="font-size: 14px; font-weight: 500; margin-bottom: 4px;">Keyboard Navigation</div>
337
+ <div style="font-size: 12px; color: #666;">Optimized keyboard shortcuts</div>
338
+ </div>
339
+ <rel-switch checked></rel-switch>
340
+ </div>
341
+ </div>
342
+ `,
343
+ };
344
+
345
+ export const AccountSettings: Story = {
346
+ render: () => html`
347
+ <div style="display: flex; flex-direction: column; gap: 24px; max-width: 600px;">
348
+ <section>
349
+ <h3 style="margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">Security</h3>
350
+ <div style="display: flex; flex-direction: column; gap: 16px;">
351
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
352
+ <div>
353
+ <div style="font-size: 14px; font-weight: 500;">Two-Factor Authentication</div>
354
+ <div style="font-size: 12px; color: #666; margin-top: 4px;">Require 2FA for sign in</div>
355
+ </div>
356
+ <rel-switch checked></rel-switch>
357
+ </div>
358
+
359
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
360
+ <div>
361
+ <div style="font-size: 14px; font-weight: 500;">Login Alerts</div>
362
+ <div style="font-size: 12px; color: #666; margin-top: 4px;">Get notified of new sign-ins</div>
363
+ </div>
364
+ <rel-switch checked></rel-switch>
365
+ </div>
366
+ </div>
367
+ </section>
368
+
369
+ <section>
370
+ <h3 style="margin: 0 0 16px 0; font-size: 16px; font-weight: 600;">Preferences</h3>
371
+ <div style="display: flex; flex-direction: column; gap: 16px;">
372
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
373
+ <div>
374
+ <div style="font-size: 14px; font-weight: 500;">Email Notifications</div>
375
+ <div style="font-size: 12px; color: #666; margin-top: 4px;">Receive account updates via email</div>
376
+ </div>
377
+ <rel-switch checked></rel-switch>
378
+ </div>
379
+
380
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
381
+ <div>
382
+ <div style="font-size: 14px; font-weight: 500;">Newsletter Subscription</div>
383
+ <div style="font-size: 12px; color: #666; margin-top: 4px;">Weekly updates and tips</div>
384
+ </div>
385
+ <rel-switch></rel-switch>
386
+ </div>
387
+
388
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9f9; border-radius: 6px;">
389
+ <div>
390
+ <div style="font-size: 14px; font-weight: 500;">Beta Features</div>
391
+ <div style="font-size: 12px; color: #666; margin-top: 4px;">Try experimental features early</div>
392
+ </div>
393
+ <rel-switch></rel-switch>
394
+ </div>
395
+ </div>
396
+ </section>
397
+ </div>
398
+ `,
399
+ };
400
+
401
+ export const DashboardSettings: Story = {
402
+ render: () => html`
403
+ <div style="display: flex; gap: 24px; flex-wrap: wrap;">
404
+ <div style="flex: 1; min-width: 280px; padding: 20px; border: 1px solid #e0e0e0; border-radius: 8px;">
405
+ <h4 style="margin: 0 0 16px 0; font-size: 14px; font-weight: 600; text-transform: uppercase; color: #666;">Display</h4>
406
+ <div style="display: flex; flex-direction: column; gap: 16px;">
407
+ <div style="display: flex; align-items: center; justify-content: space-between;">
408
+ <span style="font-size: 14px;">Dark Mode</span>
409
+ <rel-switch></rel-switch>
410
+ </div>
411
+ <div style="display: flex; align-items: center; justify-content: space-between;">
412
+ <span style="font-size: 14px;">Compact View</span>
413
+ <rel-switch checked></rel-switch>
414
+ </div>
415
+ <div style="display: flex; align-items: center; justify-content: space-between;">
416
+ <span style="font-size: 14px;">Show Sidebar</span>
417
+ <rel-switch checked></rel-switch>
418
+ </div>
419
+ </div>
420
+ </div>
421
+
422
+ <div style="flex: 1; min-width: 280px; padding: 20px; border: 1px solid #e0e0e0; border-radius: 8px;">
423
+ <h4 style="margin: 0 0 16px 0; font-size: 14px; font-weight: 600; text-transform: uppercase; color: #666;">Behavior</h4>
424
+ <div style="display: flex; flex-direction: column; gap: 16px;">
425
+ <div style="display: flex; align-items: center; justify-content: space-between;">
426
+ <span style="font-size: 14px;">Auto-save</span>
427
+ <rel-switch checked></rel-switch>
428
+ </div>
429
+ <div style="display: flex; align-items: center; justify-content: space-between;">
430
+ <span style="font-size: 14px;">Sound Effects</span>
431
+ <rel-switch></rel-switch>
432
+ </div>
433
+ <div style="display: flex; align-items: center; justify-content: space-between;">
434
+ <span style="font-size: 14px;">Animations</span>
435
+ <rel-switch checked></rel-switch>
436
+ </div>
437
+ </div>
438
+ </div>
439
+ </div>
440
+ `,
441
+ };
@@ -0,0 +1,161 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+
4
+ @customElement("rel-switch")
5
+ export class RelSwitch extends LitElement {
6
+ @property({ type: Boolean }) checked = false;
7
+ @property({ type: Boolean }) disabled = false;
8
+
9
+ @state() private isFocused = false;
10
+ @state() private isPressed = false;
11
+
12
+ static styles = css`
13
+ :host {
14
+ --switch-track: #7e8186;
15
+ --switch-knob-on: #000000;
16
+ --switch-knob-off: #ffffff;
17
+ --switch-halo: rgba(224, 224, 224, 0.8);
18
+ --switch-disabled: #d1d5db;
19
+ display: inline-block;
20
+ }
21
+
22
+ /* Wrapper */
23
+ .switch-wrapper {
24
+ position: relative;
25
+ display: inline-block;
26
+ width: 42px;
27
+ height: 22px;
28
+ cursor: pointer;
29
+ }
30
+
31
+ .switch-wrapper.switch-disabled {
32
+ cursor: not-allowed;
33
+ opacity: 0.5;
34
+ }
35
+
36
+ /* Hide native checkbox */
37
+ .switch-wrapper input {
38
+ opacity: 0;
39
+ width: 0;
40
+ height: 0;
41
+ }
42
+
43
+ /* Track */
44
+ .switch-slider {
45
+ position: absolute;
46
+ top: 50%;
47
+ left: 4px;
48
+ right: 4px;
49
+ height: 12px;
50
+ background-color: var(--switch-track);
51
+ border-radius: 999px;
52
+ transform: translateY(-50%);
53
+ transition: background-color 0.25s ease;
54
+ }
55
+
56
+ /* Knob */
57
+ .switch-slider::before {
58
+ content: "";
59
+ position: absolute;
60
+ top: 50%;
61
+ left: -2px;
62
+ transform: translateY(-50%);
63
+ height: 18px;
64
+ width: 18px;
65
+ background-color: var(--switch-knob-off);
66
+ border-radius: 50%;
67
+ transition: transform 0.25s ease, background-color 0.25s ease,
68
+ box-shadow 0.2s ease;
69
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
70
+ }
71
+
72
+ /* ON state */
73
+ input:checked + .switch-slider::before {
74
+ background-color: var(--switch-knob-on);
75
+ transform: translateY(-50%) translateX(22px);
76
+ }
77
+
78
+ /* Hover */
79
+ .switch-wrapper:hover:not(.switch-disabled) .switch-slider {
80
+ background-color: #666;
81
+ }
82
+
83
+ /* Focus halo */
84
+ .switch-focused .switch-slider::before {
85
+ box-shadow: 0 0 0 6px var(--switch-halo);
86
+ }
87
+
88
+ /* Pressed */
89
+ .switch-pressed .switch-slider::before {
90
+ transform: scale(0.9) translateY(-50%);
91
+ }
92
+ .switch-pressed input:checked + .switch-slider::before {
93
+ transform: scale(0.9) translateY(-50%) translateX(22px);
94
+ }
95
+
96
+ /* Disabled */
97
+ .switch-disabled .switch-slider {
98
+ background-color: var(--switch-disabled);
99
+ }
100
+ .switch-disabled .switch-slider::before {
101
+ background-color: var(--switch-disabled);
102
+ }
103
+ `;
104
+
105
+ private toggle() {
106
+ if (this.disabled) return;
107
+ this.checked = !this.checked;
108
+ this.requestUpdate();
109
+ this.dispatchEvent(
110
+ new CustomEvent("changed", {
111
+ detail: this.checked,
112
+ bubbles: true,
113
+ composed: true,
114
+ })
115
+ );
116
+ }
117
+
118
+ private handleFocus() {
119
+ this.isFocused = true;
120
+ }
121
+
122
+ private handleBlur() {
123
+ this.isFocused = false;
124
+ this.isPressed = false;
125
+ }
126
+
127
+ private handleMouseDown() {
128
+ if (!this.disabled) this.isPressed = true;
129
+ }
130
+
131
+ private handleMouseUp() {
132
+ if (!this.disabled) this.isPressed = false;
133
+ }
134
+
135
+ render() {
136
+ const wrapperClasses = [
137
+ "switch-wrapper",
138
+ this.disabled && "switch-disabled",
139
+ this.isFocused && "switch-focused",
140
+ this.isPressed && "switch-pressed",
141
+ ]
142
+ .filter(Boolean)
143
+ .join(" ");
144
+
145
+ return html`
146
+ <label class="${wrapperClasses}">
147
+ <input
148
+ type="checkbox"
149
+ ?disabled=${this.disabled}
150
+ .checked=${this.checked}
151
+ @change=${this.toggle}
152
+ @focus=${this.handleFocus}
153
+ @blur=${this.handleBlur}
154
+ @mousedown=${this.handleMouseDown}
155
+ @mouseup=${this.handleMouseUp}
156
+ />
157
+ <span class="switch-slider"></span>
158
+ </label>
159
+ `;
160
+ }
161
+ }