@m3ui-vue/m3ui-vue 0.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/LICENSE +21 -0
  2. package/README.md +102 -0
  3. package/dist/components/MAlert.vue.d.ts +27 -0
  4. package/dist/components/MAppBar.vue.d.ts +24 -0
  5. package/dist/components/MAvatar.vue.d.ts +9 -0
  6. package/dist/components/MBadge.vue.d.ts +22 -0
  7. package/dist/components/MBottomSheet.vue.d.ts +26 -0
  8. package/dist/components/MBreadcrumbs.vue.d.ts +19 -0
  9. package/dist/components/MButton.vue.d.ts +32 -0
  10. package/dist/components/MCalendar.vue.d.ts +23 -0
  11. package/dist/components/MCard.vue.d.ts +28 -0
  12. package/dist/components/MChart.vue.d.ts +13 -0
  13. package/dist/components/MCheckbox.vue.d.ts +26 -0
  14. package/dist/components/MChip.vue.d.ts +33 -0
  15. package/dist/components/MCodeEditor.vue.d.ts +35 -0
  16. package/dist/components/MColorPicker.vue.d.ts +18 -0
  17. package/dist/components/MCommandPalette.vue.d.ts +29 -0
  18. package/dist/components/MConfirmDialog.vue.d.ts +23 -0
  19. package/dist/components/MContainer.vue.d.ts +24 -0
  20. package/dist/components/MContextMenu.vue.d.ts +35 -0
  21. package/dist/components/MDataTable.vue.d.ts +83 -0
  22. package/dist/components/MDatePicker.vue.d.ts +21 -0
  23. package/dist/components/MDateRangePicker.vue.d.ts +24 -0
  24. package/dist/components/MDialog.vue.d.ts +30 -0
  25. package/dist/components/MDivider.vue.d.ts +11 -0
  26. package/dist/components/MDragDropList.vue.d.ts +40 -0
  27. package/dist/components/MEmptyState.vue.d.ts +21 -0
  28. package/dist/components/MExpansionPanel.vue.d.ts +28 -0
  29. package/dist/components/MFab.vue.d.ts +28 -0
  30. package/dist/components/MFileUpload.vue.d.ts +25 -0
  31. package/dist/components/MGrid.vue.d.ts +26 -0
  32. package/dist/components/MHotkeys.vue.d.ts +16 -0
  33. package/dist/components/MIcon.vue.d.ts +9 -0
  34. package/dist/components/MIconButton.vue.d.ts +14 -0
  35. package/dist/components/MInfiniteScroll.vue.d.ts +34 -0
  36. package/dist/components/MJsonEditor.vue.d.ts +17 -0
  37. package/dist/components/MJsonViewer.vue.d.ts +14 -0
  38. package/dist/components/MKanban.vue.d.ts +53 -0
  39. package/dist/components/MLoadingOverlay.vue.d.ts +28 -0
  40. package/dist/components/MMarkdown.vue.d.ts +11 -0
  41. package/dist/components/MMasonry.vue.d.ts +23 -0
  42. package/dist/components/MMenu.vue.d.ts +27 -0
  43. package/dist/components/MMenuItem.vue.d.ts +16 -0
  44. package/dist/components/MMultiSelect.vue.d.ts +34 -0
  45. package/dist/components/MNavigationBar.vue.d.ts +18 -0
  46. package/dist/components/MNavigationDrawer.vue.d.ts +41 -0
  47. package/dist/components/MNavigationRail.vue.d.ts +32 -0
  48. package/dist/components/MPagination.vue.d.ts +12 -0
  49. package/dist/components/MProgressBar.vue.d.ts +13 -0
  50. package/dist/components/MRadio.vue.d.ts +17 -0
  51. package/dist/components/MRadioGroup.vue.d.ts +24 -0
  52. package/dist/components/MRating.vue.d.ts +23 -0
  53. package/dist/components/MResult.vue.d.ts +20 -0
  54. package/dist/components/MRichTextEditor.vue.d.ts +17 -0
  55. package/dist/components/MScheduler.vue.d.ts +35 -0
  56. package/dist/components/MSegmentedButton.vue.d.ts +24 -0
  57. package/dist/components/MSelect.vue.d.ts +29 -0
  58. package/dist/components/MSideSheet.vue.d.ts +28 -0
  59. package/dist/components/MSkeleton.vue.d.ts +14 -0
  60. package/dist/components/MSlider.vue.d.ts +24 -0
  61. package/dist/components/MSnackbar.vue.d.ts +3 -0
  62. package/dist/components/MSpinner.vue.d.ts +10 -0
  63. package/dist/components/MSplitter.vue.d.ts +26 -0
  64. package/dist/components/MSpotlightSearch.vue.d.ts +34 -0
  65. package/dist/components/MStack.vue.d.ts +30 -0
  66. package/dist/components/MStatCard.vue.d.ts +24 -0
  67. package/dist/components/MStepper.vue.d.ts +33 -0
  68. package/dist/components/MSwitch.vue.d.ts +14 -0
  69. package/dist/components/MTable.vue.d.ts +73 -0
  70. package/dist/components/MTabs.vue.d.ts +20 -0
  71. package/dist/components/MTerminal.vue.d.ts +25 -0
  72. package/dist/components/MTextField.vue.d.ts +41 -0
  73. package/dist/components/MTimePicker.vue.d.ts +20 -0
  74. package/dist/components/MTimeline.vue.d.ts +31 -0
  75. package/dist/components/MTooltip.vue.d.ts +21 -0
  76. package/dist/components/MTopAppBar.vue.d.ts +29 -0
  77. package/dist/components/MTour.vue.d.ts +19 -0
  78. package/dist/components/MTransferList.vue.d.ts +23 -0
  79. package/dist/components/MTree.vue.d.ts +68 -0
  80. package/dist/components/MTreeTable.vue.d.ts +57 -0
  81. package/dist/components/MVirtualTable.vue.d.ts +40 -0
  82. package/dist/components/_MContextMenuPanel.vue.d.ts +13 -0
  83. package/dist/components/_MTreeNode.vue.d.ts +26 -0
  84. package/dist/composables/useColorPalette.d.ts +11 -0
  85. package/dist/composables/useFieldBg.d.ts +13 -0
  86. package/dist/composables/useTheme.d.ts +5 -0
  87. package/dist/composables/useToast.d.ts +59 -0
  88. package/dist/index.d.ts +112 -0
  89. package/dist/m3ui.css +2 -0
  90. package/dist/m3ui.js +7432 -0
  91. package/dist/m3ui.js.map +1 -0
  92. package/dist/plugin.d.ts +9 -0
  93. package/dist/styles/palettes.css +1253 -0
  94. package/dist/styles/theme.css +249 -0
  95. package/package.json +166 -0
  96. package/src/components/MAlert.vue +69 -0
  97. package/src/components/MAppBar.vue +40 -0
  98. package/src/components/MAvatar.vue +21 -0
  99. package/src/components/MBadge.vue +46 -0
  100. package/src/components/MBottomSheet.vue +113 -0
  101. package/src/components/MBreadcrumbs.vue +52 -0
  102. package/src/components/MButton.vue +111 -0
  103. package/src/components/MCalendar.vue +173 -0
  104. package/src/components/MCard.vue +56 -0
  105. package/src/components/MChart.vue +158 -0
  106. package/src/components/MCheckbox.vue +48 -0
  107. package/src/components/MChip.vue +87 -0
  108. package/src/components/MCodeEditor.vue +179 -0
  109. package/src/components/MColorPicker.vue +305 -0
  110. package/src/components/MCommandPalette.vue +213 -0
  111. package/src/components/MConfirmDialog.vue +43 -0
  112. package/src/components/MContainer.vue +36 -0
  113. package/src/components/MContextMenu.vue +66 -0
  114. package/src/components/MDataTable.vue +376 -0
  115. package/src/components/MDatePicker.vue +253 -0
  116. package/src/components/MDateRangePicker.vue +265 -0
  117. package/src/components/MDialog.vue +90 -0
  118. package/src/components/MDivider.vue +26 -0
  119. package/src/components/MDragDropList.vue +111 -0
  120. package/src/components/MEmptyState.vue +40 -0
  121. package/src/components/MExpansionPanel.vue +112 -0
  122. package/src/components/MFab.vue +220 -0
  123. package/src/components/MFileUpload.vue +206 -0
  124. package/src/components/MGrid.vue +99 -0
  125. package/src/components/MHotkeys.vue +122 -0
  126. package/src/components/MIcon.vue +9 -0
  127. package/src/components/MIconButton.vue +49 -0
  128. package/src/components/MInfiniteScroll.vue +68 -0
  129. package/src/components/MJsonEditor.vue +118 -0
  130. package/src/components/MJsonViewer.vue +106 -0
  131. package/src/components/MKanban.vue +147 -0
  132. package/src/components/MLoadingOverlay.vue +52 -0
  133. package/src/components/MMarkdown.vue +123 -0
  134. package/src/components/MMasonry.vue +87 -0
  135. package/src/components/MMenu.vue +113 -0
  136. package/src/components/MMenuItem.vue +15 -0
  137. package/src/components/MMultiSelect.vue +306 -0
  138. package/src/components/MNavigationBar.vue +62 -0
  139. package/src/components/MNavigationDrawer.vue +157 -0
  140. package/src/components/MNavigationRail.vue +80 -0
  141. package/src/components/MPagination.vue +37 -0
  142. package/src/components/MProgressBar.vue +200 -0
  143. package/src/components/MRadio.vue +89 -0
  144. package/src/components/MRadioGroup.vue +41 -0
  145. package/src/components/MRating.vue +108 -0
  146. package/src/components/MResult.vue +62 -0
  147. package/src/components/MRichTextEditor.vue +199 -0
  148. package/src/components/MScheduler.vue +225 -0
  149. package/src/components/MSegmentedButton.vue +75 -0
  150. package/src/components/MSelect.vue +259 -0
  151. package/src/components/MSideSheet.vue +112 -0
  152. package/src/components/MSkeleton.vue +60 -0
  153. package/src/components/MSlider.vue +188 -0
  154. package/src/components/MSnackbar.vue +244 -0
  155. package/src/components/MSpinner.vue +122 -0
  156. package/src/components/MSplitter.vue +97 -0
  157. package/src/components/MSpotlightSearch.vue +244 -0
  158. package/src/components/MStack.vue +67 -0
  159. package/src/components/MStatCard.vue +56 -0
  160. package/src/components/MStepper.vue +161 -0
  161. package/src/components/MSwitch.vue +63 -0
  162. package/src/components/MTable.vue +404 -0
  163. package/src/components/MTabs.vue +97 -0
  164. package/src/components/MTerminal.vue +146 -0
  165. package/src/components/MTextField.vue +180 -0
  166. package/src/components/MTimePicker.vue +227 -0
  167. package/src/components/MTimeline.vue +117 -0
  168. package/src/components/MTooltip.vue +82 -0
  169. package/src/components/MTopAppBar.vue +62 -0
  170. package/src/components/MTour.vue +226 -0
  171. package/src/components/MTransferList.vue +181 -0
  172. package/src/components/MTree.vue +164 -0
  173. package/src/components/MTreeTable.vue +159 -0
  174. package/src/components/MVirtualTable.vue +155 -0
  175. package/src/components/_MContextMenuPanel.vue +129 -0
  176. package/src/components/_MTreeNode.vue +171 -0
  177. package/src/composables/useColorPalette.ts +60 -0
  178. package/src/composables/useFieldBg.ts +91 -0
  179. package/src/composables/useTheme.ts +55 -0
  180. package/src/composables/useToast.ts +51 -0
  181. package/src/env.d.ts +1 -0
  182. package/src/index.ts +119 -0
  183. package/src/plugin.ts +18 -0
  184. package/src/styles/palettes.css +1253 -0
  185. package/src/styles/theme.css +249 -0
