@sondor-technology/sondor-components 1.0.0 → 1.0.1-develop.11

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 (168) hide show
  1. package/README.md +8 -0
  2. package/dist/components/alert/alert.d.ts +13 -0
  3. package/dist/components/alert/alert.d.ts.map +1 -0
  4. package/dist/components/alert/alert.js +105 -0
  5. package/dist/components/badge/badge.d.ts +13 -0
  6. package/dist/components/badge/badge.d.ts.map +1 -0
  7. package/dist/components/badge/badge.js +101 -0
  8. package/dist/components/breadcrumb/breadcrumb.d.ts +19 -0
  9. package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
  10. package/dist/components/breadcrumb/breadcrumb.js +99 -0
  11. package/dist/components/button/button.d.ts +23 -0
  12. package/dist/components/button/button.d.ts.map +1 -0
  13. package/dist/components/button/button.js +304 -0
  14. package/dist/components/button-group/button-group.d.ts +6 -0
  15. package/dist/components/button-group/button-group.d.ts.map +1 -0
  16. package/dist/components/button-group/button-group.js +74 -0
  17. package/dist/components/card/card.d.ts +11 -0
  18. package/dist/components/card/card.d.ts.map +1 -0
  19. package/dist/components/card/card.js +54 -0
  20. package/dist/components/checkbox/checkbox.d.ts +31 -0
  21. package/dist/components/checkbox/checkbox.d.ts.map +1 -0
  22. package/dist/components/checkbox/checkbox.js +265 -0
  23. package/dist/components/code-block/code-block.d.ts +20 -0
  24. package/dist/components/code-block/code-block.d.ts.map +1 -0
  25. package/dist/components/code-block/code-block.js +260 -0
  26. package/dist/components/content-card/content-card.d.ts +13 -0
  27. package/dist/components/content-card/content-card.d.ts.map +1 -0
  28. package/dist/components/content-card/content-card.js +140 -0
  29. package/dist/components/divider/divider.d.ts +16 -0
  30. package/dist/components/divider/divider.d.ts.map +1 -0
  31. package/dist/components/divider/divider.js +82 -0
  32. package/dist/components/drawer/drawer.d.ts +22 -0
  33. package/dist/components/drawer/drawer.d.ts.map +1 -0
  34. package/dist/components/drawer/drawer.js +226 -0
  35. package/dist/components/field/field.d.ts +11 -0
  36. package/dist/components/field/field.d.ts.map +1 -0
  37. package/dist/components/field/field.js +32 -0
  38. package/dist/components/footer/footer.d.ts +6 -0
  39. package/dist/components/footer/footer.d.ts.map +1 -0
  40. package/dist/components/footer/footer.js +65 -0
  41. package/dist/components/form/form-step.d.ts +14 -0
  42. package/dist/components/form/form-step.d.ts.map +1 -0
  43. package/dist/components/form/form-step.js +44 -0
  44. package/dist/components/form/form.d.ts +30 -0
  45. package/dist/components/form/form.d.ts.map +1 -0
  46. package/dist/components/form/form.js +456 -0
  47. package/dist/components/grid/grid-row.d.ts +17 -0
  48. package/dist/components/grid/grid-row.d.ts.map +1 -0
  49. package/dist/components/grid/grid-row.js +92 -0
  50. package/dist/components/grid/grid.d.ts +13 -0
  51. package/dist/components/grid/grid.d.ts.map +1 -0
  52. package/dist/components/grid/grid.js +42 -0
  53. package/dist/components/icon/icon-paths.d.ts +132 -0
  54. package/dist/components/icon/icon-paths.d.ts.map +1 -0
  55. package/dist/components/icon/icon-paths.js +137 -0
  56. package/dist/components/icon/icon.d.ts +17 -0
  57. package/dist/components/icon/icon.d.ts.map +1 -0
  58. package/dist/components/icon/icon.js +95 -0
  59. package/dist/components/input/input.d.ts +31 -0
  60. package/dist/components/input/input.d.ts.map +1 -0
  61. package/dist/components/input/input.js +268 -0
  62. package/dist/components/link/link.d.ts +16 -0
  63. package/dist/components/link/link.d.ts.map +1 -0
  64. package/dist/components/link/link.js +116 -0
  65. package/dist/components/metadata/metadata.d.ts +18 -0
  66. package/dist/components/metadata/metadata.d.ts.map +1 -0
  67. package/dist/components/metadata/metadata.js +71 -0
  68. package/dist/components/modal/modal.d.ts +35 -0
  69. package/dist/components/modal/modal.d.ts.map +1 -0
  70. package/dist/components/modal/modal.js +251 -0
  71. package/dist/components/page-layout/page-layout.d.ts +46 -0
  72. package/dist/components/page-layout/page-layout.d.ts.map +1 -0
  73. package/dist/components/page-layout/page-layout.js +697 -0
  74. package/dist/components/radio/radio.d.ts +16 -0
  75. package/dist/components/radio/radio.d.ts.map +1 -0
  76. package/dist/components/radio/radio.js +152 -0
  77. package/dist/components/radio-group/radio-group.d.ts +27 -0
  78. package/dist/components/radio-group/radio-group.d.ts.map +1 -0
  79. package/dist/components/radio-group/radio-group.js +161 -0
  80. package/dist/components/router/router.d.ts +31 -0
  81. package/dist/components/router/router.d.ts.map +1 -0
  82. package/dist/components/router/router.js +97 -0
  83. package/dist/components/select/select.d.ts +16 -0
  84. package/dist/components/select/select.d.ts.map +1 -0
  85. package/dist/components/select/select.js +172 -0
  86. package/dist/components/skeleton/skeleton.d.ts +16 -0
  87. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  88. package/dist/components/skeleton/skeleton.js +149 -0
  89. package/dist/components/switch/switch.d.ts +25 -0
  90. package/dist/components/switch/switch.d.ts.map +1 -0
  91. package/dist/components/switch/switch.js +185 -0
  92. package/dist/components/table/data-table.d.ts +72 -0
  93. package/dist/components/table/data-table.d.ts.map +1 -0
  94. package/dist/components/table/data-table.js +551 -0
  95. package/dist/components/table/table.d.ts +22 -0
  96. package/dist/components/table/table.d.ts.map +1 -0
  97. package/dist/components/table/table.js +167 -0
  98. package/dist/components/tabs/tab-panel.d.ts +8 -0
  99. package/dist/components/tabs/tab-panel.d.ts.map +1 -0
  100. package/dist/components/tabs/tab-panel.js +46 -0
  101. package/dist/components/tabs/tab.d.ts +9 -0
  102. package/dist/components/tabs/tab.d.ts.map +1 -0
  103. package/dist/components/tabs/tab.js +91 -0
  104. package/dist/components/tabs/tabs.d.ts +14 -0
  105. package/dist/components/tabs/tabs.d.ts.map +1 -0
  106. package/dist/components/tabs/tabs.js +85 -0
  107. package/dist/components/textarea/textarea.d.ts +36 -0
  108. package/dist/components/textarea/textarea.d.ts.map +1 -0
  109. package/dist/components/textarea/textarea.js +231 -0
  110. package/dist/components/theme-toggle/theme-toggle.d.ts +15 -0
  111. package/dist/components/theme-toggle/theme-toggle.d.ts.map +1 -0
  112. package/dist/components/theme-toggle/theme-toggle.js +130 -0
  113. package/dist/components/vertical-nav/vertical-nav-group.d.ts +15 -0
  114. package/dist/components/vertical-nav/vertical-nav-group.d.ts.map +1 -0
  115. package/dist/components/vertical-nav/vertical-nav-group.js +127 -0
  116. package/dist/components/vertical-nav/vertical-nav-item.d.ts +14 -0
  117. package/dist/components/vertical-nav/vertical-nav-item.d.ts.map +1 -0
  118. package/dist/components/vertical-nav/vertical-nav-item.js +85 -0
  119. package/dist/styles/index.css +171 -0
  120. package/dist/styles/reset.d.ts +2 -0
  121. package/dist/styles/reset.d.ts.map +1 -0
  122. package/dist/styles/reset.js +50 -0
  123. package/package.json +7 -2
  124. package/src/components/alert/alert.ts +100 -100
  125. package/src/components/badge/badge.ts +96 -96
  126. package/src/components/breadcrumb/breadcrumb.ts +98 -98
  127. package/src/components/button/button.ts +281 -281
  128. package/src/components/button-group/button-group.ts +67 -67
  129. package/src/components/card/card.ts +52 -52
  130. package/src/components/checkbox/checkbox.ts +266 -238
  131. package/src/components/code-block/code-block.ts +256 -256
  132. package/src/components/content-card/content-card.ts +133 -133
  133. package/src/components/divider/divider.ts +68 -68
  134. package/src/components/drawer/drawer.ts +217 -217
  135. package/src/components/field/field.ts +30 -30
  136. package/src/components/footer/footer.ts +58 -58
  137. package/src/components/form/form-step.ts +36 -32
  138. package/src/components/form/form.ts +492 -376
  139. package/src/components/grid/grid-row.ts +75 -75
  140. package/src/components/grid/grid.ts +36 -36
  141. package/src/components/icon/icon-paths.ts +140 -124
  142. package/src/components/icon/icon.ts +84 -82
  143. package/src/components/input/input.ts +248 -248
  144. package/src/components/link/link.ts +97 -97
  145. package/src/components/metadata/metadata.ts +66 -66
  146. package/src/components/modal/modal.ts +246 -158
  147. package/src/components/page-layout/page-layout.ts +704 -704
  148. package/src/components/radio/radio.ts +142 -142
  149. package/src/components/radio-group/radio-group.ts +157 -157
  150. package/src/components/router/router.ts +133 -46
  151. package/src/components/select/select.ts +161 -161
  152. package/src/components/skeleton/skeleton.ts +134 -67
  153. package/src/components/switch/switch.ts +181 -181
  154. package/src/components/table/data-table.ts +556 -556
  155. package/src/components/table/table.ts +153 -153
  156. package/src/components/tabs/tab-panel.ts +30 -30
  157. package/src/components/tabs/tab.ts +76 -76
  158. package/src/components/tabs/tabs.ts +88 -88
  159. package/src/components/textarea/textarea.ts +215 -215
  160. package/src/components/theme-toggle/theme-toggle.ts +131 -131
  161. package/src/components/vertical-nav/vertical-nav-group.ts +115 -96
  162. package/src/components/vertical-nav/vertical-nav-item.ts +72 -49
  163. package/src/styles/globals.css +58 -58
  164. package/src/styles/index.css +80 -80
  165. package/src/styles/reset.ts +50 -50
  166. package/src/styles/theme.css +58 -58
  167. package/src/styles/typography.css +25 -25
  168. package/tsconfig.json +15 -15
