snice 1.14.3 → 2.1.0

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 (185) hide show
  1. package/bin/templates/base/tsconfig.json +5 -4
  2. package/components/accordion/demo.html +403 -0
  3. package/components/accordion/snice-accordion-item.css +85 -0
  4. package/components/accordion/snice-accordion-item.ts +226 -0
  5. package/components/accordion/snice-accordion.css +31 -0
  6. package/components/accordion/snice-accordion.ts +182 -0
  7. package/components/accordion/snice-accordion.types.ts +32 -0
  8. package/components/alert/demo.html +445 -0
  9. package/components/alert/snice-alert.css +195 -0
  10. package/components/alert/snice-alert.ts +141 -0
  11. package/components/alert/snice-alert.types.ts +12 -0
  12. package/components/avatar/demo.html +598 -0
  13. package/components/avatar/snice-avatar.css +131 -0
  14. package/components/avatar/snice-avatar.ts +136 -0
  15. package/components/avatar/snice-avatar.types.ts +13 -0
  16. package/components/badge/demo.html +523 -0
  17. package/components/badge/snice-badge.css +161 -0
  18. package/components/badge/snice-badge.ts +117 -0
  19. package/components/badge/snice-badge.types.ts +16 -0
  20. package/components/breadcrumbs/demo.html +404 -0
  21. package/components/breadcrumbs/snice-breadcrumbs.css +133 -0
  22. package/components/breadcrumbs/snice-breadcrumbs.ts +191 -0
  23. package/components/breadcrumbs/snice-breadcrumbs.types.ts +26 -0
  24. package/components/breadcrumbs/snice-crumb.ts +26 -0
  25. package/components/button/demo.html +42 -0
  26. package/components/button/snice-button.css +230 -0
  27. package/components/button/snice-button.ts +169 -0
  28. package/components/button/snice-button.types.ts +25 -0
  29. package/components/card/demo.html +525 -0
  30. package/components/card/snice-card.css +140 -0
  31. package/components/card/snice-card.ts +102 -0
  32. package/components/card/snice-card.types.ts +10 -0
  33. package/components/checkbox/demo.html +253 -0
  34. package/components/checkbox/snice-checkbox.css +164 -0
  35. package/components/checkbox/snice-checkbox.ts +223 -0
  36. package/components/checkbox/snice-checkbox.types.ts +22 -0
  37. package/components/chip/demo.html +383 -0
  38. package/components/chip/snice-chip.css +195 -0
  39. package/components/chip/snice-chip.ts +139 -0
  40. package/components/chip/snice-chip.types.ts +15 -0
  41. package/components/date-picker/README.md +233 -0
  42. package/components/date-picker/demo.html +191 -0
  43. package/components/date-picker/snice-date-picker.css +330 -0
  44. package/components/date-picker/snice-date-picker.ts +777 -0
  45. package/components/date-picker/snice-date-picker.types.ts +83 -0
  46. package/components/divider/demo.html +233 -0
  47. package/components/divider/snice-divider.css +155 -0
  48. package/components/divider/snice-divider.ts +69 -0
  49. package/components/divider/snice-divider.types.ts +15 -0
  50. package/components/drawer/demo.html +328 -0
  51. package/components/drawer/snice-drawer.css +476 -0
  52. package/components/drawer/snice-drawer.ts +287 -0
  53. package/components/drawer/snice-drawer.types.ts +17 -0
  54. package/components/global.d.ts +14 -0
  55. package/components/input/demo.html +303 -0
  56. package/components/input/snice-input.css +257 -0
  57. package/components/input/snice-input.ts +442 -0
  58. package/components/input/snice-input.types.ts +59 -0
  59. package/components/input/test.html +77 -0
  60. package/components/layout/README.md +260 -0
  61. package/components/layout/demo.html +538 -0
  62. package/components/layout/snice-layout-blog.css +129 -0
  63. package/components/layout/snice-layout-blog.ts +48 -0
  64. package/components/layout/snice-layout-card.css +104 -0
  65. package/components/layout/snice-layout-card.ts +35 -0
  66. package/components/layout/snice-layout-centered.css +51 -0
  67. package/components/layout/snice-layout-centered.ts +22 -0
  68. package/components/layout/snice-layout-dashboard.css +98 -0
  69. package/components/layout/snice-layout-dashboard.ts +45 -0
  70. package/components/layout/snice-layout-fullscreen.css +72 -0
  71. package/components/layout/snice-layout-fullscreen.ts +34 -0
  72. package/components/layout/snice-layout-landing.css +92 -0
  73. package/components/layout/snice-layout-landing.ts +47 -0
  74. package/components/layout/snice-layout-minimal.css +16 -0
  75. package/components/layout/snice-layout-minimal.ts +19 -0
  76. package/components/layout/snice-layout-sidebar.css +117 -0
  77. package/components/layout/snice-layout-sidebar.ts +48 -0
  78. package/components/layout/snice-layout-split.css +103 -0
  79. package/components/layout/snice-layout-split.ts +29 -0
  80. package/components/layout/snice-layout.css +72 -0
  81. package/components/layout/snice-layout.ts +35 -0
  82. package/components/layout/snice-layout.types.ts +5 -0
  83. package/components/login/demo-auth-controller.ts +185 -0
  84. package/components/login/demo.html +470 -0
  85. package/components/login/snice-login.css +204 -0
  86. package/components/login/snice-login.ts +337 -0
  87. package/components/login/snice-login.types.ts +34 -0
  88. package/components/modal/demo.html +291 -0
  89. package/components/modal/snice-modal.css +203 -0
  90. package/components/modal/snice-modal.ts +233 -0
  91. package/components/modal/snice-modal.types.ts +21 -0
  92. package/components/pagination/demo.html +395 -0
  93. package/components/pagination/snice-pagination.ts +333 -0
  94. package/components/pagination/snice-pagination.types.ts +21 -0
  95. package/components/progress/demo.html +510 -0
  96. package/components/progress/snice-progress.css +267 -0
  97. package/components/progress/snice-progress.ts +247 -0
  98. package/components/progress/snice-progress.types.ts +19 -0
  99. package/components/radio/demo.html +287 -0
  100. package/components/radio/snice-radio.css +171 -0
  101. package/components/radio/snice-radio.ts +218 -0
  102. package/components/radio/snice-radio.types.ts +21 -0
  103. package/components/select/demo.html +511 -0
  104. package/components/select/snice-option.ts +52 -0
  105. package/components/select/snice-option.types.ts +14 -0
  106. package/components/select/snice-select.css +392 -0
  107. package/components/select/snice-select.ts +796 -0
  108. package/components/select/snice-select.types.ts +55 -0
  109. package/components/skeleton/demo.html +514 -0
  110. package/components/skeleton/snice-skeleton.css +109 -0
  111. package/components/skeleton/snice-skeleton.ts +126 -0
  112. package/components/skeleton/snice-skeleton.types.ts +11 -0
  113. package/components/switch/demo.html +284 -0
  114. package/components/switch/snice-switch.css +221 -0
  115. package/components/switch/snice-switch.ts +229 -0
  116. package/components/switch/snice-switch.types.ts +23 -0
  117. package/components/symbols.ts +23 -0
  118. package/components/table/demo-table-controller.ts +100 -0
  119. package/components/table/demo.html +480 -0
  120. package/components/table/snice-cell-boolean.ts +112 -0
  121. package/components/table/snice-cell-date.ts +210 -0
  122. package/components/table/snice-cell-duration.ts +91 -0
  123. package/components/table/snice-cell-filesize.ts +90 -0
  124. package/components/table/snice-cell-number.ts +165 -0
  125. package/components/table/snice-cell-progress.ts +83 -0
  126. package/components/table/snice-cell-rating.ts +82 -0
  127. package/components/table/snice-cell-sparkline.ts +253 -0
  128. package/components/table/snice-cell-text.ts +125 -0
  129. package/components/table/snice-cell.css +296 -0
  130. package/components/table/snice-cell.ts +473 -0
  131. package/components/table/snice-column.ts +353 -0
  132. package/components/table/snice-header.css +243 -0
  133. package/components/table/snice-header.ts +261 -0
  134. package/components/table/snice-progress.ts +66 -0
  135. package/components/table/snice-rating.ts +45 -0
  136. package/components/table/snice-row.css +255 -0
  137. package/components/table/snice-row.ts +331 -0
  138. package/components/table/snice-table.css +241 -0
  139. package/components/table/snice-table.ts +737 -0
  140. package/components/table/snice-table.types.ts +158 -0
  141. package/components/tabs/demo.html +487 -0
  142. package/components/tabs/snice-tab-panel.css +264 -0
  143. package/components/tabs/snice-tab-panel.ts +47 -0
  144. package/components/tabs/snice-tab.css +96 -0
  145. package/components/tabs/snice-tab.ts +65 -0
  146. package/components/tabs/snice-tabs.css +189 -0
  147. package/components/tabs/snice-tabs.ts +332 -0
  148. package/components/tabs/snice-tabs.types.ts +28 -0
  149. package/components/theme/theme.css +234 -0
  150. package/components/toast/demo.html +329 -0
  151. package/components/toast/snice-toast-container.ts +256 -0
  152. package/components/toast/snice-toast.css +213 -0
  153. package/components/toast/snice-toast.ts +276 -0
  154. package/components/toast/snice-toast.types.ts +35 -0
  155. package/components/tooltip/demo.html +350 -0
  156. package/components/tooltip/snice-tooltip-portal.css +79 -0
  157. package/components/tooltip/snice-tooltip.css +117 -0
  158. package/components/tooltip/snice-tooltip.ts +612 -0
  159. package/components/tooltip/snice-tooltip.types.ts +32 -0
  160. package/components/transitions.ts +94 -0
  161. package/components/tsconfig.json +18 -0
  162. package/dist/index.cjs +441 -329
  163. package/dist/index.cjs.map +1 -1
  164. package/dist/index.cjs.min.map +1 -1
  165. package/dist/index.esm.js +441 -329
  166. package/dist/index.esm.js.map +1 -1
  167. package/dist/index.esm.min.js +3 -3
  168. package/dist/index.esm.min.js.map +1 -1
  169. package/dist/index.iife.js +441 -329
  170. package/dist/index.iife.js.map +1 -1
  171. package/dist/index.iife.min.js +3 -3
  172. package/dist/index.iife.min.js.map +1 -1
  173. package/dist/symbols.esm.js +1 -1
  174. package/dist/transitions.esm.js +1 -1
  175. package/dist/types/controller.d.ts +1 -1
  176. package/dist/types/element.d.ts +10 -10
  177. package/dist/types/events.d.ts +2 -2
  178. package/dist/types/index.d.ts +1 -1
  179. package/dist/types/observe.d.ts +1 -1
  180. package/dist/types/request-response.d.ts +2 -3
  181. package/dist/types/router.d.ts +1 -1
  182. package/package.json +9 -3
  183. package/dist/index.cjs.min +0 -15
  184. package/dist/symbols.cjs +0 -103
  185. package/dist/transitions.cjs +0 -219
