@veristone/nuxt-v-app 0.2.0 → 0.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.
- package/app/components/V/A/Crud/Delete.vue +0 -1
- package/app/components/V/A/Modal/Base.vue +64 -167
- package/app/components/V/A/Modal/Form.vue +47 -21
- package/app/components/V/A/Slide.vue +17 -19
- package/app/components/V/Modal.vue +51 -18
- package/app/composables/useVToast.ts +10 -10
- package/app/layouts/default.vue +4 -2
- package/app/pages/playground/modals.vue +794 -557
- package/app/pages/test-api-auth.vue +140 -93
- package/package.json +1 -1
|
@@ -1,195 +1,92 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
|
-
* VAModalBase -
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Size variants: sm, md, lg, xl, full
|
|
8
|
-
* - Backdrop with optional blur
|
|
9
|
-
* - Accessible: focus trap, escape key, click outside to close
|
|
10
|
-
* - Header/body/footer slots
|
|
11
|
-
* - Smooth enter/leave transitions
|
|
3
|
+
* VAModalBase - Base Modal Component using Nuxt UI v4
|
|
4
|
+
* Simple wrapper around UModal with consistent V-App styling
|
|
12
5
|
*/
|
|
13
|
-
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
|
14
|
-
|
|
15
6
|
const props = withDefaults(defineProps<{
|
|
16
7
|
modelValue?: boolean
|
|
17
8
|
title?: string
|
|
18
9
|
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
triggerLabel?: string
|
|
11
|
+
triggerIcon?: string
|
|
12
|
+
triggerColor?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'
|
|
13
|
+
triggerVariant?: 'solid' | 'outline' | 'soft' | 'ghost' | 'link'
|
|
14
|
+
triggerSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
24
15
|
}>(), {
|
|
25
|
-
modelValue:
|
|
16
|
+
modelValue: undefined,
|
|
26
17
|
title: '',
|
|
27
18
|
size: 'md',
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
showBackdrop: true,
|
|
32
|
-
backdropBlur: true
|
|
19
|
+
triggerColor: 'primary',
|
|
20
|
+
triggerVariant: 'solid',
|
|
21
|
+
triggerSize: 'sm'
|
|
33
22
|
})
|
|
34
23
|
|
|
35
|
-
const emit = defineEmits
|
|
36
|
-
'update:modelValue': [value: boolean]
|
|
37
|
-
'open': []
|
|
38
|
-
'close': []
|
|
39
|
-
}>()
|
|
24
|
+
const emit = defineEmits(['update:modelValue', 'open', 'close'])
|
|
40
25
|
|
|
41
|
-
|
|
42
|
-
const modalRef = ref<HTMLElement | null>(null)
|
|
43
|
-
const { activate, deactivate } = useFocusTrap(modalRef, {
|
|
44
|
-
immediate: false,
|
|
45
|
-
allowOutsideClick: true,
|
|
46
|
-
escapeDeactivates: false // We handle escape ourselves
|
|
47
|
-
})
|
|
26
|
+
const internalOpen = ref(false)
|
|
48
27
|
|
|
49
|
-
// Computed open state
|
|
50
28
|
const isOpen = computed({
|
|
51
|
-
get: () => props.modelValue,
|
|
52
|
-
set: (val) =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!props.closable) return
|
|
58
|
-
isOpen.value = false
|
|
59
|
-
emit('close')
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Open modal
|
|
63
|
-
const open = () => {
|
|
64
|
-
isOpen.value = true
|
|
65
|
-
emit('open')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Handle escape key
|
|
69
|
-
const handleKeydown = (e: KeyboardEvent) => {
|
|
70
|
-
if (e.key === 'Escape' && props.closeOnEscape && props.closable) {
|
|
71
|
-
close()
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Handle click outside
|
|
76
|
-
const handleBackdropClick = (e: MouseEvent) => {
|
|
77
|
-
if (e.target === e.currentTarget && props.closeOnClickOutside && props.closable) {
|
|
78
|
-
close()
|
|
29
|
+
get: () => props.modelValue ?? internalOpen.value,
|
|
30
|
+
set: (val) => {
|
|
31
|
+
emit('update:modelValue', val)
|
|
32
|
+
internalOpen.value = val
|
|
33
|
+
if (val) emit('open')
|
|
34
|
+
else emit('close')
|
|
79
35
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Watch for open state changes
|
|
83
|
-
watch(isOpen, (val) => {
|
|
84
|
-
if (val) {
|
|
85
|
-
document.body.style.overflow = 'hidden'
|
|
86
|
-
nextTick(() => {
|
|
87
|
-
activate()
|
|
88
|
-
})
|
|
89
|
-
} else {
|
|
90
|
-
document.body.style.overflow = ''
|
|
91
|
-
deactivate()
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
// Cleanup on unmount
|
|
96
|
-
onUnmounted(() => {
|
|
97
|
-
document.body.style.overflow = ''
|
|
98
|
-
deactivate()
|
|
99
36
|
})
|
|
100
37
|
|
|
101
|
-
// Size
|
|
102
|
-
const
|
|
38
|
+
// Size to width mapping
|
|
39
|
+
const widthClass = computed(() => {
|
|
103
40
|
switch (props.size) {
|
|
104
|
-
case 'sm': return 'max-w-sm'
|
|
105
|
-
case 'md': return 'max-w-
|
|
106
|
-
case 'lg': return 'max-w-
|
|
107
|
-
case 'xl': return 'max-w-
|
|
108
|
-
case 'full': return 'max-w-
|
|
109
|
-
default: return 'max-w-
|
|
41
|
+
case 'sm': return 'sm:max-w-sm'
|
|
42
|
+
case 'md': return 'sm:max-w-lg'
|
|
43
|
+
case 'lg': return 'sm:max-w-2xl'
|
|
44
|
+
case 'xl': return 'sm:max-w-4xl'
|
|
45
|
+
case 'full': return 'sm:max-w-full'
|
|
46
|
+
default: return 'sm:max-w-lg'
|
|
110
47
|
}
|
|
111
48
|
})
|
|
112
|
-
|
|
113
|
-
// Expose methods
|
|
114
|
-
defineExpose({ open, close })
|
|
115
49
|
</script>
|
|
116
50
|
|
|
117
51
|
<template>
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
:class="[
|
|
131
|
-
showBackdrop ? (backdropBlur ? 'bg-gray-900/60 backdrop-blur-sm' : 'bg-gray-900/60') : ''
|
|
132
|
-
]"
|
|
133
|
-
@click="handleBackdropClick"
|
|
134
|
-
@keydown="handleKeydown"
|
|
135
|
-
>
|
|
136
|
-
<Transition
|
|
137
|
-
enter-active-class="duration-200 ease-out"
|
|
138
|
-
enter-from-class="opacity-0 scale-95 translate-y-4"
|
|
139
|
-
enter-to-class="opacity-100 scale-100 translate-y-0"
|
|
140
|
-
leave-active-class="duration-150 ease-in"
|
|
141
|
-
leave-from-class="opacity-100 scale-100 translate-y-0"
|
|
142
|
-
leave-to-class="opacity-0 scale-95 translate-y-4"
|
|
143
|
-
>
|
|
144
|
-
<div
|
|
145
|
-
v-if="isOpen"
|
|
146
|
-
ref="modalRef"
|
|
147
|
-
class="relative w-full bg-white dark:bg-gray-900 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-800 flex flex-col overflow-hidden"
|
|
148
|
-
:class="[sizeClasses, size === 'full' ? '' : 'max-h-[calc(100vh-2rem)]']"
|
|
149
|
-
role="dialog"
|
|
150
|
-
aria-modal="true"
|
|
151
|
-
:aria-labelledby="title ? 'va-modal-title' : undefined"
|
|
152
|
-
>
|
|
153
|
-
<!-- Header -->
|
|
154
|
-
<div
|
|
155
|
-
v-if="title || $slots.header || closable"
|
|
156
|
-
class="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-800 shrink-0"
|
|
157
|
-
>
|
|
158
|
-
<slot name="header">
|
|
159
|
-
<h2
|
|
160
|
-
v-if="title"
|
|
161
|
-
id="va-modal-title"
|
|
162
|
-
class="text-lg font-semibold text-gray-900 dark:text-white"
|
|
163
|
-
>
|
|
164
|
-
{{ title }}
|
|
165
|
-
</h2>
|
|
166
|
-
</slot>
|
|
167
|
-
<button
|
|
168
|
-
v-if="closable"
|
|
169
|
-
type="button"
|
|
170
|
-
class="p-1.5 rounded-lg text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
171
|
-
aria-label="Close modal"
|
|
172
|
-
@click="close"
|
|
173
|
-
>
|
|
174
|
-
<UIcon name="i-lucide-x" class="w-5 h-5" />
|
|
175
|
-
</button>
|
|
176
|
-
</div>
|
|
52
|
+
<UModal v-model:open="isOpen" :ui="{ width: widthClass }">
|
|
53
|
+
<!-- Trigger Button (required for Nuxt UI v4) -->
|
|
54
|
+
<slot name="trigger">
|
|
55
|
+
<UButton
|
|
56
|
+
v-if="triggerLabel || triggerIcon"
|
|
57
|
+
:label="triggerLabel"
|
|
58
|
+
:icon="triggerIcon"
|
|
59
|
+
:color="triggerColor"
|
|
60
|
+
:variant="triggerVariant"
|
|
61
|
+
:size="triggerSize"
|
|
62
|
+
/>
|
|
63
|
+
</slot>
|
|
177
64
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
65
|
+
<template #header>
|
|
66
|
+
<div class="flex items-center justify-between w-full">
|
|
67
|
+
<slot name="header">
|
|
68
|
+
<h3 v-if="title" class="text-base font-semibold text-gray-900 dark:text-white">
|
|
69
|
+
{{ title }}
|
|
70
|
+
</h3>
|
|
71
|
+
</slot>
|
|
72
|
+
<UButton
|
|
73
|
+
icon="i-lucide-x"
|
|
74
|
+
color="neutral"
|
|
75
|
+
variant="ghost"
|
|
76
|
+
size="sm"
|
|
77
|
+
@click="isOpen = false"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
182
81
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class="px-6 py-4 border-t border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-800/50 shrink-0"
|
|
187
|
-
>
|
|
188
|
-
<slot name="footer" />
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
</Transition>
|
|
82
|
+
<template #body>
|
|
83
|
+
<div class="p-4">
|
|
84
|
+
<slot />
|
|
192
85
|
</div>
|
|
193
|
-
</
|
|
194
|
-
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<template v-if="$slots.footer" #footer>
|
|
89
|
+
<slot name="footer" />
|
|
90
|
+
</template>
|
|
91
|
+
</UModal>
|
|
195
92
|
</template>
|
|
@@ -8,13 +8,22 @@ const props = withDefaults(defineProps<{
|
|
|
8
8
|
endpoint?: string
|
|
9
9
|
recordId?: string | number
|
|
10
10
|
initialData?: any
|
|
11
|
-
size?:
|
|
11
|
+
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
12
12
|
loading?: boolean
|
|
13
13
|
error?: string
|
|
14
|
+
triggerLabel?: string
|
|
15
|
+
triggerIcon?: string
|
|
16
|
+
triggerColor?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'
|
|
17
|
+
triggerVariant?: 'solid' | 'outline' | 'soft' | 'ghost' | 'link'
|
|
18
|
+
triggerSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
14
19
|
}>(), {
|
|
15
|
-
modelValue:
|
|
20
|
+
modelValue: undefined,
|
|
16
21
|
title: 'Edit Record',
|
|
17
|
-
size: 'md'
|
|
22
|
+
size: 'md',
|
|
23
|
+
triggerIcon: 'i-lucide-edit',
|
|
24
|
+
triggerColor: 'primary',
|
|
25
|
+
triggerVariant: 'solid',
|
|
26
|
+
triggerSize: 'sm'
|
|
18
27
|
})
|
|
19
28
|
|
|
20
29
|
const emit = defineEmits(['update:modelValue', 'submit', 'success', 'error'])
|
|
@@ -25,7 +34,7 @@ const formData = ref({ ...(props.initialData || {}) })
|
|
|
25
34
|
const isEdit = computed(() => !!props.recordId)
|
|
26
35
|
|
|
27
36
|
const isOpen = computed({
|
|
28
|
-
get: () => props.modelValue
|
|
37
|
+
get: () => props.modelValue ?? internalOpen.value,
|
|
29
38
|
set: (val) => {
|
|
30
39
|
emit('update:modelValue', val)
|
|
31
40
|
internalOpen.value = val
|
|
@@ -65,18 +74,36 @@ const handleSubmit = async (data: any = formData.value) => {
|
|
|
65
74
|
</script>
|
|
66
75
|
|
|
67
76
|
<template>
|
|
68
|
-
<UModal v-model="isOpen" :ui="{ width: size === 'lg' ? 'sm:max-w-4xl' : 'sm:max-w-xl' }">
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
<UModal v-model:open="isOpen" :ui="{ width: size === 'lg' ? 'sm:max-w-4xl' : 'sm:max-w-xl' }">
|
|
78
|
+
<!-- Trigger Button (required for Nuxt UI v4) -->
|
|
79
|
+
<slot name="trigger">
|
|
80
|
+
<UButton
|
|
81
|
+
v-if="triggerLabel || triggerIcon"
|
|
82
|
+
:label="triggerLabel"
|
|
83
|
+
:icon="triggerIcon"
|
|
84
|
+
:color="triggerColor"
|
|
85
|
+
:variant="triggerVariant"
|
|
86
|
+
:size="triggerSize"
|
|
87
|
+
/>
|
|
88
|
+
</slot>
|
|
89
|
+
|
|
90
|
+
<template #header>
|
|
91
|
+
<div class="flex items-center justify-between w-full">
|
|
92
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
|
93
|
+
{{ title }}
|
|
94
|
+
</h3>
|
|
95
|
+
<UButton
|
|
96
|
+
icon="i-lucide-x"
|
|
97
|
+
color="neutral"
|
|
98
|
+
variant="ghost"
|
|
99
|
+
size="sm"
|
|
100
|
+
@click="isOpen = false"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
</template>
|
|
78
104
|
|
|
79
|
-
|
|
105
|
+
<template #body>
|
|
106
|
+
<div class="p-6 space-y-4">
|
|
80
107
|
<slot
|
|
81
108
|
:state="formData"
|
|
82
109
|
:loading="loading || crudLoading"
|
|
@@ -86,20 +113,19 @@ const handleSubmit = async (data: any = formData.value) => {
|
|
|
86
113
|
<!-- Default content if no slot -->
|
|
87
114
|
<form @submit.prevent="handleSubmit()">
|
|
88
115
|
<div class="space-y-4">
|
|
89
|
-
<!-- If we knew fields we'd render them, but this is generic -->
|
|
90
116
|
<p class="text-gray-500 italic">Form content goes here...</p>
|
|
91
117
|
</div>
|
|
92
118
|
</form>
|
|
93
119
|
</slot>
|
|
94
120
|
|
|
95
|
-
<div v-if="error || errorState" class="
|
|
121
|
+
<div v-if="error || errorState" class="p-3 rounded bg-red-50 text-red-600 text-sm">
|
|
96
122
|
{{ error || errorState }}
|
|
97
123
|
</div>
|
|
98
124
|
</div>
|
|
125
|
+
</template>
|
|
99
126
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
</UCard>
|
|
127
|
+
<template v-if="$slots.footer" #footer>
|
|
128
|
+
<slot name="footer" :submit="handleSubmit" :loading="loading || crudLoading" />
|
|
129
|
+
</template>
|
|
104
130
|
</UModal>
|
|
105
131
|
</template>
|
|
@@ -50,7 +50,7 @@ const widthClass = computed(() => {
|
|
|
50
50
|
case 'sm': return 'max-w-xs'
|
|
51
51
|
case 'md': return 'max-w-md'
|
|
52
52
|
case 'lg': return 'max-w-lg'
|
|
53
|
-
case 'xl': return 'max-w-2xl'
|
|
53
|
+
case 'xl': return 'max-w-2xl'
|
|
54
54
|
case 'full': return 'max-w-full'
|
|
55
55
|
default: return 'max-w-md'
|
|
56
56
|
}
|
|
@@ -74,36 +74,34 @@ const widthClass = computed(() => {
|
|
|
74
74
|
|
|
75
75
|
<!-- Slideover -->
|
|
76
76
|
<USlideover
|
|
77
|
-
v-model="isOpen"
|
|
77
|
+
v-model:open="isOpen"
|
|
78
78
|
:side="side"
|
|
79
|
-
:ui="{ width: widthClass
|
|
79
|
+
:ui="{ width: widthClass }"
|
|
80
80
|
>
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
<div class="px-6 py-4 flex items-center justify-between border-b border-gray-100 dark:border-gray-800">
|
|
81
|
+
<template #header>
|
|
82
|
+
<div class="flex items-center justify-between w-full">
|
|
84
83
|
<slot name="header">
|
|
85
84
|
<h2 class="text-lg font-bold text-gray-900 dark:text-white">{{ title }}</h2>
|
|
86
85
|
</slot>
|
|
87
|
-
<UButton
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
<UButton
|
|
87
|
+
icon="i-lucide-x"
|
|
88
|
+
color="neutral"
|
|
89
|
+
variant="ghost"
|
|
91
90
|
size="sm"
|
|
92
|
-
|
|
93
|
-
@click="close"
|
|
91
|
+
@click="close"
|
|
94
92
|
/>
|
|
95
93
|
</div>
|
|
94
|
+
</template>
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
<div class="flex-1 overflow-y-auto
|
|
96
|
+
<template #body>
|
|
97
|
+
<div class="flex-1 overflow-y-auto">
|
|
99
98
|
<slot />
|
|
100
99
|
</div>
|
|
100
|
+
</template>
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
102
|
+
<template v-if="$slots.footer" #footer>
|
|
103
|
+
<slot name="footer" />
|
|
104
|
+
</template>
|
|
107
105
|
</USlideover>
|
|
108
106
|
</div>
|
|
109
107
|
</template>
|
|
@@ -1,36 +1,69 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
const props = defineProps<{
|
|
2
|
+
const props = withDefaults(defineProps<{
|
|
3
3
|
title?: string
|
|
4
4
|
modelValue?: boolean
|
|
5
|
-
|
|
5
|
+
triggerLabel?: string
|
|
6
|
+
triggerIcon?: string
|
|
7
|
+
triggerColor?: 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'
|
|
8
|
+
triggerVariant?: 'solid' | 'outline' | 'soft' | 'ghost' | 'link'
|
|
9
|
+
triggerSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
10
|
+
}>(), {
|
|
11
|
+
modelValue: undefined,
|
|
12
|
+
triggerColor: 'primary',
|
|
13
|
+
triggerVariant: 'solid',
|
|
14
|
+
triggerSize: 'sm'
|
|
15
|
+
})
|
|
6
16
|
|
|
7
17
|
const emit = defineEmits(['update:modelValue'])
|
|
8
18
|
|
|
19
|
+
const internalOpen = ref(false)
|
|
20
|
+
|
|
9
21
|
const isOpen = computed({
|
|
10
|
-
get: () => props.modelValue,
|
|
11
|
-
set: (val) =>
|
|
22
|
+
get: () => props.modelValue ?? internalOpen.value,
|
|
23
|
+
set: (val) => {
|
|
24
|
+
emit('update:modelValue', val)
|
|
25
|
+
internalOpen.value = val
|
|
26
|
+
}
|
|
12
27
|
})
|
|
13
28
|
</script>
|
|
14
29
|
|
|
15
30
|
<template>
|
|
16
|
-
<UModal v-model="isOpen">
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
<UModal v-model:open="isOpen">
|
|
32
|
+
<!-- Trigger Button (required for Nuxt UI v4) -->
|
|
33
|
+
<slot name="trigger">
|
|
34
|
+
<UButton
|
|
35
|
+
v-if="triggerLabel || triggerIcon"
|
|
36
|
+
:label="triggerLabel"
|
|
37
|
+
:icon="triggerIcon"
|
|
38
|
+
:color="triggerColor"
|
|
39
|
+
:variant="triggerVariant"
|
|
40
|
+
:size="triggerSize"
|
|
41
|
+
/>
|
|
42
|
+
</slot>
|
|
43
|
+
|
|
44
|
+
<template #header>
|
|
45
|
+
<div class="flex items-center justify-between w-full">
|
|
46
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
|
47
|
+
{{ title }}
|
|
48
|
+
</h3>
|
|
49
|
+
<UButton
|
|
50
|
+
icon="i-lucide-x"
|
|
51
|
+
color="neutral"
|
|
52
|
+
variant="ghost"
|
|
53
|
+
size="sm"
|
|
54
|
+
@click="isOpen = false"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
26
58
|
|
|
59
|
+
<template #body>
|
|
27
60
|
<div class="p-4">
|
|
28
61
|
<slot />
|
|
29
62
|
</div>
|
|
63
|
+
</template>
|
|
30
64
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</UCard>
|
|
65
|
+
<template v-if="$slots.footer" #footer>
|
|
66
|
+
<slot name="footer" />
|
|
67
|
+
</template>
|
|
35
68
|
</UModal>
|
|
36
69
|
</template>
|
|
@@ -11,8 +11,8 @@ export function useVToast() {
|
|
|
11
11
|
title,
|
|
12
12
|
description,
|
|
13
13
|
icon: 'i-lucide-check-circle',
|
|
14
|
-
color: '
|
|
15
|
-
timeout
|
|
14
|
+
color: 'success',
|
|
15
|
+
duration: timeout
|
|
16
16
|
})
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -21,8 +21,8 @@ export function useVToast() {
|
|
|
21
21
|
title,
|
|
22
22
|
description,
|
|
23
23
|
icon: 'i-lucide-info',
|
|
24
|
-
color: '
|
|
25
|
-
timeout
|
|
24
|
+
color: 'info',
|
|
25
|
+
duration: timeout
|
|
26
26
|
})
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -31,8 +31,8 @@ export function useVToast() {
|
|
|
31
31
|
title,
|
|
32
32
|
description,
|
|
33
33
|
icon: 'i-lucide-alert-circle',
|
|
34
|
-
color: '
|
|
35
|
-
timeout
|
|
34
|
+
color: 'error',
|
|
35
|
+
duration: timeout
|
|
36
36
|
})
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -41,8 +41,8 @@ export function useVToast() {
|
|
|
41
41
|
title,
|
|
42
42
|
description,
|
|
43
43
|
icon: 'i-lucide-alert-triangle',
|
|
44
|
-
color: '
|
|
45
|
-
timeout
|
|
44
|
+
color: 'warning',
|
|
45
|
+
duration: timeout
|
|
46
46
|
})
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -50,7 +50,7 @@ export function useVToast() {
|
|
|
50
50
|
err: any,
|
|
51
51
|
friendlyName: string,
|
|
52
52
|
action: string,
|
|
53
|
-
successColor: string = '
|
|
53
|
+
successColor: string = 'success'
|
|
54
54
|
) => {
|
|
55
55
|
if (err.value) {
|
|
56
56
|
error(
|
|
@@ -63,7 +63,7 @@ export function useVToast() {
|
|
|
63
63
|
title: `${friendlyName} ${action} successful`,
|
|
64
64
|
icon: 'i-lucide-check-circle',
|
|
65
65
|
color: successColor as any,
|
|
66
|
-
timeout
|
|
66
|
+
duration: timeout
|
|
67
67
|
})
|
|
68
68
|
return true
|
|
69
69
|
}
|
package/app/layouts/default.vue
CHANGED
|
@@ -106,7 +106,7 @@ const groups = computed(() => [
|
|
|
106
106
|
</script>
|
|
107
107
|
|
|
108
108
|
<template>
|
|
109
|
-
<UDashboardGroup unit="rem">
|
|
109
|
+
<UDashboardGroup unit="rem" class="h-full overflow-hidden">
|
|
110
110
|
<UDashboardSidebar
|
|
111
111
|
resizable
|
|
112
112
|
collapsible
|
|
@@ -173,7 +173,9 @@ const groups = computed(() => [
|
|
|
173
173
|
|
|
174
174
|
<UDashboardSearch :groups="groups" />
|
|
175
175
|
|
|
176
|
-
<
|
|
176
|
+
<div class="flex-1 overflow-y-auto">
|
|
177
|
+
<slot />
|
|
178
|
+
</div>
|
|
177
179
|
<VaLayoutNotificationsSlideover />
|
|
178
180
|
</UDashboardGroup>
|
|
179
181
|
</template>
|