ketekny-ui-kit 1.0.39 → 1.0.41
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/index.js +2 -1
- package/package.json +1 -1
- package/src/ui/kConfirmDialog.vue +2 -2
- package/src/ui/kDialog.vue +7 -7
- package/src/ui/kDrawer.vue +62 -36
- package/src/ui/kToggle_v2.vue +77 -0
- package/src/ui/kToggle_v3.vue +132 -0
- package/src/ui/kToggle_v4.vue +122 -0
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import kDialog from './src/ui/kDialog.vue'
|
|
|
7
7
|
import kDrawer from './src/ui/kDrawer.vue'
|
|
8
8
|
import kInput from './src/ui/kInput.vue'
|
|
9
9
|
import kDateSelector from './src/ui/kDateSelector.vue'
|
|
10
|
+
import kDateSelectorV2 from './src/ui/kDateSelector_v2.vue'
|
|
10
11
|
import kToolbar from './src/ui/kToolbar.vue'
|
|
11
12
|
import kSelect from './src/ui/kSelect.vue'
|
|
12
13
|
import kTable from './src/ui/kTable.vue'
|
|
@@ -50,7 +51,7 @@ export {
|
|
|
50
51
|
kMessage, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu, kSkeleton, kProgressBar, kTree,
|
|
51
52
|
|
|
52
53
|
// Form Components
|
|
53
|
-
kButton, kSelect, kUploader, kToggle, kInput, kDateSelector, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea,
|
|
54
|
+
kButton, kSelect, kUploader, kToggle, kInput, kDateSelector, kDateSelectorV2, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea,
|
|
54
55
|
|
|
55
56
|
// Dialogs
|
|
56
57
|
kDialog, kDrawer,
|
package/package.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
<Teleport to="body">
|
|
3
3
|
<Transition name="fade">
|
|
4
4
|
<div v-if="visible" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-40" style="z-index: 1500">
|
|
5
|
-
<div class="w-full max-w-md p-6 bg-white border rounded-lg shadow-xl border-primary/20 dark:bg-
|
|
5
|
+
<div class="w-full max-w-md p-6 bg-white border rounded-lg shadow-xl border-primary/20 dark:bg-zinc-900 dark:border-zinc-800">
|
|
6
6
|
<h2 class="mb-2 text-lg font-semibold text-primary">{{ title }}</h2>
|
|
7
|
-
<div class="mb-6 text-gray-700 dark:text-
|
|
7
|
+
<div class="mb-6 text-gray-700 dark:text-zinc-300"><span v-html="message" /></div>
|
|
8
8
|
<div class="flex justify-end gap-3">
|
|
9
9
|
<kButton :disabled="loading" secondary label="Άκυρο" @click="cancel" />
|
|
10
10
|
<kButton :loading="loading" danger label="Συνέχεια" @click="confirm" />
|
package/src/ui/kDialog.vue
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<transition name="dialog">
|
|
9
9
|
<div
|
|
10
10
|
ref="dialogPanel"
|
|
11
|
-
class="relative bg-white dark:bg-
|
|
11
|
+
class="relative bg-white dark:bg-zinc-900 shadow-lg overflow-hidden rounded-2xl flex flex-col max-h-[calc(100dvh-1rem)] sm:max-h-[90vh] m-2 sm:m-4"
|
|
12
12
|
:class="dialogClasses"
|
|
13
13
|
:role="'dialog'"
|
|
14
14
|
:aria-modal="'true'"
|
|
@@ -17,16 +17,16 @@
|
|
|
17
17
|
v-show="visible"
|
|
18
18
|
>
|
|
19
19
|
<!-- Header -->
|
|
20
|
-
<div class="flex flex-row items-center
|
|
21
|
-
<div :id="titleId" class="text-
|
|
20
|
+
<div class="flex flex-row items-center px-4 py-3 border-b border-primary/20 bg-primary/5 dark:bg-primary/10 shrink-0">
|
|
21
|
+
<div :id="titleId" class="text-sm font-semibold text-primary sm:text-base">{{ title }}</div>
|
|
22
22
|
<div class="flex-1" />
|
|
23
23
|
<button
|
|
24
24
|
type="button"
|
|
25
|
-
class="p-1
|
|
25
|
+
class="p-1.5 rounded-md text-zinc-400 hover:text-primary dark:text-zinc-500 dark:hover:text-primary hover:bg-primary/10 transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
|
|
26
26
|
aria-label="Close dialog"
|
|
27
27
|
@click="close"
|
|
28
28
|
>
|
|
29
|
-
<X />
|
|
29
|
+
<X class="w-5 h-5" />
|
|
30
30
|
</button>
|
|
31
31
|
</div>
|
|
32
32
|
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
</div>
|
|
37
37
|
|
|
38
38
|
<!-- Scrollable slot content -->
|
|
39
|
-
<div class="flex-1 p-3 overflow-auto
|
|
39
|
+
<div class="flex-1 p-3 overflow-auto sm:p-4">
|
|
40
40
|
<slot></slot>
|
|
41
41
|
</div>
|
|
42
42
|
|
|
43
43
|
<!-- Footer -->
|
|
44
44
|
<div
|
|
45
45
|
v-if="$slots.footer"
|
|
46
|
-
class="flex flex-col-reverse gap-2 p-3 border-t shrink-0 border-
|
|
46
|
+
class="flex flex-col-reverse gap-2 p-3 border-t shrink-0 border-zinc-200 dark:border-zinc-800 sm:flex-row sm:justify-end sm:p-4"
|
|
47
47
|
>
|
|
48
48
|
<slot name="footer"></slot>
|
|
49
49
|
</div>
|
package/src/ui/kDrawer.vue
CHANGED
|
@@ -1,41 +1,50 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<div class="
|
|
16
|
-
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
<div
|
|
3
|
+
v-if="visible"
|
|
4
|
+
class="fixed inset-0 z-50 flex overflow-hidden bg-black/50 backdrop-blur-sm"
|
|
5
|
+
:class="side === 'right' ? 'justify-end' : 'justify-start'"
|
|
6
|
+
@click.self="close"
|
|
7
|
+
>
|
|
8
|
+
<transition :name="side === 'right' ? 'drawer-right' : 'drawer-left'">
|
|
9
|
+
<div
|
|
10
|
+
class="relative m-2 flex h-[calc(100dvh-1rem)] w-[calc(100vw-1rem)] max-w-[calc(100vw-1rem)] flex-col overflow-hidden rounded-2xl bg-white shadow-lg dark:bg-zinc-900 sm:m-4 sm:h-[calc(100vh-2rem)] sm:w-[400px]"
|
|
11
|
+
role="dialog"
|
|
12
|
+
aria-modal="true"
|
|
13
|
+
@click.stop
|
|
14
|
+
>
|
|
15
|
+
<div class="flex flex-row items-center border-b border-primary/20 bg-primary/5 px-4 py-3 dark:bg-primary/10">
|
|
16
|
+
<div class="text-sm font-semibold text-primary sm:text-base">{{ title }}</div>
|
|
17
|
+
<div class="flex-1" />
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
class="cursor-pointer rounded-md p-1.5 text-zinc-400 transition-colors hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 dark:text-zinc-500 dark:hover:text-primary"
|
|
21
|
+
aria-label="Close drawer"
|
|
22
|
+
@click="close"
|
|
23
|
+
>
|
|
24
|
+
<X class="w-5 h-5" />
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</div>
|
|
28
|
+
<div class="flex-1 overflow-auto p-3 sm:p-4">
|
|
29
|
+
<slot></slot>
|
|
30
|
+
</div>
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
<div
|
|
33
|
+
v-if="$slots.footer"
|
|
34
|
+
class="flex flex-col-reverse gap-2 border-t border-zinc-200 p-3 dark:border-zinc-800 sm:flex-row sm:justify-end sm:p-4"
|
|
35
|
+
>
|
|
36
|
+
<slot name="footer"></slot>
|
|
37
|
+
</div>
|
|
29
38
|
</div>
|
|
30
|
-
</
|
|
39
|
+
</transition>
|
|
31
40
|
</div>
|
|
32
41
|
</template>
|
|
33
42
|
|
|
34
43
|
<script>
|
|
35
|
-
import { X } from
|
|
44
|
+
import { X } from 'lucide-vue-next'
|
|
36
45
|
|
|
37
46
|
export default {
|
|
38
|
-
name:
|
|
47
|
+
name: 'KDrawer',
|
|
39
48
|
components: {
|
|
40
49
|
X,
|
|
41
50
|
},
|
|
@@ -46,28 +55,45 @@ export default {
|
|
|
46
55
|
},
|
|
47
56
|
title: {
|
|
48
57
|
type: String,
|
|
49
|
-
default:
|
|
58
|
+
default: '',
|
|
50
59
|
},
|
|
51
60
|
side: {
|
|
52
61
|
type: String,
|
|
53
|
-
default:
|
|
54
|
-
validator: (val) => [
|
|
62
|
+
default: 'right', // "left" or "right"
|
|
63
|
+
validator: (val) => ['left', 'right'].includes(val),
|
|
55
64
|
},
|
|
56
65
|
},
|
|
57
66
|
watch: {
|
|
58
67
|
visible(val) {
|
|
59
|
-
document.body.style.overflow = val ?
|
|
68
|
+
document.body.style.overflow = val ? 'hidden' : ''
|
|
60
69
|
},
|
|
61
70
|
},
|
|
62
71
|
beforeUnmount() {
|
|
63
|
-
document.body.style.overflow =
|
|
72
|
+
document.body.style.overflow = ''
|
|
64
73
|
},
|
|
65
74
|
methods: {
|
|
66
75
|
close() {
|
|
67
|
-
this.$emit(
|
|
76
|
+
this.$emit('update:visible', false)
|
|
68
77
|
},
|
|
69
78
|
},
|
|
70
|
-
}
|
|
79
|
+
}
|
|
71
80
|
</script>
|
|
72
81
|
|
|
73
|
-
<style scoped
|
|
82
|
+
<style scoped>
|
|
83
|
+
.drawer-right-enter-active,
|
|
84
|
+
.drawer-right-leave-active,
|
|
85
|
+
.drawer-left-enter-active,
|
|
86
|
+
.drawer-left-leave-active {
|
|
87
|
+
transition: transform 0.25s ease;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.drawer-right-enter-from,
|
|
91
|
+
.drawer-right-leave-to {
|
|
92
|
+
transform: translateX(100%);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.drawer-left-enter-from,
|
|
96
|
+
.drawer-left-leave-to {
|
|
97
|
+
transform: translateX(-100%);
|
|
98
|
+
}
|
|
99
|
+
</style>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="labelStyle === 'inline' ? 'flex items-center gap-2' : ''">
|
|
3
|
+
<div
|
|
4
|
+
v-if="showLabel && labelStyle !== 'inline'"
|
|
5
|
+
:class="[
|
|
6
|
+
'block mb-1 text-sm font-bold',
|
|
7
|
+
disabled ? 'text-slate-500' : 'text-primary/90',
|
|
8
|
+
]"
|
|
9
|
+
>
|
|
10
|
+
{{ label }}
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<el-switch
|
|
14
|
+
:model-value="resolvedValue"
|
|
15
|
+
:disabled="disabled"
|
|
16
|
+
:active-text="activeText"
|
|
17
|
+
:inactive-text="inactiveText"
|
|
18
|
+
:active-color="activeColor"
|
|
19
|
+
:inactive-color="inactiveColor"
|
|
20
|
+
:size="size"
|
|
21
|
+
:width="width"
|
|
22
|
+
:inline-prompt="inlinePrompt"
|
|
23
|
+
@update:model-value="emitModelUpdate"
|
|
24
|
+
@change="$emit('change', $event)"
|
|
25
|
+
/>
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
v-if="showLabel && labelStyle === 'inline'"
|
|
29
|
+
:class="[
|
|
30
|
+
'text-sm font-bold cursor-pointer',
|
|
31
|
+
disabled ? 'text-slate-500 cursor-not-allowed' : 'text-primary/90',
|
|
32
|
+
]"
|
|
33
|
+
@click="!disabled && emitModelUpdate(!resolvedValue)"
|
|
34
|
+
>
|
|
35
|
+
{{ label }}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup>
|
|
41
|
+
import { computed, getCurrentInstance } from 'vue'
|
|
42
|
+
import { ElSwitch } from 'element-plus'
|
|
43
|
+
|
|
44
|
+
const props = defineProps({
|
|
45
|
+
modelValue: { type: Boolean, default: false },
|
|
46
|
+
value: { type: Boolean, default: undefined },
|
|
47
|
+
label: { type: String, default: '' },
|
|
48
|
+
labelStyle: { type: String, default: null },
|
|
49
|
+
disabled: { type: Boolean, default: false },
|
|
50
|
+
size: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: 'default',
|
|
53
|
+
validator: (val) => ['large', 'default', 'small'].includes(val),
|
|
54
|
+
},
|
|
55
|
+
width: { type: [String, Number], default: undefined },
|
|
56
|
+
activeText: { type: String, default: '' },
|
|
57
|
+
inactiveText: { type: String, default: '' },
|
|
58
|
+
activeColor: { type: String, default: '' },
|
|
59
|
+
inactiveColor: { type: String, default: '' },
|
|
60
|
+
inlinePrompt: { type: Boolean, default: false },
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const emit = defineEmits(['update:modelValue', 'update:value', 'change'])
|
|
64
|
+
|
|
65
|
+
const instance = getCurrentInstance()
|
|
66
|
+
const passedProps = new Set(Object.keys(instance?.vnode.props || {}))
|
|
67
|
+
const hasProp = (...names) => names.some((name) => passedProps.has(name))
|
|
68
|
+
|
|
69
|
+
const resolvedValue = computed(() => (hasProp('value') ? props.value : props.modelValue))
|
|
70
|
+
|
|
71
|
+
const showLabel = computed(() => !!props.label && props.label.trim() !== '')
|
|
72
|
+
|
|
73
|
+
function emitModelUpdate(val) {
|
|
74
|
+
emit('update:modelValue', val)
|
|
75
|
+
emit('update:value', val)
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="labelStyle === 'inline' ? 'flex items-center gap-2' : ''">
|
|
3
|
+
<div
|
|
4
|
+
v-if="showLabel && labelStyle !== 'inline'"
|
|
5
|
+
:class="[
|
|
6
|
+
'block mb-1 text-sm font-bold',
|
|
7
|
+
disabled ? 'text-slate-500' : 'text-primary/90',
|
|
8
|
+
]"
|
|
9
|
+
>
|
|
10
|
+
{{ label }}
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<SwitchRoot
|
|
14
|
+
:checked="resolvedValue"
|
|
15
|
+
:disabled="disabled"
|
|
16
|
+
class="k-toggle-v3-track"
|
|
17
|
+
:data-state="resolvedValue ? 'checked' : 'unchecked'"
|
|
18
|
+
@update:checked="emitModelUpdate"
|
|
19
|
+
>
|
|
20
|
+
<SwitchThumb class="k-toggle-v3-thumb" />
|
|
21
|
+
</SwitchRoot>
|
|
22
|
+
|
|
23
|
+
<div
|
|
24
|
+
v-if="showLabel && labelStyle === 'inline'"
|
|
25
|
+
:class="[
|
|
26
|
+
'text-sm font-bold cursor-pointer',
|
|
27
|
+
disabled ? 'text-slate-500 cursor-not-allowed' : 'text-primary/90',
|
|
28
|
+
]"
|
|
29
|
+
@click="!disabled && emitModelUpdate(!resolvedValue)"
|
|
30
|
+
>
|
|
31
|
+
{{ label }}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup>
|
|
37
|
+
import { computed, getCurrentInstance } from 'vue'
|
|
38
|
+
import { SwitchRoot, SwitchThumb } from 'reka-ui'
|
|
39
|
+
|
|
40
|
+
const props = defineProps({
|
|
41
|
+
modelValue: { type: Boolean, default: false },
|
|
42
|
+
value: { type: Boolean, default: undefined },
|
|
43
|
+
label: { type: String, default: '' },
|
|
44
|
+
labelStyle: { type: String, default: null },
|
|
45
|
+
disabled: { type: Boolean, default: false },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const emit = defineEmits(['update:modelValue', 'update:value'])
|
|
49
|
+
|
|
50
|
+
const instance = getCurrentInstance()
|
|
51
|
+
const passedProps = new Set(Object.keys(instance?.vnode.props || {}))
|
|
52
|
+
const hasProp = (...names) => names.some((name) => passedProps.has(name))
|
|
53
|
+
|
|
54
|
+
const resolvedValue = computed(() => (hasProp('value') ? props.value : props.modelValue))
|
|
55
|
+
|
|
56
|
+
const showLabel = computed(() => !!props.label && props.label.trim() !== '')
|
|
57
|
+
|
|
58
|
+
function emitModelUpdate(val) {
|
|
59
|
+
emit('update:modelValue', val)
|
|
60
|
+
emit('update:value', val)
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<style scoped>
|
|
65
|
+
.k-toggle-v3-track {
|
|
66
|
+
position: relative;
|
|
67
|
+
display: inline-flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
width: 3rem;
|
|
70
|
+
height: 1.75rem;
|
|
71
|
+
padding: 0.15rem;
|
|
72
|
+
border-radius: 9999px;
|
|
73
|
+
border: none;
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
background: #94a3b8;
|
|
77
|
+
transition: background-color 0.25s ease, box-shadow 0.25s ease;
|
|
78
|
+
outline: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.k-toggle-v3-track:focus-visible {
|
|
82
|
+
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #10b981;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.k-toggle-v3-track[data-state='checked'] {
|
|
86
|
+
background: #10b981;
|
|
87
|
+
box-shadow: 0 0 12px rgba(16, 185, 129, 0.35);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.k-toggle-v3-track[data-state='unchecked'] {
|
|
91
|
+
background: #94a3b8;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.k-toggle-v3-track[data-disabled] {
|
|
95
|
+
cursor: not-allowed;
|
|
96
|
+
opacity: 0.45;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.k-toggle-v3-thumb {
|
|
100
|
+
display: block;
|
|
101
|
+
width: 1.35rem;
|
|
102
|
+
height: 1.35rem;
|
|
103
|
+
border-radius: 9999px;
|
|
104
|
+
background: #fff;
|
|
105
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
106
|
+
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
107
|
+
pointer-events: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.k-toggle-v3-track[data-state='checked'] .k-toggle-v3-thumb {
|
|
111
|
+
transform: translateX(1.25rem);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.k-toggle-v3-track[data-state='unchecked'] .k-toggle-v3-thumb {
|
|
115
|
+
transform: translateX(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Dark mode */
|
|
119
|
+
:global(html.dark) .k-toggle-v3-track[data-state='unchecked'] {
|
|
120
|
+
background: #475569;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
:global(html.dark) .k-toggle-v3-track[data-state='checked'] {
|
|
124
|
+
background: #059669;
|
|
125
|
+
box-shadow: 0 0 14px rgba(5, 150, 105, 0.4);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
:global(html.dark) .k-toggle-v3-thumb {
|
|
129
|
+
background: #f1f5f9;
|
|
130
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="labelStyle === 'inline' ? 'flex items-center gap-2' : ''">
|
|
3
|
+
<div
|
|
4
|
+
v-if="showLabel && labelStyle !== 'inline'"
|
|
5
|
+
:class="[
|
|
6
|
+
'block mb-1 text-sm font-bold',
|
|
7
|
+
disabled ? 'text-slate-500' : 'text-primary/90',
|
|
8
|
+
]"
|
|
9
|
+
>
|
|
10
|
+
{{ label }}
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<button
|
|
14
|
+
type="button"
|
|
15
|
+
role="switch"
|
|
16
|
+
:aria-checked="resolvedValue"
|
|
17
|
+
:aria-label="label || 'Toggle'"
|
|
18
|
+
:disabled="disabled"
|
|
19
|
+
:class="cn(toggleVariants({ state: resolvedValue ? 'checked' : 'unchecked', size }), $attrs.class)"
|
|
20
|
+
@click="!disabled && emitModelUpdate(!resolvedValue)"
|
|
21
|
+
>
|
|
22
|
+
<span
|
|
23
|
+
:class="cn(thumbVariants({ state: resolvedValue ? 'checked' : 'unchecked', size }))"
|
|
24
|
+
/>
|
|
25
|
+
</button>
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
v-if="showLabel && labelStyle === 'inline'"
|
|
29
|
+
:class="[
|
|
30
|
+
'text-sm font-bold cursor-pointer',
|
|
31
|
+
disabled ? 'text-slate-500 cursor-not-allowed' : 'text-primary/90',
|
|
32
|
+
]"
|
|
33
|
+
@click="!disabled && emitModelUpdate(!resolvedValue)"
|
|
34
|
+
>
|
|
35
|
+
{{ label }}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup>
|
|
41
|
+
import { computed, getCurrentInstance } from 'vue'
|
|
42
|
+
import { cva } from 'class-variance-authority'
|
|
43
|
+
import { cn } from '../lib/utils'
|
|
44
|
+
|
|
45
|
+
const toggleVariants = cva(
|
|
46
|
+
'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-slate-300 dark:focus-visible:ring-offset-slate-950 disabled:cursor-not-allowed disabled:opacity-50',
|
|
47
|
+
{
|
|
48
|
+
variants: {
|
|
49
|
+
state: {
|
|
50
|
+
checked: 'bg-slate-900 dark:bg-slate-50',
|
|
51
|
+
unchecked: 'bg-slate-200 dark:bg-slate-800',
|
|
52
|
+
},
|
|
53
|
+
size: {
|
|
54
|
+
sm: 'h-4 w-7',
|
|
55
|
+
default: 'h-5 w-9',
|
|
56
|
+
lg: 'h-6 w-11',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
defaultVariants: {
|
|
60
|
+
state: 'unchecked',
|
|
61
|
+
size: 'default',
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const thumbVariants = cva(
|
|
67
|
+
'pointer-events-none block rounded-full bg-white shadow-sm ring-0 transition-transform duration-200 dark:bg-slate-950',
|
|
68
|
+
{
|
|
69
|
+
variants: {
|
|
70
|
+
state: {
|
|
71
|
+
checked: '',
|
|
72
|
+
unchecked: '',
|
|
73
|
+
},
|
|
74
|
+
size: {
|
|
75
|
+
sm: 'h-3 w-3',
|
|
76
|
+
default: 'h-4 w-4',
|
|
77
|
+
lg: 'h-5 w-5',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
compoundVariants: [
|
|
81
|
+
{ state: 'checked', size: 'sm', class: 'translate-x-3' },
|
|
82
|
+
{ state: 'unchecked', size: 'sm', class: 'translate-x-0.5' },
|
|
83
|
+
{ state: 'checked', size: 'default', class: 'translate-x-4' },
|
|
84
|
+
{ state: 'unchecked', size: 'default', class: 'translate-x-0.5' },
|
|
85
|
+
{ state: 'checked', size: 'lg', class: 'translate-x-5' },
|
|
86
|
+
{ state: 'unchecked', size: 'lg', class: 'translate-x-0.5' },
|
|
87
|
+
],
|
|
88
|
+
defaultVariants: {
|
|
89
|
+
state: 'unchecked',
|
|
90
|
+
size: 'default',
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
const props = defineProps({
|
|
96
|
+
modelValue: { type: Boolean, default: false },
|
|
97
|
+
value: { type: Boolean, default: undefined },
|
|
98
|
+
label: { type: String, default: '' },
|
|
99
|
+
labelStyle: { type: String, default: null },
|
|
100
|
+
disabled: { type: Boolean, default: false },
|
|
101
|
+
size: {
|
|
102
|
+
type: String,
|
|
103
|
+
default: 'default',
|
|
104
|
+
validator: (val) => ['sm', 'default', 'lg'].includes(val),
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const emit = defineEmits(['update:modelValue', 'update:value'])
|
|
109
|
+
|
|
110
|
+
const instance = getCurrentInstance()
|
|
111
|
+
const passedProps = new Set(Object.keys(instance?.vnode.props || {}))
|
|
112
|
+
const hasProp = (...names) => names.some((name) => passedProps.has(name))
|
|
113
|
+
|
|
114
|
+
const resolvedValue = computed(() => (hasProp('value') ? props.value : props.modelValue))
|
|
115
|
+
|
|
116
|
+
const showLabel = computed(() => !!props.label && props.label.trim() !== '')
|
|
117
|
+
|
|
118
|
+
function emitModelUpdate(val) {
|
|
119
|
+
emit('update:modelValue', val)
|
|
120
|
+
emit('update:value', val)
|
|
121
|
+
}
|
|
122
|
+
</script>
|