@luanlu/mk-motion 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/package.json +2 -4
  2. package/src/a11y/focus-trap.ts +64 -0
  3. package/src/a11y/keyboard.ts +43 -0
  4. package/src/components/alert/alert.css +111 -0
  5. package/src/components/alert/alert.ts +107 -0
  6. package/src/components/avatar/avatar.css +112 -0
  7. package/src/components/avatar/avatar.ts +175 -0
  8. package/src/components/breadcrumb/breadcrumb.css +31 -0
  9. package/src/components/breadcrumb/breadcrumb.ts +71 -0
  10. package/src/components/button/button.css +108 -0
  11. package/src/components/button/button.ts +140 -0
  12. package/src/components/card/card.css +52 -0
  13. package/src/components/card/card.ts +87 -0
  14. package/src/components/collapse/collapse.css +76 -0
  15. package/src/components/collapse/collapse.ts +168 -0
  16. package/src/components/dialog/dialog.css +78 -0
  17. package/src/components/dialog/dialog.ts +164 -0
  18. package/src/components/drawer/drawer.css +73 -0
  19. package/src/components/drawer/drawer.ts +131 -0
  20. package/src/components/empty/empty.css +36 -0
  21. package/src/components/empty/empty.ts +85 -0
  22. package/src/components/form/checkbox.css +56 -0
  23. package/src/components/form/checkbox.ts +119 -0
  24. package/src/components/form/radio.css +57 -0
  25. package/src/components/form/radio.ts +153 -0
  26. package/src/components/form/select.css +91 -0
  27. package/src/components/form/select.ts +174 -0
  28. package/src/components/form/slider.css +56 -0
  29. package/src/components/form/slider.ts +148 -0
  30. package/src/components/input/input.css +92 -0
  31. package/src/components/input/input.ts +162 -0
  32. package/src/components/layout/divider.css +32 -0
  33. package/src/components/layout/divider.ts +42 -0
  34. package/src/components/layout/row.css +64 -0
  35. package/src/components/layout/row.ts +57 -0
  36. package/src/components/layout/space.css +14 -0
  37. package/src/components/layout/space.ts +48 -0
  38. package/src/components/loading/loading.css +37 -0
  39. package/src/components/loading/loading.ts +46 -0
  40. package/src/components/menu/menu.css +121 -0
  41. package/src/components/menu/menu.ts +187 -0
  42. package/src/components/message/message.css +64 -0
  43. package/src/components/message/message.ts +96 -0
  44. package/src/components/popover/popover.css +73 -0
  45. package/src/components/popover/popover.ts +279 -0
  46. package/src/components/progress/progress.css +112 -0
  47. package/src/components/progress/progress.ts +171 -0
  48. package/src/components/steps/steps.css +127 -0
  49. package/src/components/steps/steps.ts +102 -0
  50. package/src/components/styles/components.css +28 -0
  51. package/src/components/styles/reset.css +24 -0
  52. package/src/components/styles/tokens.css +248 -0
  53. package/src/components/styles/variables.css +24 -0
  54. package/src/components/switch/switch.css +53 -0
  55. package/src/components/switch/switch.ts +103 -0
  56. package/src/components/table/table.css +192 -0
  57. package/src/components/table/table.ts +370 -0
  58. package/src/components/tabs/tabs.css +138 -0
  59. package/src/components/tabs/tabs.ts +211 -0
  60. package/src/components/tag/tag.css +123 -0
  61. package/src/components/tag/tag.ts +112 -0
  62. package/src/components/tooltip/tooltip.css +66 -0
  63. package/src/components/tooltip/tooltip.ts +185 -0
  64. package/src/core/animator.ts +124 -0
  65. package/src/core/timeline.ts +128 -0
  66. package/src/core/utils.ts +47 -0
  67. package/src/effects/glitch.ts +99 -0
  68. package/src/effects/particle.ts +134 -0
  69. package/src/effects/text-split.ts +95 -0
  70. package/src/effects/wave-text.ts +88 -0
  71. package/src/gesture/draggable.ts +130 -0
  72. package/src/gesture/spring.ts +152 -0
  73. package/src/index.ts +162 -0
  74. package/src/interactive/coverflow.ts +100 -0
  75. package/src/interactive/cursor-trail.ts +113 -0
  76. package/src/interactive/flip-card.ts +114 -0
  77. package/src/interactive/magnetic.ts +121 -0
  78. package/src/micro/hover-lift.ts +94 -0
  79. package/src/micro/ripple.ts +130 -0
  80. package/src/motion/component-motion.ts +177 -0
  81. package/src/presets/index.ts +69 -0
  82. package/src/scroll/scroll-trigger.ts +104 -0
  83. package/src/styles/animations.css +135 -0
  84. package/src/styles/element-plus.css +174 -0
  85. package/src/text/count-up.ts +108 -0
  86. package/src/text/typewriter.ts +109 -0
  87. package/src/theme/dark.css +19 -0
  88. package/src/theme/light.css +19 -0
  89. package/src/theme/theme.ts +65 -0
  90. package/src/transitions/blur-reveal.ts +92 -0
  91. package/src/transitions/collapse.ts +112 -0
  92. package/src/transitions/lazy-image.ts +87 -0
  93. package/src/transitions/list.ts +75 -0
  94. package/src/transitions/loading.ts +95 -0
  95. package/src/transitions/parallax.ts +60 -0
  96. package/src/transitions/shimmer.ts +105 -0
  97. package/src/transitions/toast.ts +151 -0
  98. package/src/types.d.ts +4 -0