@@ -0,0 +1,126 @@
1
+ import { element, property, watch, ready } from 'snice';
2
+ import css from './snice-skeleton.css?inline';
3
+ import type { SkeletonVariant, SkeletonAnimation, SniceSkeletonElement } from './snice-skeleton.types';
4
+
5
+ @element('snice-skeleton')
6
+ export class SniceSkeleton extends HTMLElement implements SniceSkeletonElement {
7
+ @property({ reflect: true })
8
+ variant: SkeletonVariant = 'text';
9
+
10
+ @property({ reflect: true })
11
+ width = '';
12
+
13
+ @property({ reflect: true })
14
+ height = '';
15
+
16
+ @property({ reflect: true })
17
+ animation: SkeletonAnimation = 'wave';
18
+
19
+ @property({ type: Number, reflect: true })
20
+ count = 1;
21
+
22
+ @property({ reflect: true })
23
+ spacing = '8px';
24
+
25
+ html() {
26
+ const items = Array(this.count).fill(0);
27
+
28
+ return /*html*/`
29
+ <div class="skeleton-container" style="gap: ${this.spacing}">
30
+ ${items.map(() => /*html*/`
31
+ <div class="skeleton skeleton--${this.variant} skeleton--${this.animation}"
32
+ style="${this.getInlineStyles()}"
33
+ role="status"
34
+ aria-label="Loading...">
35
+ <span class="skeleton-screen-reader">Loading...</span>
36
+ </div>
37
+ `).join('')}
38
+ </div>
39
+ `;
40
+ }
41
+
42
+ css() {
43
+ return css;
44
+ }
45
+
46
+ @ready()
47
+ init() {
48
+ this.updateDimensions();
49
+ }
50
+
51
+ @watch('width', 'height')
52
+ updateDimensions() {
53
+ const container = this.shadowRoot?.querySelector('.skeleton-container');
54
+ if (!container) return;
55
+
56
+ const skeletons = container.querySelectorAll('.skeleton');
57
+ skeletons.forEach(skeleton => {
58
+ (skeleton as HTMLElement).style.cssText = this.getInlineStyles();
59
+ });
60
+ }
61
+
62
+ @watch('variant')
63
+ updateVariant() {
64
+ // Re-render when variant changes to apply proper default dimensions
65
+ this.render();
66
+ }
67
+
68
+ private getInlineStyles(): string {
69
+ const styles: string[] = [];
70
+
71
+ // Apply width
72
+ if (this.width) {
73
+ styles.push(`width: ${this.width}`);
74
+ } else {
75
+ // Default widths based on variant
76
+ switch (this.variant) {
77
+ case 'circular':
78
+ styles.push('width: 40px');
79
+ break;
80
+ case 'rectangular':
81
+ case 'rounded':
82
+ styles.push('width: 100%');
83
+ break;
84
+ case 'text':
85
+ styles.push('width: 100%');
86
+ break;
87
+ }
88
+ }
89
+
90
+ // Apply height
91
+ if (this.height) {
92
+ styles.push(`height: ${this.height}`);
93
+ } else {
94
+ // Default heights based on variant
95
+ switch (this.variant) {
96
+ case 'circular':
97
+ styles.push('height: 40px');
98
+ break;
99
+ case 'rectangular':
100
+ case 'rounded':
101
+ styles.push('height: 120px');
102
+ break;
103
+ case 'text':
104
+ styles.push('height: 20px');
105
+ break;
106
+ }
107
+ }
108
+
109
+ return styles.join('; ');
110
+ }
111
+
112
+ private render() {
113
+ const shadow = this.shadowRoot;
114
+ if (shadow) {
115
+ shadow.innerHTML = '';
116
+ if (this.css) {
117
+ const style = document.createElement('style');
118
+ style.textContent = this.css();
119
+ shadow.appendChild(style);
120
+ }
121
+ const template = document.createElement('template');
122
+ template.innerHTML = this.html();
123
+ shadow.appendChild(template.content.cloneNode(true));
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,11 @@
1
+ export type SkeletonVariant = 'text' | 'circular' | 'rectangular' | 'rounded';
2
+ export type SkeletonAnimation = 'pulse' | 'wave' | 'none';
3
+
4
+ export interface SniceSkeletonElement extends HTMLElement {
5
+ variant: SkeletonVariant;
6
+ width: string;
7
+ height: string;
8
+ animation: SkeletonAnimation;
9
+ count: number;
10
+ spacing: string;
11
+ }
@@ -0,0 +1,284 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Toggle/Switch Demo</title>
7
+ <link rel="stylesheet" href="../theme/theme.css">
8
+ <style>
9
+ body {
10
+ padding: 40px;
11
+ font-family: var(--snice-font-family);
12
+ background: var(--snice-color-background);
13
+ color: var(--snice-color-text);
14
+ max-width: 800px;
15
+ margin: 0 auto;
16
+ }
17
+
18
+ .section {
19
+ margin-bottom: 40px;
20
+ }
21
+
22
+ h2 {
23
+ color: var(--snice-color-text);
24
+ margin-bottom: 20px;
25
+ font-size: var(--snice-font-size-2xl);
26
+ }
27
+
28
+ h3 {
29
+ color: var(--snice-color-text-secondary);
30
+ margin-bottom: 16px;
31
+ font-size: var(--snice-font-size-lg);
32
+ }
33
+
34
+ .switch-group {
35
+ display: flex;
36
+ flex-direction: column;
37
+ gap: 16px;
38
+ margin-bottom: 20px;
39
+ }
40
+
41
+ .row {
42
+ display: flex;
43
+ gap: 20px;
44
+ align-items: center;
45
+ flex-wrap: wrap;
46
+ }
47
+
48
+ .output {
49
+ padding: 10px;
50
+ background: var(--snice-color-background-secondary);
51
+ border-radius: var(--snice-border-radius-md);
52
+ font-family: monospace;
53
+ font-size: var(--snice-font-size-sm);
54
+ margin-top: 10px;
55
+ }
56
+
57
+ button {
58
+ padding: 8px 16px;
59
+ background: var(--snice-color-primary);
60
+ color: white;
61
+ border: none;
62
+ border-radius: var(--snice-border-radius-md);
63
+ cursor: pointer;
64
+ font-size: var(--snice-font-size-md);
65
+ margin-right: 8px;
66
+ }
67
+
68
+ button:hover {
69
+ background: var(--snice-color-primary-hover);
70
+ }
71
+
72
+ .settings-card {
73
+ background: var(--snice-color-background-secondary);
74
+ border: 1px solid var(--snice-color-border);
75
+ border-radius: var(--snice-border-radius-lg);
76
+ padding: 20px;
77
+ margin-bottom: 20px;
78
+ }
79
+
80
+ .settings-card h4 {
81
+ margin-top: 0;
82
+ margin-bottom: 16px;
83
+ font-size: var(--snice-font-size-lg);
84
+ }
85
+
86
+ .setting-row {
87
+ display: flex;
88
+ justify-content: space-between;
89
+ align-items: center;
90
+ padding: 12px 0;
91
+ border-bottom: 1px solid var(--snice-color-border);
92
+ }
93
+
94
+ .setting-row:last-child {
95
+ border-bottom: none;
96
+ }
97
+
98
+ .setting-info {
99
+ flex: 1;
100
+ margin-right: 16px;
101
+ }
102
+
103
+ .setting-title {
104
+ font-weight: 500;
105
+ margin-bottom: 4px;
106
+ }
107
+
108
+ .setting-description {
109
+ font-size: var(--snice-font-size-sm);
110
+ color: var(--snice-color-text-secondary);
111
+ }
112
+ </style>
113
+ </head>
114
+ <body>
115
+ <h2>Toggle/Switch Component Demo</h2>
116
+
117
+ <div class="section">
118
+ <h3>Basic Toggles</h3>
119
+ <div class="switch-group">
120
+ <snice-switch label="Default switch"></snice-switch>
121
+ <snice-switch label="Pre-checked" checked></snice-switch>
122
+ <snice-switch label="Without label"></snice-switch>
123
+ <snice-switch label="Disabled" disabled></snice-switch>
124
+ <snice-switch label="Disabled checked" disabled checked></snice-switch>
125
+ </div>
126
+ </div>
127
+
128
+ <div class="section">
129
+ <h3>Sizes</h3>
130
+ <div class="switch-group">
131
+ <snice-switch size="small" label="Small switch"></snice-switch>
132
+ <snice-switch size="medium" label="Medium switch (default)"></snice-switch>
133
+ <snice-switch size="large" label="Large switch"></snice-switch>
134
+ </div>
135
+ </div>
136
+
137
+ <div class="section">
138
+ <h3>With State Labels</h3>
139
+ <div class="switch-group">
140
+ <snice-switch size="medium" label="Medium with labels" label-on="ON" label-off="OFF"></snice-switch>
141
+ <snice-switch size="large" label="Large with labels" label-on="YES" label-off="NO" checked></snice-switch>
142
+ <snice-switch size="large" label="Enabled/Disabled" label-on="✓" label-off="✗"></snice-switch>
143
+ </div>
144
+ </div>
145
+
146
+ <div class="section">
147
+ <h3>States</h3>
148
+ <div class="switch-group">
149
+ <snice-switch label="Required field" required></snice-switch>
150
+ <snice-switch label="Invalid state" invalid></snice-switch>
151
+ <snice-switch label="Invalid and checked" invalid checked></snice-switch>
152
+ </div>
153
+ </div>
154
+
155
+ <div class="section">
156
+ <h3>Settings Panel Example</h3>
157
+ <div class="settings-card">
158
+ <h4>Notification Settings</h4>
159
+
160
+ <div class="setting-row">
161
+ <div class="setting-info">
162
+ <div class="setting-title">Email Notifications</div>
163
+ <div class="setting-description">Receive email updates about your account</div>
164
+ </div>
165
+ <snice-switch id="email-notifications" checked></snice-switch>
166
+ </div>
167
+
168
+ <div class="setting-row">
169
+ <div class="setting-info">
170
+ <div class="setting-title">Push Notifications</div>
171
+ <div class="setting-description">Get push notifications on your device</div>
172
+ </div>
173
+ <snice-switch id="push-notifications"></snice-switch>
174
+ </div>
175
+
176
+ <div class="setting-row">
177
+ <div class="setting-info">
178
+ <div class="setting-title">Marketing Emails</div>
179
+ <div class="setting-description">Receive promotional content and updates</div>
180
+ </div>
181
+ <snice-switch id="marketing-emails"></snice-switch>
182
+ </div>
183
+
184
+ <div class="setting-row">
185
+ <div class="setting-info">
186
+ <div class="setting-title">Dark Mode</div>
187
+ <div class="setting-description">Use dark theme across the application</div>
188
+ </div>
189
+ <snice-switch id="dark-mode" size="large" label-on="🌙" label-off="☀️"></snice-switch>
190
+ </div>
191
+ </div>
192
+ <button onclick="getSettings()">Get Current Settings</button>
193
+ <div id="settings-output" class="output">Click button to see current settings</div>
194
+ </div>
195
+
196
+ <div class="section">
197
+ <h3>Feature Flags</h3>
198
+ <div class="switch-group">
199
+ <snice-switch id="feature-beta" label="Enable Beta Features" label-on="BETA" label-off=""></snice-switch>
200
+ <snice-switch id="feature-experimental" label="Experimental Mode" label-on="EXP" label-off=""></snice-switch>
201
+ <snice-switch id="feature-debug" label="Debug Mode" label-on="DBG" label-off=""></snice-switch>
202
+ </div>
203
+ </div>
204
+
205
+ <div class="section">
206
+ <h3>Interactive Example</h3>
207
+ <div class="switch-group">
208
+ <snice-switch
209
+ id="interactive-switch"
210
+ label="Interactive Toggle"
211
+ size="large"
212
+ label-on="ON"
213
+ label-off="OFF">
214
+ </snice-switch>
215
+ </div>
216
+ <div class="row">
217
+ <button onclick="document.getElementById('interactive-switch').toggle()">Toggle</button>
218
+ <button onclick="document.getElementById('interactive-switch').focus()">Focus</button>
219
+ <button onclick="document.getElementById('interactive-switch').checked = true">Turn On</button>
220
+ <button onclick="document.getElementById('interactive-switch').checked = false">Turn Off</button>
221
+ </div>
222
+ <div id="event-output" class="output">Events will appear here...</div>
223
+ </div>
224
+
225
+ <div class="section">
226
+ <h3>Form Example</h3>
227
+ <form id="demo-form">
228
+ <div class="switch-group">
229
+ <snice-switch name="terms" label="I accept the terms and conditions" required></snice-switch>
230
+ <snice-switch name="newsletter" label="Subscribe to newsletter"></snice-switch>
231
+ <snice-switch name="privacy" label="I accept the privacy policy" required></snice-switch>
232
+ </div>
233
+ <button type="submit">Submit</button>
234
+ </form>
235
+ <div id="form-output" class="output">Form data will appear here...</div>
236
+ </div>
237
+
238
+ <script type="module">
239
+ import './snice-switch.ts';
240
+
241
+ // Settings panel
242
+ window.getSettings = function() {
243
+ const settings = {
244
+ emailNotifications: document.getElementById('email-notifications').checked,
245
+ pushNotifications: document.getElementById('push-notifications').checked,
246
+ marketingEmails: document.getElementById('marketing-emails').checked,
247
+ darkMode: document.getElementById('dark-mode').checked
248
+ };
249
+ document.getElementById('settings-output').textContent =
250
+ `Settings: ${JSON.stringify(settings, null, 2)}`;
251
+ };
252
+
253
+ // Interactive switch events
254
+ const interactiveToggle = document.getElementById('interactive-switch');
255
+ const eventOutput = document.getElementById('event-output');
256
+
257
+ interactiveToggle.addEventListener('@snice/switch-change', (e) => {
258
+ eventOutput.textContent = `Changed: checked=${e.detail.checked}`;
259
+ });
260
+
261
+ // Feature flags
262
+ const featureToggles = document.querySelectorAll('[id^="feature-"]');
263
+ featureToggles.forEach(_switch => {
264
+ _switch.addEventListener('@snice/switch-change', (e) => {
265
+ console.log(`Feature ${_switch.id}: ${e.detail.checked ? 'enabled' : 'disabled'}`);
266
+ });
267
+ });
268
+
269
+ // Form submission
270
+ document.getElementById('demo-form').addEventListener('submit', (e) => {
271
+ e.preventDefault();
272
+ const data = {};
273
+ const switches = e.target.querySelectorAll('snice-switch');
274
+ switches.forEach(_switch => {
275
+ if (_switch.name) {
276
+ data[_switch.name] = _switch.checked;
277
+ }
278
+ });
279
+ document.getElementById('form-output').textContent =
280
+ `Form data: ${JSON.stringify(data, null, 2)}`;
281
+ });
282
+ </script>
283
+ </body>
284
+ </html>
@@ -0,0 +1,221 @@
1
+ :host {
2
+ display: inline-block;
3
+ font-family: var(--snice-font-family);
4
+ }
5
+
6
+ .switch-wrapper {
7
+ display: inline-flex;
8
+ align-items: center;
9
+ gap: 0.5rem; /* 8px */
10
+ cursor: pointer;
11
+ user-select: none;
12
+ }
13
+
14
+ .switch-wrapper--disabled {
15
+ cursor: not-allowed;
16
+ opacity: 0.6;
17
+ }
18
+
19
+ /* Hide native checkbox */
20
+ .switch-input {
21
+ position: absolute;
22
+ opacity: 0;
23
+ width: 0;
24
+ height: 0;
25
+ }
26
+
27
+ /* Toggle track */
28
+ .switch-track {
29
+ position: relative;
30
+ display: inline-block;
31
+ background: var(--snice-color-border);
32
+ border-radius: 999px;
33
+ transition: background-color var(--snice-transition-fast) ease;
34
+ }
35
+
36
+ /* Size variants */
37
+ .switch-track--small {
38
+ width: 2.25rem; /* 36px */
39
+ height: 1.25rem; /* 20px */
40
+ }
41
+
42
+ .switch-track--medium {
43
+ width: 2.75rem; /* 44px */
44
+ height: 1.5rem; /* 24px */
45
+ }
46
+
47
+ .switch-track--large {
48
+ width: 3.25rem; /* 52px */
49
+ height: 1.75rem; /* 28px */
50
+ }
51
+
52
+ /* Toggle thumb */
53
+ .switch-thumb {
54
+ position: absolute;
55
+ top: 2px;
56
+ left: 2px;
57
+ background: white;
58
+ border-radius: 50%;
59
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
60
+ transition: transform var(--snice-transition-fast) ease;
61
+ }
62
+
63
+ /* Thumb sizes */
64
+ .switch-track--small .switch-thumb {
65
+ width: 1rem; /* 16px */
66
+ height: 1rem; /* 16px */
67
+ }
68
+
69
+ .switch-track--medium .switch-thumb {
70
+ width: 1.25rem; /* 20px */
71
+ height: 1.25rem; /* 20px */
72
+ }
73
+
74
+ .switch-track--large .switch-thumb {
75
+ width: 1.5rem; /* 24px */
76
+ height: 1.5rem; /* 24px */
77
+ }
78
+
79
+ /* Checked state */
80
+ .switch-input:checked ~ .switch-track {
81
+ background: var(--snice-color-primary);
82
+ }
83
+
84
+ .switch-input:checked ~ .switch-track--small .switch-thumb {
85
+ transform: translateX(1rem); /* 16px */
86
+ }
87
+
88
+ .switch-input:checked ~ .switch-track--medium .switch-thumb {
89
+ transform: translateX(1.25rem); /* 20px */
90
+ }
91
+
92
+ .switch-input:checked ~ .switch-track--large .switch-thumb {
93
+ transform: translateX(1.5rem); /* 24px */
94
+ }
95
+
96
+ /* Focus state */
97
+ .switch-input:focus-visible ~ .switch-track {
98
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
99
+ }
100
+
101
+ .switch-input:focus-visible ~ .switch-track {
102
+ outline: 2px solid var(--snice-color-primary);
103
+ outline-offset: 2px;
104
+ }
105
+
106
+ /* Disabled state */
107
+ .switch-input:disabled ~ .switch-track {
108
+ background: var(--snice-color-background-secondary);
109
+ }
110
+
111
+ .switch-input:disabled ~ .switch-track .switch-thumb {
112
+ background: var(--snice-color-background);
113
+ }
114
+
115
+ .switch-input:disabled:checked ~ .switch-track {
116
+ background: var(--snice-color-text-secondary);
117
+ }
118
+
119
+ /* Invalid state */
120
+ .switch-track--invalid {
121
+ background: var(--snice-color-danger-light);
122
+ }
123
+
124
+ .switch-input:checked ~ .switch-track--invalid {
125
+ background: var(--snice-color-danger);
126
+ }
127
+
128
+ /* Hover effect */
129
+ .switch-wrapper:not(.switch-wrapper--disabled):hover .switch-track {
130
+ background: var(--snice-color-border-hover);
131
+ }
132
+
133
+ .switch-wrapper:not(.switch-wrapper--disabled):hover .switch-input:checked ~ .switch-track {
134
+ background: var(--snice-color-primary-hover);
135
+ }
136
+
137
+ /* Labels */
138
+ .switch-label {
139
+ color: var(--snice-color-text);
140
+ cursor: pointer;
141
+ }
142
+
143
+ .switch-label--small {
144
+ font-size: var(--snice-font-size-sm);
145
+ }
146
+
147
+ .switch-label--medium {
148
+ font-size: var(--snice-font-size-md);
149
+ }
150
+
151
+ .switch-label--large {
152
+ font-size: var(--snice-font-size-lg);
153
+ }
154
+
155
+ .switch-wrapper--disabled .switch-label {
156
+ color: var(--snice-color-text-secondary);
157
+ cursor: not-allowed;
158
+ }
159
+
160
+ /* Required indicator */
161
+ .switch-label--required::after {
162
+ content: ' *';
163
+ color: var(--snice-color-danger);
164
+ }
165
+
166
+ /* State labels (on/off text inside the track) */
167
+ .switch-state-label {
168
+ position: absolute;
169
+ top: 50%;
170
+ transform: translateY(-50%);
171
+ font-size: 0.625rem; /* 10px */
172
+ font-weight: 600;
173
+ color: white;
174
+ text-transform: uppercase;
175
+ opacity: 0;
176
+ transition: opacity var(--snice-transition-fast) ease;
177
+ pointer-events: none;
178
+ }
179
+
180
+ .switch-state-label--on {
181
+ left: 0.375rem; /* 6px */
182
+ }
183
+
184
+ .switch-state-label--off {
185
+ right: 0.375rem; /* 6px */
186
+ }
187
+
188
+ .switch-track--medium .switch-state-label {
189
+ font-size: 0.6875rem; /* 11px */
190
+ }
191
+
192
+ .switch-track--large .switch-state-label {
193
+ font-size: 0.75rem; /* 12px */
194
+ }
195
+
196
+ .switch-input:checked ~ .switch-track .switch-state-label--on {
197
+ opacity: 0.8;
198
+ }
199
+
200
+ .switch-input:not(:checked) ~ .switch-track .switch-state-label--off {
201
+ opacity: 0.8;
202
+ }
203
+
204
+ /* Hide state labels on small size */
205
+ .switch-track--small .switch-state-label {
206
+ display: none;
207
+ }
208
+
209
+ /* Loading state */
210
+ .switch-track--loading .switch-thumb {
211
+ animation: switch-pulse 1s ease-in-out infinite;
212
+ }
213
+
214
+ @keyframes switch-pulse {
215
+ 0%, 100% {
216
+ opacity: 1;
217
+ }
218
+ 50% {
219
+ opacity: 0.5;
220
+ }
221
+ }