@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.
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/components/MAlert.vue.d.ts +27 -0
- package/dist/components/MAppBar.vue.d.ts +24 -0
- package/dist/components/MAvatar.vue.d.ts +9 -0
- package/dist/components/MBadge.vue.d.ts +22 -0
- package/dist/components/MBottomSheet.vue.d.ts +26 -0
- package/dist/components/MBreadcrumbs.vue.d.ts +19 -0
- package/dist/components/MButton.vue.d.ts +32 -0
- package/dist/components/MCalendar.vue.d.ts +23 -0
- package/dist/components/MCard.vue.d.ts +28 -0
- package/dist/components/MChart.vue.d.ts +13 -0
- package/dist/components/MCheckbox.vue.d.ts +26 -0
- package/dist/components/MChip.vue.d.ts +33 -0
- package/dist/components/MCodeEditor.vue.d.ts +35 -0
- package/dist/components/MColorPicker.vue.d.ts +18 -0
- package/dist/components/MCommandPalette.vue.d.ts +29 -0
- package/dist/components/MConfirmDialog.vue.d.ts +23 -0
- package/dist/components/MContainer.vue.d.ts +24 -0
- package/dist/components/MContextMenu.vue.d.ts +35 -0
- package/dist/components/MDataTable.vue.d.ts +83 -0
- package/dist/components/MDatePicker.vue.d.ts +21 -0
- package/dist/components/MDateRangePicker.vue.d.ts +24 -0
- package/dist/components/MDialog.vue.d.ts +30 -0
- package/dist/components/MDivider.vue.d.ts +11 -0
- package/dist/components/MDragDropList.vue.d.ts +40 -0
- package/dist/components/MEmptyState.vue.d.ts +21 -0
- package/dist/components/MExpansionPanel.vue.d.ts +28 -0
- package/dist/components/MFab.vue.d.ts +28 -0
- package/dist/components/MFileUpload.vue.d.ts +25 -0
- package/dist/components/MGrid.vue.d.ts +26 -0
- package/dist/components/MHotkeys.vue.d.ts +16 -0
- package/dist/components/MIcon.vue.d.ts +9 -0
- package/dist/components/MIconButton.vue.d.ts +14 -0
- package/dist/components/MInfiniteScroll.vue.d.ts +34 -0
- package/dist/components/MJsonEditor.vue.d.ts +17 -0
- package/dist/components/MJsonViewer.vue.d.ts +14 -0
- package/dist/components/MKanban.vue.d.ts +53 -0
- package/dist/components/MLoadingOverlay.vue.d.ts +28 -0
- package/dist/components/MMarkdown.vue.d.ts +11 -0
- package/dist/components/MMasonry.vue.d.ts +23 -0
- package/dist/components/MMenu.vue.d.ts +27 -0
- package/dist/components/MMenuItem.vue.d.ts +16 -0
- package/dist/components/MMultiSelect.vue.d.ts +34 -0
- package/dist/components/MNavigationBar.vue.d.ts +18 -0
- package/dist/components/MNavigationDrawer.vue.d.ts +41 -0
- package/dist/components/MNavigationRail.vue.d.ts +32 -0
- package/dist/components/MPagination.vue.d.ts +12 -0
- package/dist/components/MProgressBar.vue.d.ts +13 -0
- package/dist/components/MRadio.vue.d.ts +17 -0
- package/dist/components/MRadioGroup.vue.d.ts +24 -0
- package/dist/components/MRating.vue.d.ts +23 -0
- package/dist/components/MResult.vue.d.ts +20 -0
- package/dist/components/MRichTextEditor.vue.d.ts +17 -0
- package/dist/components/MScheduler.vue.d.ts +35 -0
- package/dist/components/MSegmentedButton.vue.d.ts +24 -0
- package/dist/components/MSelect.vue.d.ts +29 -0
- package/dist/components/MSideSheet.vue.d.ts +28 -0
- package/dist/components/MSkeleton.vue.d.ts +14 -0
- package/dist/components/MSlider.vue.d.ts +24 -0
- package/dist/components/MSnackbar.vue.d.ts +3 -0
- package/dist/components/MSpinner.vue.d.ts +10 -0
- package/dist/components/MSplitter.vue.d.ts +26 -0
- package/dist/components/MSpotlightSearch.vue.d.ts +34 -0
- package/dist/components/MStack.vue.d.ts +30 -0
- package/dist/components/MStatCard.vue.d.ts +24 -0
- package/dist/components/MStepper.vue.d.ts +33 -0
- package/dist/components/MSwitch.vue.d.ts +14 -0
- package/dist/components/MTable.vue.d.ts +73 -0
- package/dist/components/MTabs.vue.d.ts +20 -0
- package/dist/components/MTerminal.vue.d.ts +25 -0
- package/dist/components/MTextField.vue.d.ts +41 -0
- package/dist/components/MTimePicker.vue.d.ts +20 -0
- package/dist/components/MTimeline.vue.d.ts +31 -0
- package/dist/components/MTooltip.vue.d.ts +21 -0
- package/dist/components/MTopAppBar.vue.d.ts +29 -0
- package/dist/components/MTour.vue.d.ts +19 -0
- package/dist/components/MTransferList.vue.d.ts +23 -0
- package/dist/components/MTree.vue.d.ts +68 -0
- package/dist/components/MTreeTable.vue.d.ts +57 -0
- package/dist/components/MVirtualTable.vue.d.ts +40 -0
- package/dist/components/_MContextMenuPanel.vue.d.ts +13 -0
- package/dist/components/_MTreeNode.vue.d.ts +26 -0
- package/dist/composables/useColorPalette.d.ts +11 -0
- package/dist/composables/useFieldBg.d.ts +13 -0
- package/dist/composables/useTheme.d.ts +5 -0
- package/dist/composables/useToast.d.ts +59 -0
- package/dist/index.d.ts +112 -0
- package/dist/m3ui.css +2 -0
- package/dist/m3ui.js +7432 -0
- package/dist/m3ui.js.map +1 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/styles/palettes.css +1253 -0
- package/dist/styles/theme.css +249 -0
- package/package.json +166 -0
- package/src/components/MAlert.vue +69 -0
- package/src/components/MAppBar.vue +40 -0
- package/src/components/MAvatar.vue +21 -0
- package/src/components/MBadge.vue +46 -0
- package/src/components/MBottomSheet.vue +113 -0
- package/src/components/MBreadcrumbs.vue +52 -0
- package/src/components/MButton.vue +111 -0
- package/src/components/MCalendar.vue +173 -0
- package/src/components/MCard.vue +56 -0
- package/src/components/MChart.vue +158 -0
- package/src/components/MCheckbox.vue +48 -0
- package/src/components/MChip.vue +87 -0
- package/src/components/MCodeEditor.vue +179 -0
- package/src/components/MColorPicker.vue +305 -0
- package/src/components/MCommandPalette.vue +213 -0
- package/src/components/MConfirmDialog.vue +43 -0
- package/src/components/MContainer.vue +36 -0
- package/src/components/MContextMenu.vue +66 -0
- package/src/components/MDataTable.vue +376 -0
- package/src/components/MDatePicker.vue +253 -0
- package/src/components/MDateRangePicker.vue +265 -0
- package/src/components/MDialog.vue +90 -0
- package/src/components/MDivider.vue +26 -0
- package/src/components/MDragDropList.vue +111 -0
- package/src/components/MEmptyState.vue +40 -0
- package/src/components/MExpansionPanel.vue +112 -0
- package/src/components/MFab.vue +220 -0
- package/src/components/MFileUpload.vue +206 -0
- package/src/components/MGrid.vue +99 -0
- package/src/components/MHotkeys.vue +122 -0
- package/src/components/MIcon.vue +9 -0
- package/src/components/MIconButton.vue +49 -0
- package/src/components/MInfiniteScroll.vue +68 -0
- package/src/components/MJsonEditor.vue +118 -0
- package/src/components/MJsonViewer.vue +106 -0
- package/src/components/MKanban.vue +147 -0
- package/src/components/MLoadingOverlay.vue +52 -0
- package/src/components/MMarkdown.vue +123 -0
- package/src/components/MMasonry.vue +87 -0
- package/src/components/MMenu.vue +113 -0
- package/src/components/MMenuItem.vue +15 -0
- package/src/components/MMultiSelect.vue +306 -0
- package/src/components/MNavigationBar.vue +62 -0
- package/src/components/MNavigationDrawer.vue +157 -0
- package/src/components/MNavigationRail.vue +80 -0
- package/src/components/MPagination.vue +37 -0
- package/src/components/MProgressBar.vue +200 -0
- package/src/components/MRadio.vue +89 -0
- package/src/components/MRadioGroup.vue +41 -0
- package/src/components/MRating.vue +108 -0
- package/src/components/MResult.vue +62 -0
- package/src/components/MRichTextEditor.vue +199 -0
- package/src/components/MScheduler.vue +225 -0
- package/src/components/MSegmentedButton.vue +75 -0
- package/src/components/MSelect.vue +259 -0
- package/src/components/MSideSheet.vue +112 -0
- package/src/components/MSkeleton.vue +60 -0
- package/src/components/MSlider.vue +188 -0
- package/src/components/MSnackbar.vue +244 -0
- package/src/components/MSpinner.vue +122 -0
- package/src/components/MSplitter.vue +97 -0
- package/src/components/MSpotlightSearch.vue +244 -0
- package/src/components/MStack.vue +67 -0
- package/src/components/MStatCard.vue +56 -0
- package/src/components/MStepper.vue +161 -0
- package/src/components/MSwitch.vue +63 -0
- package/src/components/MTable.vue +404 -0
- package/src/components/MTabs.vue +97 -0
- package/src/components/MTerminal.vue +146 -0
- package/src/components/MTextField.vue +180 -0
- package/src/components/MTimePicker.vue +227 -0
- package/src/components/MTimeline.vue +117 -0
- package/src/components/MTooltip.vue +82 -0
- package/src/components/MTopAppBar.vue +62 -0
- package/src/components/MTour.vue +226 -0
- package/src/components/MTransferList.vue +181 -0
- package/src/components/MTree.vue +164 -0
- package/src/components/MTreeTable.vue +159 -0
- package/src/components/MVirtualTable.vue +155 -0
- package/src/components/_MContextMenuPanel.vue +129 -0
- package/src/components/_MTreeNode.vue +171 -0
- package/src/composables/useColorPalette.ts +60 -0
- package/src/composables/useFieldBg.ts +91 -0
- package/src/composables/useTheme.ts +55 -0
- package/src/composables/useToast.ts +51 -0
- package/src/env.d.ts +1 -0
- package/src/index.ts +119 -0
- package/src/plugin.ts +18 -0
- package/src/styles/palettes.css +1253 -0
- 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>
|