@@ -0,0 +1,102 @@
1
+ import './steps.css'
2
+
3
+ export interface StepItem {
4
+ title: string
5
+ description?: string
6
+ icon?: string
7
+ status?: 'wait' | 'process' | 'finish' | 'error'
8
+ }
9
+
10
+ export interface StepsOptions {
11
+ direction?: 'horizontal' | 'vertical'
12
+ current?: number
13
+ items: StepItem[]
14
+ size?: 'small' | 'default'
15
+ }
16
+
17
+ export class MkSteps {
18
+ el: HTMLElement
19
+ private options: StepsOptions
20
+
21
+ constructor(container: HTMLElement | string, options: StepsOptions) {
22
+ const parent = typeof container === 'string' ? document.querySelector(container)! : container
23
+ this.options = { direction: 'horizontal', current: 0, size: 'default', ...options }
24
+
25
+ this.el = document.createElement('div')
26
+ this.el.className = `mk-steps mk-steps--${this.options.direction}`
27
+ if (this.options.size === 'small') this.el.classList.add('mk-steps--small')
28
+
29
+ this.render()
30
+ parent.appendChild(this.el)
31
+ }
32
+
33
+ private render(): void {
34
+ this.el.innerHTML = ''
35
+ const { items, current = 0, direction } = this.options
36
+
37
+ items.forEach((item, index) => {
38
+ const step = document.createElement('div')
39
+ step.className = 'mk-step'
40
+
41
+ const status = item.status ?? (index < current ? 'finish' : index === current ? 'process' : 'wait')
42
+ step.classList.add(`is-${status}`)
43
+ if (index === items.length - 1) step.classList.add('is-last')
44
+
45
+ const head = document.createElement('div')
46
+ head.className = 'mk-step__head'
47
+
48
+ const line = document.createElement('div')
49
+ line.className = 'mk-step__line'
50
+ if (direction === 'vertical') {
51
+ line.style.width = '1px'
52
+ line.style.height = '100%'
53
+ line.style.left = '15px'
54
+ line.style.top = '30px'
55
+ }
56
+ head.appendChild(line)
57
+
58
+ const icon = document.createElement('div')
59
+ icon.className = 'mk-step__icon'
60
+ if (status === 'finish') {
61
+ icon.textContent = item.icon || '✓'
62
+ } else if (status === 'error') {
63
+ icon.textContent = item.icon || '✕'
64
+ } else {
65
+ icon.textContent = item.icon || String(index + 1)
66
+ }
67
+ head.appendChild(icon)
68
+
69
+ const main = document.createElement('div')
70
+ main.className = 'mk-step__main'
71
+
72
+ const title = document.createElement('div')
73
+ title.className = 'mk-step__title'
74
+ title.textContent = item.title
75
+ main.appendChild(title)
76
+
77
+ if (item.description) {
78
+ const desc = document.createElement('div')
79
+ desc.className = 'mk-step__description'
80
+ desc.textContent = item.description
81
+ main.appendChild(desc)
82
+ }
83
+
84
+ step.appendChild(head)
85
+ step.appendChild(main)
86
+ this.el.appendChild(step)
87
+ })
88
+ }
89
+
90
+ setCurrent(current: number): void {
91
+ this.options.current = current
92
+ this.render()
93
+ }
94
+
95
+ destroy(): void {
96
+ this.el.remove()
97
+ }
98
+ }
99
+
100
+ export function createSteps(container: HTMLElement | string, options: StepsOptions): MkSteps {
101
+ return new MkSteps(container, options)
102
+ }
@@ -0,0 +1,28 @@
1
+ /* MotionKit UI Components - Unified Styles */
2
+ @import '../button/button.css';
3
+ @import '../input/input.css';
4
+ @import '../card/card.css';
5
+ @import '../dialog/dialog.css';
6
+ @import '../drawer/drawer.css';
7
+ @import '../message/message.css';
8
+ @import '../switch/switch.css';
9
+ @import '../loading/loading.css';
10
+ @import '../tag/tag.css';
11
+ @import '../tabs/tabs.css';
12
+ @import '../table/table.css';
13
+ @import '../avatar/avatar.css';
14
+ @import '../alert/alert.css';
15
+ @import '../progress/progress.css';
16
+ @import '../collapse/collapse.css';
17
+ @import '../empty/empty.css';
18
+ @import '../popover/popover.css';
19
+ @import '../menu/menu.css';
20
+ @import '../breadcrumb/breadcrumb.css';
21
+ @import '../steps/steps.css';
22
+ @import '../layout/row.css';
23
+ @import '../layout/space.css';
24
+ @import '../layout/divider.css';
25
+ @import '../form/select.css';
26
+ @import '../form/checkbox.css';
27
+ @import '../form/radio.css';
28
+ @import '../form/slider.css';
@@ -0,0 +1,24 @@
1
+ .mk-ui *, .mk-ui *::before, .mk-ui *::after { box-sizing: border-box; }
2
+ .mk-ui {
3
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4
+ font-size: 14px;
5
+ color: var(--mk-text-muted);
6
+ line-height: 1.6;
7
+ -webkit-font-smoothing: antialiased;
8
+ }
9
+
10
+ /* Modern glass overlay */
11
+ .mk-glass {
12
+ background: rgba(19, 19, 30, 0.75);
13
+ backdrop-filter: blur(20px) saturate(1.5);
14
+ -webkit-backdrop-filter: blur(20px) saturate(1.5);
15
+ }
16
+ [data-mk-theme="light"] .mk-glass {
17
+ background: rgba(241, 245, 249, 0.75);
18
+ }
19
+
20
+ /* Animated gradient border */
21
+ @keyframes mk-gradient-shift {
22
+ 0%, 100% { background-position: 0% 50%; }
23
+ 50% { background-position: 100% 50%; }
24
+ }
@@ -0,0 +1,248 @@
1
+ /* ==========================================================
2
+ MotionKit Design Tokens v2.0
3
+ ==========================================================
4
+ A comprehensive design system powering all MotionKit
5
+ components with consistent colors, typography, spacing,
6
+ shadows, borders, and motion.
7
+ ========================================================== */
8
+
9
+ /* ----- Core Color Scale (each color: 50-950) ----- */
10
+ :root {
11
+ /* Indigo - Primary */
12
+ --mk-indigo-50: #eef2ff;
13
+ --mk-indigo-100: #e0e7ff;
14
+ --mk-indigo-200: #c7d2fe;
15
+ --mk-indigo-300: #a5b4fc;
16
+ --mk-indigo-400: #818cf8;
17
+ --mk-indigo-500: #6366f1;
18
+ --mk-indigo-600: #4f46e5;
19
+ --mk-indigo-700: #4338ca;
20
+ --mk-indigo-800: #3730a3;
21
+ --mk-indigo-900: #312e81;
22
+ --mk-indigo-950: #1e1b4b;
23
+
24
+ /* Green - Success */
25
+ --mk-green-50: #f0fdf4;
26
+ --mk-green-100: #dcfce7;
27
+ --mk-green-200: #bbf7d0;
28
+ --mk-green-300: #86efac;
29
+ --mk-green-400: #4ade80;
30
+ --mk-green-500: #22c55e;
31
+ --mk-green-600: #16a34a;
32
+ --mk-green-700: #15803d;
33
+ --mk-green-800: #166534;
34
+ --mk-green-900: #14532d;
35
+ --mk-green-950: #052e16;
36
+
37
+ /* Amber - Warning */
38
+ --mk-amber-50: #fffbeb;
39
+ --mk-amber-100: #fef3c7;
40
+ --mk-amber-200: #fde68a;
41
+ --mk-amber-300: #fcd34d;
42
+ --mk-amber-400: #fbbf24;
43
+ --mk-amber-500: #f59e0b;
44
+ --mk-amber-600: #d97706;
45
+ --mk-amber-700: #b45309;
46
+ --mk-amber-800: #92400e;
47
+ --mk-amber-900: #78350f;
48
+ --mk-amber-950: #451a03;
49
+
50
+ /* Red - Danger */
51
+ --mk-red-50: #fef2f2;
52
+ --mk-red-100: #fee2e2;
53
+ --mk-red-200: #fecaca;
54
+ --mk-red-300: #fca5a5;
55
+ --mk-red-400: #f87171;
56
+ --mk-red-500: #ef4444;
57
+ --mk-red-600: #dc2626;
58
+ --mk-red-700: #b91c1c;
59
+ --mk-red-800: #991b1b;
60
+ --mk-red-900: #7f1d1d;
61
+ --mk-red-950: #450a0a;
62
+
63
+ /* Slate - Neutral / Gray */
64
+ --mk-slate-50: #f8fafc;
65
+ --mk-slate-100: #f1f5f9;
66
+ --mk-slate-200: #e2e8f0;
67
+ --mk-slate-300: #cbd5e1;
68
+ --mk-slate-400: #94a3b8;
69
+ --mk-slate-500: #64748b;
70
+ --mk-slate-600: #475569;
71
+ --mk-slate-700: #334155;
72
+ --mk-slate-800: #1e293b;
73
+ --mk-slate-900: #0f172a;
74
+ --mk-slate-950: #020617;
75
+
76
+ /* Sky - Info */
77
+ --mk-sky-50: #f0f9ff;
78
+ --mk-sky-100: #e0f2fe;
79
+ --mk-sky-200: #bae6fd;
80
+ --mk-sky-300: #7dd3fc;
81
+ --mk-sky-400: #38bdf8;
82
+ --mk-sky-500: #0ea5e9;
83
+ --mk-sky-600: #0284c7;
84
+ --mk-sky-700: #0369a1;
85
+ --mk-sky-800: #075985;
86
+ --mk-sky-900: #0c4a6e;
87
+ --mk-sky-950: #082f49;
88
+ }
89
+
90
+ /* ----- Semantic Tokens (dark-first) ----- */
91
+ :root {
92
+ /* Surfaces */
93
+ --mk-bg: #09090b;
94
+ --mk-bg-elevated: #0c0c14;
95
+ --mk-surface: #12121a;
96
+ --mk-surface-hover:#1a1a25;
97
+ --mk-surface-active:#22222e;
98
+ --mk-surface-raised:#1e1e2a;
99
+
100
+ /* Borders */
101
+ --mk-border: #1e1e2e;
102
+ --mk-border-hover: #27273a;
103
+ --mk-border-active: #3a3a55;
104
+ --mk-divider: rgba(255,255,255,0.06);
105
+
106
+ /* Primary */
107
+ --mk-primary: var(--mk-indigo-500);
108
+ --mk-primary-hover: var(--mk-indigo-400);
109
+ --mk-primary-active: var(--mk-indigo-600);
110
+ --mk-primary-soft: rgba(99,102,241,0.12);
111
+ --mk-primary-muted: rgba(99,102,241,0.06);
112
+
113
+ /* Semantic */
114
+ --mk-success: var(--mk-green-500);
115
+ --mk-success-soft: rgba(34,197,94,0.12);
116
+ --mk-warning: var(--mk-amber-500);
117
+ --mk-warning-soft: rgba(245,158,11,0.12);
118
+ --mk-danger: var(--mk-red-500);
119
+ --mk-danger-soft: rgba(239,68,68,0.12);
120
+ --mk-info: var(--mk-sky-500);
121
+ --mk-info-soft: rgba(14,165,233,0.12);
122
+
123
+ /* Text */
124
+ --mk-text: #f8fafc;
125
+ --mk-text-primary: #f1f5f9;
126
+ --mk-text-secondary: #94a3b8;
127
+ --mk-text-tertiary: #64748b;
128
+ --mk-text-disabled: #475569;
129
+ --mk-text-inverse: #0f172a;
130
+
131
+ /* Typography */
132
+ --mk-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
133
+ --mk-font-mono: 'SF Mono', SFMono-Regular, ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', monospace;
134
+
135
+ --mk-text-xs: 0.75rem; /* 12px */
136
+ --mk-text-sm: 0.8125rem; /* 13px */
137
+ --mk-text-base: 0.875rem; /* 14px */
138
+ --mk-text-md: 1rem; /* 16px */
139
+ --mk-text-lg: 1.125rem; /* 18px */
140
+ --mk-text-xl: 1.25rem; /* 20px */
141
+ --mk-text-2xl: 1.5rem; /* 24px */
142
+ --mk-text-3xl: 1.875rem; /* 30px */
143
+ --mk-text-4xl: 2.25rem; /* 36px */
144
+
145
+ --mk-font-normal: 400;
146
+ --mk-font-medium: 500;
147
+ --mk-font-semibold: 600;
148
+ --mk-font-bold: 700;
149
+
150
+ --mk-leading-none: 1;
151
+ --mk-leading-tight: 1.25;
152
+ --mk-leading-snug: 1.375;
153
+ --mk-leading-normal: 1.5;
154
+ --mk-leading-relaxed:1.625;
155
+
156
+ /* Spacing */
157
+ --mk-space-0: 0px;
158
+ --mk-space-1: 4px;
159
+ --mk-space-2: 8px;
160
+ --mk-space-3: 12px;
161
+ --mk-space-4: 16px;
162
+ --mk-space-5: 20px;
163
+ --mk-space-6: 24px;
164
+ --mk-space-8: 32px;
165
+ --mk-space-10: 40px;
166
+ --mk-space-12: 48px;
167
+ --mk-space-16: 64px;
168
+ --mk-space-20: 80px;
169
+ --mk-space-24: 96px;
170
+
171
+ /* Radius */
172
+ --mk-radius-none: 0px;
173
+ --mk-radius-sm: 4px;
174
+ --mk-radius: 8px;
175
+ --mk-radius-md: 10px;
176
+ --mk-radius-lg: 12px;
177
+ --mk-radius-xl: 16px;
178
+ --mk-radius-2xl: 20px;
179
+ --mk-radius-full: 9999px;
180
+
181
+ /* Shadows */
182
+ --mk-shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
183
+ --mk-shadow: 0 1px 3px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.2);
184
+ --mk-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.3), 0 2px 4px -2px rgba(0,0,0,0.2);
185
+ --mk-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.35), 0 4px 6px -4px rgba(0,0,0,0.2);
186
+ --mk-shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.4), 0 8px 10px -6px rgba(0,0,0,0.2);
187
+ --mk-shadow-glow: 0 0 20px rgba(99,102,241,0.15);
188
+
189
+ /* Motion */
190
+ --mk-duration-fast: 150ms;
191
+ --mk-duration-normal: 250ms;
192
+ --mk-duration-slow: 400ms;
193
+ --mk-duration-slower: 600ms;
194
+
195
+ --mk-ease-default: cubic-bezier(0.4, 0, 0.2, 1);
196
+ --mk-ease-in: cubic-bezier(0.4, 0, 1, 1);
197
+ --mk-ease-out: cubic-bezier(0, 0, 0.2, 1);
198
+ --mk-ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
199
+ --mk-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
200
+ --mk-ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
201
+
202
+ --mk-transition-colors: color var(--mk-duration-fast) var(--mk-ease-default), background-color var(--mk-duration-fast) var(--mk-ease-default), border-color var(--mk-duration-fast) var(--mk-ease-default);
203
+ --mk-transition-transform: transform var(--mk-duration-normal) var(--mk-ease-out-expo);
204
+ --mk-transition-shadow: box-shadow var(--mk-duration-normal) var(--mk-ease-default);
205
+ --mk-transition-opacity: opacity var(--mk-duration-fast) var(--mk-ease-default);
206
+ --mk-transition-all: all var(--mk-duration-fast) var(--mk-ease-default);
207
+
208
+ /* Z-Index Scale */
209
+ --mk-z-base: 0;
210
+ --mk-z-dropdown: 100;
211
+ --mk-z-sticky: 200;
212
+ --mk-z-fixed: 300;
213
+ --mk-z-modal-backdrop: 400;
214
+ --mk-z-modal: 500;
215
+ --mk-z-popover: 600;
216
+ --mk-z-tooltip: 700;
217
+ --mk-z-toast: 800;
218
+ --mk-z-top: 9999;
219
+ }
220
+
221
+ /* ----- Light Theme Override ----- */
222
+ [data-mk-theme="light"] {
223
+ --mk-bg: #ffffff;
224
+ --mk-bg-elevated: #f8fafc;
225
+ --mk-surface: #f1f5f9;
226
+ --mk-surface-hover:#e2e8f0;
227
+ --mk-surface-active:#cbd5e1;
228
+ --mk-surface-raised:#ffffff;
229
+
230
+ --mk-border: #e2e8f0;
231
+ --mk-border-hover: #cbd5e1;
232
+ --mk-border-active: #94a3b8;
233
+ --mk-divider: rgba(0,0,0,0.06);
234
+
235
+ --mk-text: #0f172a;
236
+ --mk-text-primary: #1e293b;
237
+ --mk-text-secondary: #475569;
238
+ --mk-text-tertiary: #94a3b8;
239
+ --mk-text-disabled: #cbd5e1;
240
+ --mk-text-inverse: #f8fafc;
241
+
242
+ --mk-shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
243
+ --mk-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
244
+ --mk-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.08), 0 2px 4px -2px rgba(0,0,0,0.04);
245
+ --mk-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.08), 0 4px 6px -4px rgba(0,0,0,0.03);
246
+ --mk-shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.08), 0 8px 10px -6px rgba(0,0,0,0.02);
247
+ --mk-shadow-glow: 0 0 20px rgba(99,102,241,0.12);
248
+ }
@@ -0,0 +1,24 @@
1
+ :root {
2
+ --mk-bg: #09090b;
3
+ --mk-surface: #12121a;
4
+ --mk-surface-hover: #1a1a25;
5
+ --mk-border: #1f1f2e;
6
+ --mk-border-hover: #2e2e42;
7
+
8
+ --mk-primary: #6366f1;
9
+ --mk-primary-hover: #818cf8;
10
+ --mk-primary-active: #4f46e5;
11
+
12
+ --mk-success: #22c55e;
13
+ --mk-warning: #f59e0b;
14
+ --mk-danger: #ef4444;
15
+
16
+ --mk-text: #f8fafc;
17
+ --mk-text-secondary: #94a3b8;
18
+ --mk-text-tertiary: #64748b;
19
+
20
+ --mk-radius: 8px;
21
+ --mk-radius-lg: 12px;
22
+
23
+ --mk-transition: 0.15s ease;
24
+ }
@@ -0,0 +1,53 @@
1
+ .mk-switch {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 10px;
5
+ cursor: pointer;
6
+ user-select: none;
7
+ }
8
+
9
+ .mk-switch__core {
10
+ position: relative;
11
+ width: 40px;
12
+ height: 22px;
13
+ background: var(--mk-surface-hover);
14
+ border: 1px solid var(--mk-border);
15
+ border-radius: 11px;
16
+ transition: var(--mk-transition);
17
+ flex-shrink: 0;
18
+ }
19
+
20
+ .mk-switch__core::after {
21
+ content: '';
22
+ position: absolute;
23
+ top: 2px;
24
+ left: 2px;
25
+ width: 16px;
26
+ height: 16px;
27
+ background: var(--mk-text-secondary);
28
+ border-radius: 50%;
29
+ transition: var(--mk-transition);
30
+ }
31
+
32
+ .mk-switch.is-checked .mk-switch__core {
33
+ background: var(--mk-primary);
34
+ border-color: var(--mk-primary);
35
+ }
36
+ .mk-switch.is-checked .mk-switch__core::after {
37
+ transform: translateX(18px);
38
+ background: #fff;
39
+ }
40
+
41
+ .mk-switch__label {
42
+ font-size: 13px;
43
+ color: var(--mk-text-secondary);
44
+ transition: color 0.2s;
45
+ }
46
+ .mk-switch.is-checked .mk-switch__label--active {
47
+ color: var(--mk-text);
48
+ }
49
+
50
+ .mk-switch.is-disabled {
51
+ opacity: 0.4;
52
+ cursor: not-allowed;
53
+ }
@@ -0,0 +1,103 @@
1
+ import './switch.css'
2
+ import { onKey, Keys } from '../../a11y/keyboard.ts'
3
+
4
+ export interface SwitchOptions {
5
+ value?: boolean
6
+ disabled?: boolean
7
+ activeText?: string
8
+ inactiveText?: string
9
+ onChange?: (value: boolean) => void
10
+ }
11
+
12
+ export class MkSwitch {
13
+ el: HTMLLabelElement
14
+ private core: HTMLDivElement
15
+ private options: SwitchOptions
16
+ private _value: boolean
17
+ private activeLabel?: HTMLSpanElement
18
+ private _cleanupKey?: () => void
19
+ private inactiveLabel?: HTMLSpanElement
20
+
21
+ constructor(container: HTMLElement | string, options: SwitchOptions = {}) {
22
+ const parent =
23
+ typeof container === 'string'
24
+ ? document.querySelector(container)!
25
+ : container
26
+
27
+ this.options = { value: false, ...options }
28
+ this._value = this.options.value!
29
+
30
+ this.el = document.createElement('label')
31
+ this.el.className = 'mk-switch'
32
+ if (this._value) this.el.classList.add('is-checked')
33
+ if (this.options.disabled) this.el.classList.add('is-disabled')
34
+ this.el.setAttribute('role', 'switch')
35
+ this.el.setAttribute('aria-checked', String(this._value))
36
+ if (!this.options.disabled) {
37
+ this.el.setAttribute('tabindex', '0')
38
+ }
39
+
40
+ if (this.options.inactiveText) {
41
+ this.inactiveLabel = document.createElement('span')
42
+ this.inactiveLabel.className = 'mk-switch__label'
43
+ this.inactiveLabel.textContent = this.options.inactiveText
44
+ if (!this._value) this.inactiveLabel.classList.add('mk-switch__label--active')
45
+ this.el.appendChild(this.inactiveLabel)
46
+ }
47
+
48
+ this.core = document.createElement('div')
49
+ this.core.className = 'mk-switch__core'
50
+ this.el.appendChild(this.core)
51
+
52
+ if (this.options.activeText) {
53
+ this.activeLabel = document.createElement('span')
54
+ this.activeLabel.className = 'mk-switch__label'
55
+ this.activeLabel.textContent = this.options.activeText
56
+ if (this._value) this.activeLabel.classList.add('mk-switch__label--active')
57
+ this.el.appendChild(this.activeLabel)
58
+ }
59
+
60
+ this.el.addEventListener('click', () => {
61
+ if (this.options.disabled) return
62
+ this.toggle()
63
+ })
64
+
65
+ this._cleanupKey = onKey(this.el, [
66
+ { key: Keys.Enter, handler: () => this.toggle() },
67
+ { key: Keys.Space, handler: () => this.toggle() },
68
+ ])
69
+
70
+ parent.appendChild(this.el)
71
+ }
72
+
73
+ get value(): boolean {
74
+ return this._value
75
+ }
76
+
77
+ set value(v: boolean) {
78
+ if (this._value !== v) {
79
+ this._value = v
80
+ this.el.classList.toggle('is-checked', v)
81
+ this.el.setAttribute('aria-checked', String(v))
82
+ this.activeLabel?.classList.toggle('mk-switch__label--active', v)
83
+ this.inactiveLabel?.classList.toggle('mk-switch__label--active', !v)
84
+ this.options.onChange?.(v)
85
+ }
86
+ }
87
+
88
+ toggle(): void {
89
+ this.value = !this._value
90
+ }
91
+
92
+ destroy(): void {
93
+ this._cleanupKey?.()
94
+ this.el.remove()
95
+ }
96
+ }
97
+
98
+ export function createSwitch(
99
+ container: HTMLElement | string,
100
+ options?: SwitchOptions
101
+ ): MkSwitch {
102
+ return new MkSwitch(container, options)
103
+ }