windly-ui 1.0.1 → 1.0.2
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 +57 -33
- package/package.json +47 -15
- package/ui-library/dist/module.d.mts +3 -0
- package/ui-library/dist/module.json +8 -0
- package/ui-library/dist/module.mjs +22 -0
- package/ui-library/dist/types.d.mts +7 -0
- package/ui-library/components/Accordion.vue +0 -118
- package/ui-library/components/Avatar.vue +0 -121
- package/ui-library/components/Badge.vue +0 -116
- package/ui-library/components/Breadcrumbs.vue +0 -138
- package/ui-library/components/Button.vue +0 -154
- package/ui-library/components/Card.vue +0 -84
- package/ui-library/components/Checkbox.vue +0 -148
- package/ui-library/components/CodeBlock.vue +0 -99
- package/ui-library/components/ColorPicker.vue +0 -302
- package/ui-library/components/Date.vue +0 -240
- package/ui-library/components/Dialog.vue +0 -187
- package/ui-library/components/Divider.vue +0 -78
- package/ui-library/components/Drawer.vue +0 -67
- package/ui-library/components/Dropdown.vue +0 -248
- package/ui-library/components/FileUploader.vue +0 -330
- package/ui-library/components/Icon.vue +0 -82
- package/ui-library/components/Image.vue +0 -78
- package/ui-library/components/Input.vue +0 -531
- package/ui-library/components/Radio.vue +0 -356
- package/ui-library/components/ScrollArea.vue +0 -43
- package/ui-library/components/Select.vue +0 -309
- package/ui-library/components/Stepper.vue +0 -361
- package/ui-library/components/TabPanel.vue +0 -25
- package/ui-library/components/Table.vue +0 -152
- package/ui-library/components/Tabs.vue +0 -71
- package/ui-library/components/Textarea.vue +0 -233
- package/ui-library/components/Toggle.vue +0 -319
- package/ui-library/components/Tooltip.vue +0 -200
- package/ui-library/components/plugin.ts +0 -8
- package/ui-library/components/uiProps.ts +0 -11
- package/ui-library/components/useUiClasses.ts +0 -159
- package/ui-library/module.ts +0 -20
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import { RouterLink, useRoute } from 'vue-router'
|
|
4
|
-
|
|
5
|
-
type BreadcrumbItem = {
|
|
6
|
-
label: string
|
|
7
|
-
icon?: string
|
|
8
|
-
caption?: string
|
|
9
|
-
to?: string | object
|
|
10
|
-
href?: string
|
|
11
|
-
target?: string
|
|
12
|
-
tag?: string
|
|
13
|
-
exact?: boolean
|
|
14
|
-
replace?: boolean
|
|
15
|
-
activeClass?: string
|
|
16
|
-
exactActiveClass?: string
|
|
17
|
-
disable?: boolean
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const props = defineProps({
|
|
21
|
-
items: {
|
|
22
|
-
type: Array as () => BreadcrumbItem[],
|
|
23
|
-
required: true,
|
|
24
|
-
},
|
|
25
|
-
separator: {
|
|
26
|
-
type: String,
|
|
27
|
-
default: '/',
|
|
28
|
-
},
|
|
29
|
-
gutter: {
|
|
30
|
-
type: String,
|
|
31
|
-
default: '0.5rem',
|
|
32
|
-
},
|
|
33
|
-
align: {
|
|
34
|
-
type: String,
|
|
35
|
-
default: 'start', // start | center | end
|
|
36
|
-
},
|
|
37
|
-
activeColor: {
|
|
38
|
-
type: String,
|
|
39
|
-
default: 'primary',
|
|
40
|
-
},
|
|
41
|
-
separatorColor: {
|
|
42
|
-
type: String,
|
|
43
|
-
default: 'grey-6',
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const route = useRoute()
|
|
48
|
-
|
|
49
|
-
const alignmentClass = computed(() => {
|
|
50
|
-
switch (props.align) {
|
|
51
|
-
case 'center':
|
|
52
|
-
return 'justify-center'
|
|
53
|
-
case 'end':
|
|
54
|
-
return 'justify-end'
|
|
55
|
-
default:
|
|
56
|
-
return 'justify-start'
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
function resolveTag(item: BreadcrumbItem) {
|
|
61
|
-
if (item.disable) return item.tag || 'span'
|
|
62
|
-
if (item.href) return 'a'
|
|
63
|
-
if (item.to) return RouterLink
|
|
64
|
-
return item.tag || 'span'
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function resolveProps(item: BreadcrumbItem) {
|
|
68
|
-
if (item.href) {
|
|
69
|
-
return {
|
|
70
|
-
href: item.href,
|
|
71
|
-
target: item.target,
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (item.to) {
|
|
76
|
-
return {
|
|
77
|
-
to: item.to,
|
|
78
|
-
exact: item.exact,
|
|
79
|
-
replace: item.replace,
|
|
80
|
-
activeClass: item.activeClass,
|
|
81
|
-
exactActiveClass: item.exactActiveClass,
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return {}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function isActive(item: BreadcrumbItem) {
|
|
89
|
-
if (!item.to) return false
|
|
90
|
-
return route.path === item.to
|
|
91
|
-
}
|
|
92
|
-
</script>
|
|
93
|
-
|
|
94
|
-
<template>
|
|
95
|
-
<nav
|
|
96
|
-
class="flex items-center"
|
|
97
|
-
:class="alignmentClass"
|
|
98
|
-
aria-label="Breadcrumbs"
|
|
99
|
-
>
|
|
100
|
-
<template v-for="(item, index) in items" :key="index">
|
|
101
|
-
<!-- Item -->
|
|
102
|
-
<component
|
|
103
|
-
:is="resolveTag(item)"
|
|
104
|
-
v-bind="resolveProps(item)"
|
|
105
|
-
class="flex items-center gap-1"
|
|
106
|
-
:class="[
|
|
107
|
-
item.disable && 'opacity-50 pointer-events-none',
|
|
108
|
-
isActive(item) && `text-${activeColor}`,
|
|
109
|
-
]"
|
|
110
|
-
>
|
|
111
|
-
<span v-if="item.icon && item.icon !== 'none'" class="material-icons">
|
|
112
|
-
{{ item.icon }}
|
|
113
|
-
</span>
|
|
114
|
-
|
|
115
|
-
<span>{{ item.label }}</span>
|
|
116
|
-
|
|
117
|
-
<small
|
|
118
|
-
v-if="item.caption"
|
|
119
|
-
class="block text-xs opacity-70"
|
|
120
|
-
>
|
|
121
|
-
{{ item.caption }}
|
|
122
|
-
</small>
|
|
123
|
-
</component>
|
|
124
|
-
|
|
125
|
-
<!-- Separator -->
|
|
126
|
-
<span
|
|
127
|
-
v-if="index < items.length - 1"
|
|
128
|
-
class="select-none"
|
|
129
|
-
:style="{ marginInline: gutter }"
|
|
130
|
-
:class="`text-${separatorColor}`"
|
|
131
|
-
>
|
|
132
|
-
<slot name="separator">
|
|
133
|
-
{{ separator }}
|
|
134
|
-
</slot>
|
|
135
|
-
</span>
|
|
136
|
-
</template>
|
|
137
|
-
</nav>
|
|
138
|
-
</template>
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineOptions({
|
|
3
|
-
inheritAttrs: false,
|
|
4
|
-
})
|
|
5
|
-
|
|
6
|
-
import { computed } from 'vue'
|
|
7
|
-
import { useUiClasses } from './useUiClasses'
|
|
8
|
-
import { uiProps } from "./uiProps";
|
|
9
|
-
|
|
10
|
-
// Props
|
|
11
|
-
const props = defineProps({
|
|
12
|
-
label: { type: String, default: null },
|
|
13
|
-
to: { type: [String, Object], default: null },
|
|
14
|
-
outline: { type: Boolean, default: false },
|
|
15
|
-
flat: { type: Boolean, default: false },
|
|
16
|
-
unelevated: { type: Boolean, default: false },
|
|
17
|
-
dense: { type: Boolean, default: false },
|
|
18
|
-
stacked: { type: Boolean, default: false },
|
|
19
|
-
gradient: { type: Boolean, default: false },
|
|
20
|
-
color2: { type: String, default: '' },
|
|
21
|
-
icon: { type: String, default: null },
|
|
22
|
-
iconColor: { type: String, default: null },
|
|
23
|
-
iconPosition: { type: String, default: 'start' },
|
|
24
|
-
...uiProps
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
// Use composable
|
|
28
|
-
const {
|
|
29
|
-
// props
|
|
30
|
-
contentClass,
|
|
31
|
-
contentStyle,
|
|
32
|
-
textColor,
|
|
33
|
-
disable,
|
|
34
|
-
loading,
|
|
35
|
-
color,
|
|
36
|
-
rounded,
|
|
37
|
-
size,
|
|
38
|
-
iconSize,
|
|
39
|
-
// class maps
|
|
40
|
-
roundedClass,
|
|
41
|
-
sizeClass,
|
|
42
|
-
iconSizeClasses,
|
|
43
|
-
// computed
|
|
44
|
-
colorClass,
|
|
45
|
-
computedContentClass,
|
|
46
|
-
isColorDark,
|
|
47
|
-
isHex,
|
|
48
|
-
isTailwindColor,
|
|
49
|
-
shadeTailwind,
|
|
50
|
-
shadeHex
|
|
51
|
-
} = useUiClasses(props)
|
|
52
|
-
|
|
53
|
-
const paddingClass = {
|
|
54
|
-
default: {
|
|
55
|
-
sm: "py-1 px-3 min-h-8 h-auto w-auto" + (props.label ? " min-w-16" : ""),
|
|
56
|
-
md: "py-2 px-6 min-h-10 h-auto w-auto" + (props.label ? " min-w-28":""),
|
|
57
|
-
lg: "py-3 px-8 min-h-12 h-auto w-auto" + (props.label ? " min-w-36":""),
|
|
58
|
-
},
|
|
59
|
-
dense: {
|
|
60
|
-
sm: "py-0.5 px-2 min-h-6 h-auto w-auto" + (props.label ? " min-w-16":""),
|
|
61
|
-
md: "py-1 px-4 min-h-8 h-auto w-auto" + (props.label ? " min-w-24":""),
|
|
62
|
-
lg: "py-1.5 px-6 min-h-10 h-auto w-auto" + (props.label ? " min-w-32":""),
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const computedClass = computed(() => {
|
|
67
|
-
let cls = `transition-all duration-300 text-nowrap ${roundedClass[(rounded.value as keyof typeof roundedClass)]} ${sizeClass[(size.value as keyof typeof sizeClass)]}`;
|
|
68
|
-
|
|
69
|
-
if (props.outline)
|
|
70
|
-
cls += ` border bg-white border-${colorClass.value} text-${colorClass.value} hover:bg-${colorClass.value} hover:text-white`;
|
|
71
|
-
|
|
72
|
-
if (props.flat)
|
|
73
|
-
cls += ` bg-transparent text-${colorClass.value} hover:bg-${colorClass.value} hover:bg-opacity-10 shadow-none`;
|
|
74
|
-
|
|
75
|
-
cls += props.dense ? ` ${paddingClass.dense[size.value as keyof typeof paddingClass.dense]}` : ` ${paddingClass.default[size.value as keyof typeof paddingClass.default]}`;
|
|
76
|
-
cls += props.unelevated ? " no-shadow" : " shadow-md";
|
|
77
|
-
cls += props.stacked ? " flex flex-col items-center justify-center gap-2" : " flex items-center justify-center gap-2";
|
|
78
|
-
cls += disable.value ? " opacity-50 cursor-not-allowed" : " cursor-pointer";
|
|
79
|
-
cls += loading.value ? " opacity-75 cursor-progress" : "";
|
|
80
|
-
cls += props.gradient
|
|
81
|
-
? ` bg-gradient-to-br from-${color.value} to-${computedColor2.value} hover:bg-gradient-to-tl text-${computedTextColor.value}`
|
|
82
|
-
: !props.flat && !props.outline
|
|
83
|
-
? ` bg-${color.value} hover:bg-${computedColor2.value} text-${computedTextColor.value}`
|
|
84
|
-
: "";
|
|
85
|
-
|
|
86
|
-
return cls;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const computedIconClass = computed(() => {
|
|
90
|
-
return `material-icons ${iconSizeClasses[props.iconPosition === 'start' ? 'lg' : 'md']} ${props.iconColor ? `text-${props.iconColor}` : ''}`
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
// Computed props for v-bind
|
|
94
|
-
const computedProps = computed(() => ({
|
|
95
|
-
class: computedClass.value,
|
|
96
|
-
disabled: disable.value || loading.value
|
|
97
|
-
}))
|
|
98
|
-
|
|
99
|
-
const computedTextColor = computed(() => {
|
|
100
|
-
if (textColor.value) {
|
|
101
|
-
return textColor.value;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (props.outline || props.flat) {
|
|
105
|
-
return color.value;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const darkBg = isColorDark(color.value);
|
|
109
|
-
return darkBg ? "white" : "gray-900"
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const computedColor2 = computed(() => {
|
|
113
|
-
if (props.color2) return props.color2
|
|
114
|
-
|
|
115
|
-
if (isTailwindColor(color.value)) {
|
|
116
|
-
return shadeTailwind(color.value, 100)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (isHex(color.value)) {
|
|
120
|
-
return shadeHex(color.value, -20)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return color.value
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
// Click handler
|
|
127
|
-
function onClick() {
|
|
128
|
-
if (!loading.value && !disable.value && !props.to) {
|
|
129
|
-
emit('click')
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Emit function for script setup
|
|
134
|
-
const emit = defineEmits(['click'])
|
|
135
|
-
</script>
|
|
136
|
-
|
|
137
|
-
<template>
|
|
138
|
-
<!-- Router Link if "to" prop is provided -->
|
|
139
|
-
<router-link v-if="to" v-bind="computedProps" :to="to">
|
|
140
|
-
<!-- Icon (start position) -->
|
|
141
|
-
<i v-if="icon && iconPosition === 'start'" :class="computedIconClass">{{ icon }}</i>
|
|
142
|
-
<!-- Label -->
|
|
143
|
-
<span v-if="label" class="label">{{ label }}</span>
|
|
144
|
-
<!-- Icon (end position) -->
|
|
145
|
-
<i v-if="icon && iconPosition === 'end'" :class="computedIconClass">{{ icon }}</i>
|
|
146
|
-
</router-link>
|
|
147
|
-
|
|
148
|
-
<!-- Normal button if no "to" prop -->
|
|
149
|
-
<button v-else v-bind="computedProps" @click="onClick">
|
|
150
|
-
<i v-if="icon && iconPosition === 'start'" :class="computedIconClass">{{ icon }}</i>
|
|
151
|
-
<span v-if="label" class="label">{{ label }}</span>
|
|
152
|
-
<i v-if="icon && iconPosition === 'end'" :class="computedIconClass">{{ icon }}</i>
|
|
153
|
-
</button>
|
|
154
|
-
</template>
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineOptions({
|
|
3
|
-
inheritAttrs: false,
|
|
4
|
-
})
|
|
5
|
-
|
|
6
|
-
import { computed } from 'vue'
|
|
7
|
-
|
|
8
|
-
const props = defineProps({
|
|
9
|
-
flat: { type: Boolean, default: false },
|
|
10
|
-
square: { type: Boolean, default: false },
|
|
11
|
-
dense: { type: Boolean, default: false },
|
|
12
|
-
bordered: { type: Boolean, default: true },
|
|
13
|
-
|
|
14
|
-
color: { type: String, default: 'white' },
|
|
15
|
-
borderColor: { type: String, default: 'gray-200' },
|
|
16
|
-
|
|
17
|
-
width: { type: String, default: '100%' },
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const isHex = (val: string) => val.startsWith('#')
|
|
21
|
-
|
|
22
|
-
const cardStyle = computed(() => {
|
|
23
|
-
const style: Record<string, string> = {
|
|
24
|
-
width: props.width,
|
|
25
|
-
maxWidth: '80vw',
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (isHex(props.color)) {
|
|
29
|
-
style.backgroundColor = props.color
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (props.bordered && isHex(props.borderColor)) {
|
|
33
|
-
style.borderColor = props.borderColor
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return style
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const cardClass = computed(() => [
|
|
40
|
-
'transition-shadow duration-300',
|
|
41
|
-
props.flat ? 'shadow-none' : 'shadow-md',
|
|
42
|
-
props.square ? 'rounded-none' : 'rounded-md',
|
|
43
|
-
props.bordered ? 'border' : '',
|
|
44
|
-
!isHex(props.borderColor) && props.bordered
|
|
45
|
-
? `border-${props.borderColor}`
|
|
46
|
-
: '',
|
|
47
|
-
!isHex(props.color) ? `bg-${props.color}` : '',
|
|
48
|
-
])
|
|
49
|
-
|
|
50
|
-
const sectionPadding = computed(() =>
|
|
51
|
-
props.dense ? 'p-0' : 'p-4'
|
|
52
|
-
)
|
|
53
|
-
</script>
|
|
54
|
-
|
|
55
|
-
<template>
|
|
56
|
-
<div
|
|
57
|
-
:class="cardClass"
|
|
58
|
-
:style="cardStyle"
|
|
59
|
-
v-bind="$attrs"
|
|
60
|
-
>
|
|
61
|
-
<!-- Header -->
|
|
62
|
-
<div
|
|
63
|
-
v-if="$slots.header"
|
|
64
|
-
:class="sectionPadding"
|
|
65
|
-
class="border-b border-gray-200"
|
|
66
|
-
>
|
|
67
|
-
<slot name="header" />
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<!-- Content -->
|
|
71
|
-
<div :class="sectionPadding">
|
|
72
|
-
<slot />
|
|
73
|
-
</div>
|
|
74
|
-
|
|
75
|
-
<!-- Footer -->
|
|
76
|
-
<div
|
|
77
|
-
v-if="$slots.footer"
|
|
78
|
-
:class="sectionPadding"
|
|
79
|
-
class="border-t border-gray-200"
|
|
80
|
-
>
|
|
81
|
-
<slot name="footer" />
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</template>
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineOptions({ inheritAttrs: false })
|
|
3
|
-
|
|
4
|
-
import { computed } from 'vue'
|
|
5
|
-
import { useUiClasses } from './useUiClasses'
|
|
6
|
-
import { uiProps } from './uiProps'
|
|
7
|
-
|
|
8
|
-
const emit = defineEmits<{
|
|
9
|
-
(e: 'update:modelValue', value: any): void
|
|
10
|
-
}>()
|
|
11
|
-
|
|
12
|
-
const props = defineProps({
|
|
13
|
-
modelValue: { required: true },
|
|
14
|
-
|
|
15
|
-
name: { type: String, default: null },
|
|
16
|
-
label: { type: String, default: null },
|
|
17
|
-
|
|
18
|
-
trueValue: { default: true },
|
|
19
|
-
falseValue: { default: false },
|
|
20
|
-
intermediateValue: { default: null },
|
|
21
|
-
|
|
22
|
-
checkedIcon: { type: String, default: 'check' },
|
|
23
|
-
uncheckedIcon: { type: String, default: null },
|
|
24
|
-
intermediateIcon: { type: String, default: 'remove' },
|
|
25
|
-
|
|
26
|
-
checkedColor: { type: String, default: '' },
|
|
27
|
-
uncheckedColor: { type: String, default: 'gray-400' },
|
|
28
|
-
|
|
29
|
-
dense: { type: Boolean, default: false },
|
|
30
|
-
|
|
31
|
-
...uiProps,
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
const {
|
|
35
|
-
disable,
|
|
36
|
-
color,
|
|
37
|
-
size,
|
|
38
|
-
rounded,
|
|
39
|
-
roundedClass,
|
|
40
|
-
} = useUiClasses(props)
|
|
41
|
-
|
|
42
|
-
const isChecked = computed(
|
|
43
|
-
() => props.modelValue === props.trueValue
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
const isIntermediate = computed(
|
|
47
|
-
() => props.modelValue === props.intermediateValue
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
const boxSizeClass = computed(() => {
|
|
51
|
-
const map = {
|
|
52
|
-
sm: 'w-4 h-4',
|
|
53
|
-
md: 'w-5 h-5',
|
|
54
|
-
lg: 'w-6 h-6',
|
|
55
|
-
} as const
|
|
56
|
-
|
|
57
|
-
return map[size.value as keyof typeof map]
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const labelSizeClass = computed(() => {
|
|
61
|
-
const map = {
|
|
62
|
-
sm: 'text-sm',
|
|
63
|
-
md: 'text-base',
|
|
64
|
-
lg: 'text-xl',
|
|
65
|
-
} as const
|
|
66
|
-
|
|
67
|
-
return map[size.value as keyof typeof map]
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
const boxColorClass = computed(() => {
|
|
71
|
-
if (isChecked.value) {
|
|
72
|
-
return `bg-${props.checkedColor || color.value} border-${props.checkedColor || color.value}`
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (isIntermediate.value) {
|
|
76
|
-
return `bg-${color.value} border-${color.value}`
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return `bg-transparent border-${props.uncheckedColor}`
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
const containerClass = computed(() =>
|
|
83
|
-
`
|
|
84
|
-
inline-flex items-center gap-2
|
|
85
|
-
${disable.value ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
|
86
|
-
`
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
function toggle() {
|
|
90
|
-
if (disable.value) return
|
|
91
|
-
|
|
92
|
-
if (isChecked.value || isIntermediate.value) {
|
|
93
|
-
emit('update:modelValue', props.falseValue)
|
|
94
|
-
} else {
|
|
95
|
-
emit('update:modelValue', props.trueValue)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
</script>
|
|
99
|
-
<template>
|
|
100
|
-
<label
|
|
101
|
-
:class="containerClass"
|
|
102
|
-
role="checkbox"
|
|
103
|
-
:aria-checked="isIntermediate ? 'mixed' : isChecked"
|
|
104
|
-
>
|
|
105
|
-
<input
|
|
106
|
-
type="checkbox"
|
|
107
|
-
class="hidden"
|
|
108
|
-
:name="name"
|
|
109
|
-
:disabled="disable"
|
|
110
|
-
@change="toggle"
|
|
111
|
-
/>
|
|
112
|
-
|
|
113
|
-
<span
|
|
114
|
-
:class="[
|
|
115
|
-
'flex items-center justify-center border transition-all duration-200 p-2',
|
|
116
|
-
boxSizeClass,
|
|
117
|
-
roundedClass[rounded as keyof typeof roundedClass],
|
|
118
|
-
boxColorClass,
|
|
119
|
-
]"
|
|
120
|
-
@click.prevent="toggle"
|
|
121
|
-
>
|
|
122
|
-
<i
|
|
123
|
-
v-if="isChecked && checkedIcon"
|
|
124
|
-
:class="labelSizeClass"
|
|
125
|
-
class="material-icons text-white"
|
|
126
|
-
>
|
|
127
|
-
{{ checkedIcon }}
|
|
128
|
-
</i>
|
|
129
|
-
<i
|
|
130
|
-
v-else-if="isIntermediate && intermediateIcon"
|
|
131
|
-
:class="labelSizeClass"
|
|
132
|
-
class="material-icons text-white"
|
|
133
|
-
>
|
|
134
|
-
{{ intermediateIcon }}
|
|
135
|
-
</i>
|
|
136
|
-
<i
|
|
137
|
-
v-else-if="uncheckedIcon"
|
|
138
|
-
:class="labelSizeClass"
|
|
139
|
-
class="material-icons text-gray-400"
|
|
140
|
-
>
|
|
141
|
-
{{ uncheckedIcon }}
|
|
142
|
-
</i>
|
|
143
|
-
</span>
|
|
144
|
-
<span v-if="label" class="select-none" :class="labelSizeClass">
|
|
145
|
-
{{ label }}
|
|
146
|
-
</span>
|
|
147
|
-
</label>
|
|
148
|
-
</template>
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { ref, useSlots, computed, isVNode } from 'vue'
|
|
3
|
-
|
|
4
|
-
defineProps<{
|
|
5
|
-
title?: string
|
|
6
|
-
}>()
|
|
7
|
-
|
|
8
|
-
const activeTab = ref('example')
|
|
9
|
-
const slots = useSlots()
|
|
10
|
-
|
|
11
|
-
import type { VNode } from 'vue'
|
|
12
|
-
|
|
13
|
-
function stringifyProps(props: unknown): string {
|
|
14
|
-
if (!props || typeof props !== 'object') {
|
|
15
|
-
return ''
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return Object.entries(props as Record<string, any>)
|
|
19
|
-
.filter(([key]) => !key.startsWith('on')) // 🔥 KEY FIX
|
|
20
|
-
.map(([key, value]) => {
|
|
21
|
-
if (value === true) return key
|
|
22
|
-
if (value === false || value == null) return ''
|
|
23
|
-
return `${key}="${String(value)}"`
|
|
24
|
-
})
|
|
25
|
-
.filter(Boolean)
|
|
26
|
-
.join(' ')
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function renderVNode(vnode: VNode): string {
|
|
30
|
-
if (typeof vnode.children === 'string') {
|
|
31
|
-
return vnode.children
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (vnode.type === Symbol.for('v-fgt')) {
|
|
35
|
-
return (vnode.children as VNode[])
|
|
36
|
-
.map(renderVNode)
|
|
37
|
-
.join('')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (typeof vnode.type === 'object') {
|
|
41
|
-
const typeAny = vnode.type as any
|
|
42
|
-
const name = typeAny.__name || typeAny.name || typeAny.displayName || 'Component'
|
|
43
|
-
const props = stringifyProps(vnode.props as any)
|
|
44
|
-
return `<${name}${props ? ' ' + props : ''} />`
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (typeof vnode.type === 'string') {
|
|
48
|
-
const children = Array.isArray(vnode.children)
|
|
49
|
-
? vnode.children
|
|
50
|
-
.map(child => (isVNode(child) ? renderVNode(child) : (typeof child === 'string' ? child : '')))
|
|
51
|
-
.join('')
|
|
52
|
-
: (typeof vnode.children === 'string' ? vnode.children : '')
|
|
53
|
-
return `<${vnode.type}>${children}</${vnode.type}>`
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return ''
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const slotCode = computed(() => {
|
|
60
|
-
const vnodes = slots.default?.() || []
|
|
61
|
-
return vnodes.map(renderVNode).join('')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const buttonIcon = ref('content_copy')
|
|
65
|
-
|
|
66
|
-
const copyCode = async () => {
|
|
67
|
-
await navigator.clipboard.writeText(slotCode.value)
|
|
68
|
-
|
|
69
|
-
buttonIcon.value = 'check'
|
|
70
|
-
setTimeout(() => {
|
|
71
|
-
buttonIcon.value = 'content_copy'
|
|
72
|
-
}, 2000)
|
|
73
|
-
}
|
|
74
|
-
</script>
|
|
75
|
-
<template>
|
|
76
|
-
<div class="border rounded-lg bg-white overflow-hidden">
|
|
77
|
-
<!-- Header -->
|
|
78
|
-
<div class="flex items-center justify-between px-4 py-2 border-b bg-gray-50">
|
|
79
|
-
<h3 v-if="title" class="font-medium text-gray-700">
|
|
80
|
-
{{ title }}
|
|
81
|
-
</h3>
|
|
82
|
-
|
|
83
|
-
<UIButton flat :icon="buttonIcon" size="sm" class="text-gray-600" @click="copyCode" />
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<!-- Tabs -->
|
|
87
|
-
<UITabs v-model="activeTab">
|
|
88
|
-
<UITabPanel name="example" label="Example">
|
|
89
|
-
<div class="p-4">
|
|
90
|
-
<slot />
|
|
91
|
-
</div>
|
|
92
|
-
</UITabPanel>
|
|
93
|
-
|
|
94
|
-
<UITabPanel name="code" label="Code">
|
|
95
|
-
<pre class="p-4 bg-gray-900 text-gray-100 text-sm overflow-x-auto"><code>{{ slotCode }}</code></pre>
|
|
96
|
-
</UITabPanel>
|
|
97
|
-
</UITabs>
|
|
98
|
-
</div>
|
|
99
|
-
</template>
|