@@ -1,68 +1,135 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement, property } from 'lit/decorators.js';
3
- import { resetStyles } from '../../styles/reset.js';
4
-
5
- @customElement('st-skeleton')
6
- export class StSkeleton extends LitElement {
7
- /** Variant shape: 'text' | 'circular' | 'rectangular' */
8
- @property({ type: String }) variant: 'text' | 'circular' | 'rectangular' = 'text';
9
-
10
- /** Custom width override (e.g., '200px', '50%') */
11
- @property({ type: String }) width = '';
12
-
13
- /** Custom height override (e.g., '40px', '1rem') */
14
- @property({ type: String }) height = '';
15
-
16
- static styles = [
17
- resetStyles,
18
- css`
19
- :host {
20
- display: block;
21
- width: 100%;
22
- }
23
-
24
- .skeleton {
25
- width: var(--st-skeleton-width, 100%);
26
- height: var(--st-skeleton-height, 1em);
27
- background: linear-gradient(
28
- 90deg,
29
- var(--st-color-surface-muted, #f1f5f9) 25%,
30
- var(--st-color-border, #e2e8f0) 37%,
31
- var(--st-color-surface-muted, #f1f5f9) 63%
32
- );
33
- background-size: 400% 100%;
34
- animation: st-shimmer 1.4s ease infinite;
35
- border-radius: var(--st-radius-sm, 0.25rem);
36
- }
37
-
38
- :host([variant="circular"]) .skeleton {
39
- border-radius: 50%;
40
- height: var(--st-skeleton-height, 2.5rem);
41
- width: var(--st-skeleton-width, 2.5rem);
42
- }
43
-
44
- :host([variant="rectangular"]) .skeleton {
45
- height: var(--st-skeleton-height, 8rem);
46
- }
47
-
48
- @keyframes st-shimmer {
49
- 0% { background-position: 100% 0; }
50
- 100% { background-position: 0 0; }
51
- }
52
- `
53
- ];
54
-
55
- render() {
56
- const style = `
57
- ${this.width ? `--st-skeleton-width: ${this.width};` : ''}
58
- ${this.height ? `--st-skeleton-height: ${this.height};` : ''}
59
- `;
60
- return html`<div class="skeleton" style=${style}></div>`;
61
- }
62
- }
63
-
64
- declare global {
65
- interface HTMLElementTagNameMap {
66
- 'st-skeleton': StSkeleton;
67
- }
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { resetStyles } from '../../styles/reset.js';
4
+
5
+ @customElement('st-skeleton')
6
+ export class StSkeleton extends LitElement {
7
+ @property({ type: String, reflect: true }) variant: 'text' | 'circular' | 'rectangular' = 'text';
8
+ @property({ type: String, reflect: true }) animation: 'shimmer' | 'pulse' | 'none' = 'shimmer';
9
+ @property({ type: String }) width = '';
10
+ @property({ type: String }) height = '';
11
+
12
+ // Apply properties directly to the :host as CSS variables when they change
13
+ updated(changedProperties: Map<string | number | symbol, unknown>) {
14
+ super.updated(changedProperties);
15
+
16
+ if (changedProperties.has('width')) {
17
+ if (this.width) {
18
+ this.style.setProperty('--st-skeleton-width', this.width);
19
+ } else {
20
+ this.style.removeProperty('--st-skeleton-width');
21
+ }
22
+ }
23
+
24
+ if (changedProperties.has('height')) {
25
+ if (this.height) {
26
+ this.style.setProperty('--st-skeleton-height', this.height);
27
+ } else {
28
+ this.style.removeProperty('--st-skeleton-height');
29
+ }
30
+ }
31
+ }
32
+
33
+ static styles = [
34
+ resetStyles,
35
+ css`
36
+ :host {
37
+ display: block;
38
+
39
+ /* Define base sizing variables that can be overridden by props */
40
+ --_width: var(--st-skeleton-width, 100%);
41
+ --_height: var(--st-skeleton-height, 1em);
42
+
43
+ /*
44
+ CRITICAL FIX: Apply sizing directly to the host.
45
+ This allows external classes and styles (like margin: auto) to work perfectly.
46
+ */
47
+ width: var(--_width);
48
+ height: var(--_height);
49
+
50
+ /* Expose radius as a token for external theme overrides */
51
+ --st-skeleton-radius: var(--st-radius-sm, 0.25rem);
52
+ }
53
+
54
+ /* Shape Variants adjust the host's sizing variables */
55
+ :host([variant="circular"]) {
56
+ --_width: var(--st-skeleton-width, 2.5rem);
57
+ --_height: var(--st-skeleton-height, 2.5rem);
58
+ }
59
+
60
+ :host([variant="rectangular"]) {
61
+ --_height: var(--st-skeleton-height, 8rem);
62
+ }
63
+
64
+ .skeleton {
65
+ /* The inner element simply fills the host */
66
+ display: block;
67
+ width: 100%;
68
+ height: 100%;
69
+
70
+ background: linear-gradient(
71
+ 90deg,
72
+ var(--st-color-surface-muted, #f1f5f9) 25%,
73
+ var(--st-color-border, #e2e8f0) 37%,
74
+ var(--st-color-surface-muted, #f1f5f9) 63%
75
+ );
76
+ background-size: 400% 100%;
77
+ border-radius: var(--st-skeleton-radius);
78
+
79
+ /* Prevent accidental selection */
80
+ user-select: none;
81
+ pointer-events: none;
82
+ }
83
+
84
+ :host([variant="circular"]) .skeleton {
85
+ border-radius: 50%;
86
+ }
87
+
88
+ /* Animations */
89
+ :host([animation="shimmer"]) .skeleton {
90
+ animation: st-shimmer 1.4s ease infinite;
91
+ }
92
+
93
+ :host([animation="pulse"]) .skeleton {
94
+ background: var(--st-color-surface-muted, #f1f5f9);
95
+ animation: st-pulse 1.5s ease-in-out infinite;
96
+ }
97
+
98
+ :host([animation="none"]) .skeleton {
99
+ background: var(--st-color-surface-muted, #f1f5f9);
100
+ animation: none;
101
+ }
102
+
103
+ /* Accessibility: Respect OS reduced motion settings */
104
+ @media (prefers-reduced-motion: reduce) {
105
+ .skeleton {
106
+ animation: none !important;
107
+ background: var(--st-color-surface-muted, #f1f5f9) !important;
108
+ }
109
+ }
110
+
111
+ @keyframes st-shimmer {
112
+ 0% { background-position: 100% 0; }
113
+ 100% { background-position: -100% 0; }
114
+ }
115
+
116
+ @keyframes st-pulse {
117
+ 0% { opacity: 1; }
118
+ 50% { opacity: 0.4; }
119
+ 100% { opacity: 1; }
120
+ }
121
+ `
122
+ ];
123
+
124
+ render() {
125
+ return html`
126
+ <div class="skeleton" aria-hidden="true" part="base"></div>
127
+ `;
128
+ }
129
+ }
130
+
131
+ declare global {
132
+ interface HTMLElementTagNameMap {
133
+ 'st-skeleton': StSkeleton;
134
+ }
68
135
  }
@@ -1,181 +1,181 @@
1
- import {LitElement, html, css, PropertyValues} from "lit";
2
- import {customElement, property, query} from "lit/decorators.js";
3
- import {resetStyles} from "../../styles/reset.js";
4
-
5
- @customElement("st-switch")
6
- export class StSwitch extends LitElement {
7
- static formAssociated = true;
8
- private _internals: ElementInternals;
9
-
10
- @query("input") private _inputEl!: HTMLInputElement;
11
-
12
- @property({type: String, reflect: true}) name = "";
13
- @property({type: String}) value = "on";
14
-
15
- @property({type: Boolean, reflect: true}) checked = false;
16
- @property({type: Boolean, reflect: true}) disabled = false;
17
- @property({type: Boolean, reflect: true}) required = false;
18
-
19
- @property({attribute: "aria-label"}) ariaLabel = "";
20
-
21
- constructor() {
22
- super();
23
- this._internals = this.attachInternals();
24
- }
25
-
26
- static styles = [
27
- resetStyles,
28
- css`
29
- :host {
30
- display: inline-block;
31
- outline: none;
32
- user-select: none;
33
- }
34
-
35
- label {
36
- display: inline-flex;
37
- align-items: center;
38
- gap: var(--st-spacing-sm);
39
- cursor: pointer;
40
- color: var(--st-color-text);
41
- font-size: var(--st-font-size-base);
42
- }
43
-
44
- /* Visually hide the native input */
45
- input {
46
- position: absolute;
47
- width: 1px;
48
- height: 1px;
49
- padding: 0;
50
- margin: -1px;
51
- overflow: hidden;
52
- clip: rect(0, 0, 0, 0);
53
- white-space: nowrap;
54
- border-width: 0;
55
- }
56
-
57
- /* The Switch Track (Pill) */
58
- .track {
59
- display: flex;
60
- align-items: center;
61
- width: 2.25rem; /* 36px */
62
- height: 1.25rem; /* 20px */
63
- background-color: var(--st-color-border);
64
- border-radius: 999px;
65
- padding: 2px;
66
- transition:
67
- background-color 0.2s ease,
68
- box-shadow 0.2s ease;
69
- flex-shrink: 0;
70
- }
71
-
72
- /* The Switch Thumb (Circle) */
73
- .thumb {
74
- width: 1rem; /* 16px */
75
- height: 1rem; /* 16px */
76
- background-color: var(--st-white);
77
- border-radius: 50%;
78
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
79
- transform: translateX(0);
80
- transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
81
- }
82
-
83
- /* Focus State */
84
- input:focus-visible + .track {
85
- box-shadow:
86
- 0 0 0 2px var(--st-color-bg),
87
- 0 0 0 4px var(--st-color-primary);
88
- }
89
-
90
- /* Checked State */
91
- :host([checked]) .track {
92
- background-color: var(--st-color-primary);
93
- }
94
-
95
- :host([checked]) .thumb {
96
- /* Move thumb to the right: Track width (36px) - Padding (4px) - Thumb width (16px) = 16px */
97
- transform: translateX(16px);
98
- }
99
-
100
- /* Hover State (Subtle darkening of the track if unchecked) */
101
- label:hover input:not(:checked) + .track {
102
- background-color: var(--st-color-text-muted);
103
- }
104
-
105
- /* Disabled State */
106
- :host([disabled]) {
107
- pointer-events: none;
108
- }
109
-
110
- :host([disabled]) label {
111
- opacity: 0.5;
112
- cursor: not-allowed;
113
- }
114
- `,
115
- ];
116
-
117
- override updated(changedProperties: PropertyValues<this>) {
118
- super.updated(changedProperties);
119
-
120
- if (changedProperties.has("checked") || changedProperties.has("value")) {
121
- this._internals.setFormValue(this.checked ? this.value : null);
122
- this._syncValidity();
123
- }
124
-
125
- if (changedProperties.has("required")) {
126
- this._syncValidity();
127
- }
128
- }
129
-
130
- private _syncValidity() {
131
- if (!this._inputEl) return;
132
- this._internals.setValidity(
133
- this._inputEl.validity,
134
- this._inputEl.validationMessage,
135
- this._inputEl,
136
- );
137
- }
138
-
139
- formResetCallback() {
140
- this.checked = false;
141
- this._internals.setFormValue(null);
142
- this.updateComplete.then(() => this._syncValidity());
143
- }
144
-
145
- private _handleChange(e: Event) {
146
- e.stopPropagation();
147
-
148
- if (this.disabled) return;
149
-
150
- this.checked = this._inputEl.checked;
151
- this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
152
- }
153
-
154
- render() {
155
- return html`
156
- <label>
157
- <input
158
- type="checkbox"
159
- role="switch"
160
- aria-label=${this.ariaLabel || ""}
161
- .name=${this.name}
162
- .value=${this.value}
163
- .checked=${this.checked}
164
- ?disabled=${this.disabled}
165
- ?required=${this.required}
166
- @change=${this._handleChange}
167
- />
168
- <div class="track">
169
- <div class="thumb"></div>
170
- </div>
171
- <span class="label"><slot></slot></span>
172
- </label>
173
- `;
174
- }
175
- }
176
-
177
- declare global {
178
- interface HTMLElementTagNameMap {
179
- "st-switch": StSwitch;
180
- }
181
- }
1
+ import {LitElement, html, css, PropertyValues} from "lit";
2
+ import {customElement, property, query} from "lit/decorators.js";
3
+ import {resetStyles} from "../../styles/reset.js";
4
+
5
+ @customElement("st-switch")
6
+ export class StSwitch extends LitElement {
7
+ static formAssociated = true;
8
+ private _internals: ElementInternals;
9
+
10
+ @query("input") private _inputEl!: HTMLInputElement;
11
+
12
+ @property({type: String, reflect: true}) name = "";
13
+ @property({type: String}) value = "on";
14
+
15
+ @property({type: Boolean, reflect: true}) checked = false;
16
+ @property({type: Boolean, reflect: true}) disabled = false;
17
+ @property({type: Boolean, reflect: true}) required = false;
18
+
19
+ @property({attribute: "aria-label"}) ariaLabel = "";
20
+
21
+ constructor() {
22
+ super();
23
+ this._internals = this.attachInternals();
24
+ }
25
+
26
+ static styles = [
27
+ resetStyles,
28
+ css`
29
+ :host {
30
+ display: inline-block;
31
+ outline: none;
32
+ user-select: none;
33
+ }
34
+
35
+ label {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: var(--st-spacing-sm);
39
+ cursor: pointer;
40
+ color: var(--st-color-text);
41
+ font-size: var(--st-font-size-base);
42
+ }
43
+
44
+ /* Visually hide the native input */
45
+ input {
46
+ position: absolute;
47
+ width: 1px;
48
+ height: 1px;
49
+ padding: 0;
50
+ margin: -1px;
51
+ overflow: hidden;
52
+ clip: rect(0, 0, 0, 0);
53
+ white-space: nowrap;
54
+ border-width: 0;
55
+ }
56
+
57
+ /* The Switch Track (Pill) */
58
+ .track {
59
+ display: flex;
60
+ align-items: center;
61
+ width: 2.25rem; /* 36px */
62
+ height: 1.25rem; /* 20px */
63
+ background-color: var(--st-color-border);
64
+ border-radius: 999px;
65
+ padding: 2px;
66
+ transition:
67
+ background-color 0.2s ease,
68
+ box-shadow 0.2s ease;
69
+ flex-shrink: 0;
70
+ }
71
+
72
+ /* The Switch Thumb (Circle) */
73
+ .thumb {
74
+ width: 1rem; /* 16px */
75
+ height: 1rem; /* 16px */
76
+ background-color: var(--st-white);
77
+ border-radius: 50%;
78
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
79
+ transform: translateX(0);
80
+ transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
81
+ }
82
+
83
+ /* Focus State */
84
+ input:focus-visible + .track {
85
+ box-shadow:
86
+ 0 0 0 2px var(--st-color-bg),
87
+ 0 0 0 4px var(--st-color-primary);
88
+ }
89
+
90
+ /* Checked State */
91
+ :host([checked]) .track {
92
+ background-color: var(--st-color-primary);
93
+ }
94
+
95
+ :host([checked]) .thumb {
96
+ /* Move thumb to the right: Track width (36px) - Padding (4px) - Thumb width (16px) = 16px */
97
+ transform: translateX(16px);
98
+ }
99
+
100
+ /* Hover State (Subtle darkening of the track if unchecked) */
101
+ label:hover input:not(:checked) + .track {
102
+ background-color: var(--st-color-text-muted);
103
+ }
104
+
105
+ /* Disabled State */
106
+ :host([disabled]) {
107
+ pointer-events: none;
108
+ }
109
+
110
+ :host([disabled]) label {
111
+ opacity: 0.5;
112
+ cursor: not-allowed;
113
+ }
114
+ `,
115
+ ];
116
+
117
+ override updated(changedProperties: PropertyValues<this>) {
118
+ super.updated(changedProperties);
119
+
120
+ if (changedProperties.has("checked") || changedProperties.has("value")) {
121
+ this._internals.setFormValue(this.checked ? this.value : null);
122
+ this._syncValidity();
123
+ }
124
+
125
+ if (changedProperties.has("required")) {
126
+ this._syncValidity();
127
+ }
128
+ }
129
+
130
+ private _syncValidity() {
131
+ if (!this._inputEl) return;
132
+ this._internals.setValidity(
133
+ this._inputEl.validity,
134
+ this._inputEl.validationMessage,
135
+ this._inputEl,
136
+ );
137
+ }
138
+
139
+ formResetCallback() {
140
+ this.checked = false;
141
+ this._internals.setFormValue(null);
142
+ this.updateComplete.then(() => this._syncValidity());
143
+ }
144
+
145
+ private _handleChange(e: Event) {
146
+ e.stopPropagation();
147
+
148
+ if (this.disabled) return;
149
+
150
+ this.checked = this._inputEl.checked;
151
+ this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
152
+ }
153
+
154
+ render() {
155
+ return html`
156
+ <label>
157
+ <input
158
+ type="checkbox"
159
+ role="switch"
160
+ aria-label=${this.ariaLabel || ""}
161
+ .name=${this.name}
162
+ .value=${this.value}
163
+ .checked=${this.checked}
164
+ ?disabled=${this.disabled}
165
+ ?required=${this.required}
166
+ @change=${this._handleChange}
167
+ />
168
+ <div class="track">
169
+ <div class="thumb"></div>
170
+ </div>
171
+ <span class="label"><slot></slot></span>
172
+ </label>
173
+ `;
174
+ }
175
+ }
176
+
177
+ declare global {
178
+ interface HTMLElementTagNameMap {
179
+ "st-switch": StSwitch;
180
+ }
181
+ }