@@ -0,0 +1,67 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ const props = withDefaults(
5
+ defineProps<{
6
+ direction?: 'column' | 'row'
7
+ gap?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
8
+ align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline'
9
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'
10
+ wrap?: boolean
11
+ divider?: boolean
12
+ inline?: boolean
13
+ }>(),
14
+ { direction: 'column', gap: 'md', align: 'stretch', justify: 'start', wrap: false, divider: false, inline: false },
15
+ )
16
+
17
+ const gapClasses: Record<string, string> = {
18
+ none: 'gap-0',
19
+ xs: 'gap-1',
20
+ sm: 'gap-2',
21
+ md: 'gap-4',
22
+ lg: 'gap-6',
23
+ xl: 'gap-8',
24
+ }
25
+
26
+ const alignClasses: Record<string, string> = {
27
+ start: 'items-start',
28
+ center: 'items-center',
29
+ end: 'items-end',
30
+ stretch: 'items-stretch',
31
+ baseline: 'items-baseline',
32
+ }
33
+
34
+ const justifyClasses: Record<string, string> = {
35
+ start: 'justify-start',
36
+ center: 'justify-center',
37
+ end: 'justify-end',
38
+ between: 'justify-between',
39
+ around: 'justify-around',
40
+ evenly: 'justify-evenly',
41
+ }
42
+
43
+ const classes = computed(() => [
44
+ props.inline ? 'inline-flex' : 'flex',
45
+ props.direction === 'row' ? 'flex-row' : 'flex-col',
46
+ !props.divider && gapClasses[props.gap],
47
+ alignClasses[props.align],
48
+ justifyClasses[props.justify],
49
+ props.wrap && 'flex-wrap',
50
+ ])
51
+
52
+ const dividerClass = computed(() =>
53
+ props.direction === 'row' ? 'w-px self-stretch bg-outline-variant' : 'h-px w-full bg-outline-variant',
54
+ )
55
+ </script>
56
+
57
+ <template>
58
+ <div :class="classes">
59
+ <template v-if="divider">
60
+ <template v-for="(_, i) in ($slots.default?.() ?? [])" :key="i">
61
+ <div v-if="i > 0" :class="dividerClass" role="separator" />
62
+ <component :is="_" />
63
+ </template>
64
+ </template>
65
+ <slot v-else />
66
+ </div>
67
+ </template>
@@ -0,0 +1,56 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import MIcon from './MIcon.vue'
4
+
5
+ const props = withDefaults(defineProps<{
6
+ title: string
7
+ value: string | number
8
+ icon?: string
9
+ trend?: number
10
+ trendLabel?: string
11
+ color?: 'primary' | 'secondary' | 'tertiary' | 'error' | 'success'
12
+ loading?: boolean
13
+ }>(), { color: 'primary' })
14
+
15
+ const iconBg: Record<string, string> = {
16
+ primary: 'bg-primary-container text-on-primary-container',
17
+ secondary: 'bg-secondary-container text-on-secondary-container',
18
+ tertiary: 'bg-tertiary-container text-on-tertiary-container',
19
+ error: 'bg-error-container text-on-error-container',
20
+ success: 'bg-success-container text-on-success-container',
21
+ }
22
+
23
+ const trendColor = computed(() => {
24
+ if (props.trend == null) return ''
25
+ return props.trend > 0 ? 'text-success' : props.trend < 0 ? 'text-error' : 'text-on-surface-variant'
26
+ })
27
+ const trendIcon = computed(() => {
28
+ if (props.trend == null) return ''
29
+ return props.trend > 0 ? 'trending_up' : props.trend < 0 ? 'trending_down' : 'trending_flat'
30
+ })
31
+ </script>
32
+
33
+ <template>
34
+ <div class="flex flex-col gap-3 rounded-lg border border-outline-variant bg-surface-container-lowest p-5">
35
+ <div class="flex items-start justify-between">
36
+ <div class="flex flex-col gap-1">
37
+ <span class="text-label-large text-on-surface-variant">{{ title }}</span>
38
+ <template v-if="loading">
39
+ <div class="h-8 w-24 animate-pulse rounded-md bg-on-surface/10" />
40
+ </template>
41
+ <span v-else class="text-headline-medium font-medium text-on-surface">{{ value }}</span>
42
+ </div>
43
+ <div v-if="icon" class="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl" :class="iconBg[color]">
44
+ <MIcon :name="icon" :size="24" />
45
+ </div>
46
+ </div>
47
+ <div v-if="trend != null || trendLabel || $slots.footer" class="flex items-center gap-2">
48
+ <span v-if="trend != null" class="inline-flex items-center gap-0.5 text-label-medium font-medium" :class="trendColor">
49
+ <MIcon :name="trendIcon" :size="16" />
50
+ {{ trend > 0 ? '+' : '' }}{{ trend }}%
51
+ </span>
52
+ <span v-if="trendLabel" class="text-label-medium text-on-surface-variant">{{ trendLabel }}</span>
53
+ <slot name="footer" />
54
+ </div>
55
+ </div>
56
+ </template>
@@ -0,0 +1,161 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import MIcon from './MIcon.vue'
4
+
5
+ export interface StepItem {
6
+ label: string
7
+ description?: string
8
+ icon?: string
9
+ optional?: boolean
10
+ error?: boolean
11
+ }
12
+
13
+ const props = withDefaults(defineProps<{
14
+ steps: StepItem[]
15
+ modelValue: number
16
+ direction?: 'horizontal' | 'vertical'
17
+ linear?: boolean
18
+ }>(), { direction: 'horizontal', linear: true })
19
+
20
+ const emit = defineEmits<{ 'update:modelValue': [number] }>()
21
+
22
+ function stepState(index: number) {
23
+ if (props.steps[index]?.error) return 'error'
24
+ if (index < props.modelValue) return 'completed'
25
+ if (index === props.modelValue) return 'active'
26
+ return 'inactive'
27
+ }
28
+
29
+ const canClick = computed(() => !props.linear)
30
+
31
+ function select(index: number) {
32
+ if (canClick.value) emit('update:modelValue', index)
33
+ }
34
+ </script>
35
+
36
+ <template>
37
+ <!-- Horizontal -->
38
+ <div
39
+ v-if="direction === 'horizontal'"
40
+ class="flex w-full items-start"
41
+ >
42
+ <template v-for="(step, i) in steps" :key="i">
43
+ <!-- Step -->
44
+ <div
45
+ class="flex flex-col items-center gap-2"
46
+ :class="canClick && stepState(i) !== 'active' ? 'cursor-pointer' : ''"
47
+ @click="select(i)"
48
+ >
49
+ <!-- Circle -->
50
+ <div
51
+ class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-label-large font-medium transition-colors duration-200"
52
+ :class="{
53
+ 'bg-primary text-on-primary': stepState(i) === 'active' || stepState(i) === 'completed',
54
+ 'bg-error text-on-error': stepState(i) === 'error',
55
+ 'bg-surface-container-highest text-on-surface-variant': stepState(i) === 'inactive',
56
+ }"
57
+ >
58
+ <MIcon v-if="stepState(i) === 'completed'" name="check" :size="20" />
59
+ <MIcon v-else-if="stepState(i) === 'error'" name="priority_high" :size="20" />
60
+ <MIcon v-else-if="step.icon" :name="step.icon" :size="20" />
61
+ <span v-else>{{ i + 1 }}</span>
62
+ </div>
63
+
64
+ <!-- Label -->
65
+ <div class="flex flex-col items-center text-center">
66
+ <span
67
+ class="text-label-large"
68
+ :class="{
69
+ 'font-medium text-on-surface': stepState(i) === 'active' || stepState(i) === 'completed',
70
+ 'text-error': stepState(i) === 'error',
71
+ 'text-on-surface-variant': stepState(i) === 'inactive',
72
+ }"
73
+ >
74
+ {{ step.label }}
75
+ </span>
76
+ <span v-if="step.description" class="text-body-small text-on-surface-variant">
77
+ {{ step.description }}
78
+ </span>
79
+ <span v-if="step.optional" class="text-body-small text-on-surface-variant">
80
+ Opcional
81
+ </span>
82
+ </div>
83
+ </div>
84
+
85
+ <!-- Connector -->
86
+ <div
87
+ v-if="i < steps.length - 1"
88
+ class="mt-[18px] flex flex-1 items-center px-2"
89
+ >
90
+ <div
91
+ class="h-[1px] w-full transition-colors duration-300"
92
+ :class="i < modelValue ? 'bg-primary' : 'bg-outline-variant'"
93
+ />
94
+ </div>
95
+ </template>
96
+ </div>
97
+
98
+ <!-- Vertical -->
99
+ <div v-else class="flex flex-col">
100
+ <template v-for="(step, i) in steps" :key="i">
101
+ <div class="flex gap-4">
102
+ <!-- Left column: circle + connector -->
103
+ <div class="flex flex-col items-center">
104
+ <div
105
+ class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-label-large font-medium transition-colors duration-200"
106
+ :class="[
107
+ {
108
+ 'bg-primary text-on-primary': stepState(i) === 'active' || stepState(i) === 'completed',
109
+ 'bg-error text-on-error': stepState(i) === 'error',
110
+ 'bg-surface-container-highest text-on-surface-variant': stepState(i) === 'inactive',
111
+ },
112
+ canClick && stepState(i) !== 'active' ? 'cursor-pointer' : '',
113
+ ]"
114
+ @click="select(i)"
115
+ >
116
+ <MIcon v-if="stepState(i) === 'completed'" name="check" :size="20" />
117
+ <MIcon v-else-if="stepState(i) === 'error'" name="priority_high" :size="20" />
118
+ <MIcon v-else-if="step.icon" :name="step.icon" :size="20" />
119
+ <span v-else>{{ i + 1 }}</span>
120
+ </div>
121
+ <!-- Vertical connector -->
122
+ <div
123
+ v-if="i < steps.length - 1"
124
+ class="my-1 w-[1px] flex-1 transition-colors duration-300"
125
+ :class="i < modelValue ? 'bg-primary' : 'bg-outline-variant'"
126
+ style="min-height: 24px"
127
+ />
128
+ </div>
129
+
130
+ <!-- Right column: label + content -->
131
+ <div
132
+ class="pb-6"
133
+ :class="canClick && stepState(i) !== 'active' ? 'cursor-pointer' : ''"
134
+ @click="select(i)"
135
+ >
136
+ <span
137
+ class="text-label-large"
138
+ :class="{
139
+ 'font-medium text-on-surface': stepState(i) === 'active' || stepState(i) === 'completed',
140
+ 'text-error': stepState(i) === 'error',
141
+ 'text-on-surface-variant': stepState(i) === 'inactive',
142
+ }"
143
+ >
144
+ {{ step.label }}
145
+ </span>
146
+ <p v-if="step.description" class="mt-0.5 text-body-small text-on-surface-variant">
147
+ {{ step.description }}
148
+ </p>
149
+ <p v-if="step.optional" class="text-body-small text-on-surface-variant">
150
+ Opcional
151
+ </p>
152
+
153
+ <!-- Slot for step content when active -->
154
+ <div v-if="stepState(i) === 'active' && $slots[`step-${i}`]" class="mt-3">
155
+ <slot :name="`step-${i}`" />
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </template>
160
+ </div>
161
+ </template>
@@ -0,0 +1,63 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import MIcon from './MIcon.vue'
4
+
5
+ const props = withDefaults(
6
+ defineProps<{ modelValue: boolean; disabled?: boolean; label?: string }>(),
7
+ { disabled: false },
8
+ )
9
+
10
+ const emit = defineEmits<{ 'update:modelValue': [boolean] }>()
11
+
12
+ // All thumb transforms live here so each direction can use its own easing curve.
13
+ // translateY(-50%) vertically centres the 24px thumb in the 32px track.
14
+ // ON → spring cubic-bezier: overshoots ~2px then settles → satisfying "click"
15
+ // OFF → M3 standard decelerate: clean snap back, no undershoot
16
+ const thumbStyle = computed(() => ({
17
+ transform: props.modelValue
18
+ ? 'translateY(-50%) translateX(18px) scale(1)'
19
+ : 'translateY(-50%) translateX(0px) scale(0.667)',
20
+ transition: props.modelValue
21
+ ? 'transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1), background-color 280ms ease'
22
+ : 'transform 240ms cubic-bezier(0.2, 0, 0, 1), background-color 240ms ease',
23
+ }))
24
+ </script>
25
+
26
+ <template>
27
+ <label
28
+ class="inline-flex items-center gap-3 select-none"
29
+ :class="disabled ? 'cursor-not-allowed opacity-[0.38]' : 'cursor-pointer'"
30
+ >
31
+ <span
32
+ class="relative inline-flex h-8 w-[52px] shrink-0 items-center rounded-full border-2 transition-colors duration-200"
33
+ :class="modelValue ? 'border-primary bg-primary' : 'border-outline bg-surface-container-highest'"
34
+ >
35
+ <input
36
+ type="checkbox"
37
+ class="sr-only"
38
+ :checked="modelValue"
39
+ :disabled="disabled"
40
+ @change="emit('update:modelValue', !modelValue)"
41
+ />
42
+
43
+ <!-- Thumb: position + size animated via inline style (allows per-direction easing) -->
44
+ <span
45
+ class="absolute left-1 top-1/2 flex h-6 w-6 items-center justify-center rounded-full will-change-transform"
46
+ :class="modelValue ? 'bg-on-primary shadow-sm' : 'bg-outline'"
47
+ :style="thumbStyle"
48
+ >
49
+ <Transition
50
+ enter-active-class="transition-opacity duration-150 delay-[120ms]"
51
+ enter-from-class="opacity-0"
52
+ enter-to-class="opacity-100"
53
+ leave-active-class="transition-opacity duration-75"
54
+ leave-from-class="opacity-100"
55
+ leave-to-class="opacity-0"
56
+ >
57
+ <MIcon v-if="modelValue" name="check" :size="14" class="text-primary" />
58
+ </Transition>
59
+ </span>
60
+ </span>
61
+ <span v-if="label" class="text-body-large text-on-surface">{{ label }}</span>
62
+ </label>
63
+ </template>