@innertia-solutions/innertia-nuxt 0.1.12 → 0.1.13
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/components/Attention.vue +269 -0
- package/components/Chart/index.vue +193 -0
- package/components/Container.vue +116 -0
- package/components/CopyButton.vue +98 -0
- package/components/Files/Card.vue +215 -0
- package/components/Files/Preview.vue +675 -0
- package/components/Files/Upload.vue +408 -0
- package/components/Files/UploadModal.vue +83 -0
- package/components/Forms/InputPin.vue +162 -0
- package/components/InfoToolbar.vue +54 -0
- package/components/Kanban/Standard.vue +588 -0
- package/components/Loader.vue +358 -0
- package/components/Modal/Base.vue +6 -0
- package/components/Modal/Confirm.vue +89 -0
- package/components/Modal/DeleteConfirm.vue +14 -31
- package/components/Modal/Form.vue +76 -0
- package/components/Modal/Host.vue +58 -0
- package/components/Modal/Prompt.vue +74 -0
- package/components/Modal.vue +119 -34
- package/components/Table/Standard.vue +198 -25
- package/components/Table/index.vue +169 -369
- package/components/Table/views/ViewCards.vue +74 -0
- package/components/Table/views/ViewDatabase.vue +236 -0
- package/components/Table/views/ViewTable.vue +299 -0
- package/components/Toast/Alert.vue +16 -111
- package/components/Toast/Container.vue +4 -29
- package/components/Toast/Notification.vue +8 -43
- package/components/Toast/Process.vue +8 -86
- package/components/Toast/Stack.vue +59 -0
- package/components/Toast/Standard.vue +115 -0
- package/components/Tree/Standard.vue +49 -4
- package/components/Workflow/Board.vue +356 -0
- package/components/Workflow/Builder.vue +454 -0
- package/components/Workflow/Form.vue +526 -0
- package/components/Workflow/Viewer.vue +225 -0
- package/components/Workflow/edges/TransitionEdge.vue +84 -0
- package/components/Workflow/nodes/StepNode.vue +88 -0
- package/composables/useClipboard.js +79 -0
- package/composables/useConfetti.js +194 -0
- package/composables/useFile.js +189 -0
- package/composables/useModal.js +71 -0
- package/composables/useToast.js +38 -65
- package/composables/useWorkflow/form.js +162 -0
- package/composables/useWorkflow/index.js +18 -0
- package/composables/useWorkflow/view.js +220 -0
- package/composables/useWorkflowLayout.js +162 -0
- package/css/tokens.css +10 -10
- package/nuxt.config.ts +1 -1
- package/package.json +13 -8
- package/stores/modal.js +48 -0
- package/stores/toast.js +156 -117
|
@@ -1,113 +1,18 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="max-w-xs relative bg-card border border-card-line rounded-xl shadow-lg p-4 pr-10 flex items-start overflow-hidden"
|
|
4
|
-
:class="{
|
|
5
|
-
'border-green-200': toast.severity === 'success',
|
|
6
|
-
'border-red-200': toast.severity === 'danger',
|
|
7
|
-
'border-yellow-200': toast.severity === 'warning',
|
|
8
|
-
'border-blue-200': toast.severity === 'info',
|
|
9
|
-
}"
|
|
10
|
-
role="alert"
|
|
11
|
-
>
|
|
12
|
-
<div class="mr-3 mt-1">
|
|
13
|
-
<i v-if="toast.icon" :class="toast.icon + ' text-gray-400 text-xl'" />
|
|
14
|
-
</div>
|
|
15
|
-
<div class="flex-1 mt-1">
|
|
16
|
-
<h3 v-if="toast.title" class="font-semibold text-sm text-gray-800">
|
|
17
|
-
{{ toast.title }}
|
|
18
|
-
</h3>
|
|
19
|
-
<div class="text-sm dark:text-white text-gray-600" v-html="toast.message"></div>
|
|
20
|
-
</div>
|
|
21
|
-
<button
|
|
22
|
-
class="ml-3 text-gray-400 hover:text-gray-700 absolute top-2 right-2"
|
|
23
|
-
@click="$emit('close')"
|
|
24
|
-
>
|
|
25
|
-
<span class="sr-only">Cerrar</span>
|
|
26
|
-
<svg
|
|
27
|
-
class="size-4"
|
|
28
|
-
viewBox="0 0 24 24"
|
|
29
|
-
fill="none"
|
|
30
|
-
stroke="currentColor"
|
|
31
|
-
stroke-width="2"
|
|
32
|
-
stroke-linecap="round"
|
|
33
|
-
stroke-linejoin="round"
|
|
34
|
-
>
|
|
35
|
-
<path d="M18 6 6 18" />
|
|
36
|
-
<path d="m6 6 12 12" />
|
|
37
|
-
</svg>
|
|
38
|
-
</button>
|
|
39
|
-
|
|
40
|
-
<!-- Barra de progreso temporal -->
|
|
41
|
-
<div
|
|
42
|
-
v-if="toast.duration && toast.duration > 0"
|
|
43
|
-
class="absolute bottom-0 left-0 w-full h-1 bg-card-line"
|
|
44
|
-
>
|
|
45
|
-
<div
|
|
46
|
-
class="h-full bg-gradient-to-r"
|
|
47
|
-
:class="{
|
|
48
|
-
'from-green-400 to-green-600': toast.severity === 'success',
|
|
49
|
-
'from-red-400 to-red-600': toast.severity === 'danger',
|
|
50
|
-
'from-yellow-400 to-yellow-600': toast.severity === 'warning',
|
|
51
|
-
'from-blue-400 to-blue-600': toast.severity === 'info',
|
|
52
|
-
}"
|
|
53
|
-
:style="{ width: progressWidth + '%' }"
|
|
54
|
-
></div>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</template>
|
|
58
|
-
|
|
59
1
|
<script setup>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const updateProgress = () => {
|
|
73
|
-
if (!startTime.value || duration.value <= 0) {
|
|
74
|
-
progressWidth.value = 100
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const elapsed = Date.now() - startTime.value
|
|
79
|
-
const remaining = Math.max(0, duration.value - elapsed)
|
|
80
|
-
progressWidth.value = (remaining / duration.value) * 100
|
|
81
|
-
|
|
82
|
-
if (remaining > 0) {
|
|
83
|
-
animationFrame = requestAnimationFrame(updateProgress)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Watch para reiniciar cuando cambie el duration
|
|
88
|
-
watch(duration, (newDuration) => {
|
|
89
|
-
if (animationFrame) {
|
|
90
|
-
cancelAnimationFrame(animationFrame)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (newDuration && newDuration > 0) {
|
|
94
|
-
startTime.value = Date.now()
|
|
95
|
-
updateProgress()
|
|
96
|
-
} else {
|
|
97
|
-
progressWidth.value = 100
|
|
98
|
-
}
|
|
99
|
-
}, { immediate: true })
|
|
100
|
-
|
|
101
|
-
onMounted(() => {
|
|
102
|
-
if (duration.value > 0) {
|
|
103
|
-
startTime.value = Date.now()
|
|
104
|
-
updateProgress()
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
onUnmounted(() => {
|
|
109
|
-
if (animationFrame) {
|
|
110
|
-
cancelAnimationFrame(animationFrame)
|
|
111
|
-
}
|
|
112
|
-
})
|
|
2
|
+
/**
|
|
3
|
+
* <ToastAlert> — Wrapper deprecated. Para uso nuevo: <ToastStandard>.
|
|
4
|
+
* El store ahora maneja toast.severity normalizada (success|error|warning|info).
|
|
5
|
+
*/
|
|
6
|
+
const props = defineProps({ toast: { type: Object, required: true } })
|
|
7
|
+
const emit = defineEmits(['close'])
|
|
8
|
+
|
|
9
|
+
// Map legacy severity 'danger' → 'error' para retrocompat.
|
|
10
|
+
const normalized = computed(() => ({
|
|
11
|
+
...props.toast,
|
|
12
|
+
severity: props.toast.severity === 'danger' ? 'error' : props.toast.severity,
|
|
13
|
+
}))
|
|
113
14
|
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<ToastStandard :toast="normalized" @close="emit('close')" />
|
|
18
|
+
</template>
|
|
@@ -1,34 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
'top-left', 'top-center', 'top-right',
|
|
6
|
-
'bottom-left', 'bottom-center', 'bottom-right',
|
|
7
|
-
]
|
|
8
|
-
|
|
9
|
-
const positionClass = {
|
|
10
|
-
'top-left': 'top-4 left-4',
|
|
11
|
-
'top-center': 'top-4 left-1/2 -translate-x-1/2',
|
|
12
|
-
'top-right': 'top-4 right-4',
|
|
13
|
-
'bottom-left': 'bottom-4 left-4',
|
|
14
|
-
'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
|
|
15
|
-
'bottom-right': 'bottom-4 right-4',
|
|
16
|
-
}
|
|
2
|
+
/**
|
|
3
|
+
* <ToastContainer /> — Wrapper deprecated. Para uso nuevo: <ToastStack />.
|
|
4
|
+
*/
|
|
17
5
|
</script>
|
|
18
6
|
|
|
19
7
|
<template>
|
|
20
|
-
<
|
|
21
|
-
<div
|
|
22
|
-
v-if="toastStore.toasts[pos]?.length"
|
|
23
|
-
class="fixed z-50 flex flex-col gap-2"
|
|
24
|
-
:class="positionClass[pos]"
|
|
25
|
-
>
|
|
26
|
-
<ToastAlert
|
|
27
|
-
v-for="toast in toastStore.toasts[pos]"
|
|
28
|
-
:key="toast.id"
|
|
29
|
-
:toast="toast"
|
|
30
|
-
@close="toastStore.remove(toast.id)"
|
|
31
|
-
/>
|
|
32
|
-
</div>
|
|
33
|
-
</template>
|
|
8
|
+
<ToastStack />
|
|
34
9
|
</template>
|
|
@@ -1,45 +1,10 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="max-w-xs bg-white border border-gray-200 rounded-xl shadow-lg p-4 flex"
|
|
4
|
-
role="alert"
|
|
5
|
-
>
|
|
6
|
-
<div class="shrink-0">
|
|
7
|
-
<svg
|
|
8
|
-
class="size-5 text-gray-600 mt-1"
|
|
9
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
-
width="24"
|
|
11
|
-
height="24"
|
|
12
|
-
viewBox="0 0 24 24"
|
|
13
|
-
fill="none"
|
|
14
|
-
stroke="currentColor"
|
|
15
|
-
stroke-width="2"
|
|
16
|
-
stroke-linecap="round"
|
|
17
|
-
stroke-linejoin="round"
|
|
18
|
-
>
|
|
19
|
-
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
|
|
20
|
-
<path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
|
|
21
|
-
</svg>
|
|
22
|
-
</div>
|
|
23
|
-
<div class="ms-4">
|
|
24
|
-
<h3 v-if="toast.title" class="text-gray-800 font-semibold">
|
|
25
|
-
{{ toast.title }}
|
|
26
|
-
</h3>
|
|
27
|
-
<div class="mt-1 text-sm text-gray-600" v-html="toast.message"></div>
|
|
28
|
-
<div v-if="toast.actions" class="mt-4">
|
|
29
|
-
<div class="flex gap-x-3">
|
|
30
|
-
<button
|
|
31
|
-
v-for="(action, i) in toast.actions"
|
|
32
|
-
:key="i"
|
|
33
|
-
@click="action.onClick"
|
|
34
|
-
class="text-blue-600 decoration-2 hover:underline font-medium text-sm focus:outline-hidden focus:underline"
|
|
35
|
-
>
|
|
36
|
-
{{ action.label }}
|
|
37
|
-
</button>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</template>
|
|
43
1
|
<script setup>
|
|
44
|
-
|
|
2
|
+
/** Deprecated. Para uso nuevo: <ToastStandard variant="notification">. */
|
|
3
|
+
const props = defineProps({ toast: { type: Object, required: true } })
|
|
4
|
+
const emit = defineEmits(['close'])
|
|
5
|
+
const normalized = computed(() => ({ ...props.toast, variant: 'notification' }))
|
|
45
6
|
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<ToastStandard :toast="normalized" @close="emit('close')" />
|
|
10
|
+
</template>
|
|
@@ -1,88 +1,10 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="max-w-xs relative bg-white border border-gray-200 rounded-xl shadow-lg"
|
|
4
|
-
role="alert"
|
|
5
|
-
>
|
|
6
|
-
<div class="flex gap-x-3 p-4">
|
|
7
|
-
<div class="shrink-0">
|
|
8
|
-
<span
|
|
9
|
-
class="m-1 inline-flex justify-center items-center size-8 rounded-full bg-gray-100 text-gray-800"
|
|
10
|
-
>
|
|
11
|
-
<svg
|
|
12
|
-
class="shrink-0 size-4"
|
|
13
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
-
width="24"
|
|
15
|
-
height="24"
|
|
16
|
-
viewBox="0 0 24 24"
|
|
17
|
-
fill="none"
|
|
18
|
-
stroke="currentColor"
|
|
19
|
-
stroke-width="2"
|
|
20
|
-
stroke-linecap="round"
|
|
21
|
-
stroke-linejoin="round"
|
|
22
|
-
>
|
|
23
|
-
<path
|
|
24
|
-
d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"
|
|
25
|
-
/>
|
|
26
|
-
<path d="M12 12v9" />
|
|
27
|
-
<path d="m16 16-4-4-4 4" />
|
|
28
|
-
</svg>
|
|
29
|
-
</span>
|
|
30
|
-
<button
|
|
31
|
-
class="absolute top-3 end-3 inline-flex shrink-0 justify-center items-center size-5 rounded-lg text-gray-800 opacity-50 hover:opacity-100 focus:outline-hidden focus:opacity-100"
|
|
32
|
-
@click="$emit('close')"
|
|
33
|
-
aria-label="Close"
|
|
34
|
-
>
|
|
35
|
-
<span class="sr-only">Close</span>
|
|
36
|
-
<svg
|
|
37
|
-
class="shrink-0 size-4"
|
|
38
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
39
|
-
width="24"
|
|
40
|
-
height="24"
|
|
41
|
-
viewBox="0 0 24 24"
|
|
42
|
-
fill="none"
|
|
43
|
-
stroke="currentColor"
|
|
44
|
-
stroke-width="2"
|
|
45
|
-
stroke-linecap="round"
|
|
46
|
-
stroke-linejoin="round"
|
|
47
|
-
>
|
|
48
|
-
<path d="M18 6 6 18" />
|
|
49
|
-
<path d="m6 6 12 12" />
|
|
50
|
-
</svg>
|
|
51
|
-
</button>
|
|
52
|
-
</div>
|
|
53
|
-
<div class="grow me-5">
|
|
54
|
-
<h3 v-if="toast.title" class="text-gray-800 font-medium text-sm">
|
|
55
|
-
{{ toast.title }}
|
|
56
|
-
</h3>
|
|
57
|
-
<div
|
|
58
|
-
v-if="toast.progress !== undefined"
|
|
59
|
-
class="mt-2 flex flex-col gap-x-3"
|
|
60
|
-
>
|
|
61
|
-
<span class="block mb-1.5 text-xs text-gray-500">{{
|
|
62
|
-
toast.progressLabel
|
|
63
|
-
}}</span>
|
|
64
|
-
<div
|
|
65
|
-
class="flex w-full h-1 bg-gray-200 rounded-full overflow-hidden"
|
|
66
|
-
role="progressbar"
|
|
67
|
-
:aria-valuenow="toast.progress"
|
|
68
|
-
aria-valuemin="0"
|
|
69
|
-
aria-valuemax="100"
|
|
70
|
-
>
|
|
71
|
-
<div
|
|
72
|
-
class="flex flex-col justify-center overflow-hidden bg-blue-600 text-xs text-white text-center whitespace-nowrap"
|
|
73
|
-
:style="{ width: toast.progress + '%' }"
|
|
74
|
-
></div>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
<div
|
|
78
|
-
v-else
|
|
79
|
-
class="mt-2 text-sm text-gray-600"
|
|
80
|
-
v-html="toast.message"
|
|
81
|
-
></div>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
</template>
|
|
86
1
|
<script setup>
|
|
87
|
-
|
|
2
|
+
/** Deprecated. Para uso nuevo: <ToastStandard variant="process">. */
|
|
3
|
+
const props = defineProps({ toast: { type: Object, required: true } })
|
|
4
|
+
const emit = defineEmits(['close'])
|
|
5
|
+
const normalized = computed(() => ({ ...props.toast, variant: 'process' }))
|
|
88
6
|
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<ToastStandard :toast="normalized" @close="emit('close')" />
|
|
10
|
+
</template>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* <ToastStack /> — Mount-once en el layout root. Renderiza los toasts del
|
|
4
|
+
* store en las 6 posiciones, con animación stack.
|
|
5
|
+
*/
|
|
6
|
+
import { useToastStore } from '../../stores/toast'
|
|
7
|
+
|
|
8
|
+
const store = useToastStore()
|
|
9
|
+
|
|
10
|
+
const POSITIONS = [
|
|
11
|
+
'top-left', 'top-center', 'top-right',
|
|
12
|
+
'bottom-left', 'bottom-center', 'bottom-right',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const positionClass = {
|
|
16
|
+
'top-left': 'top-4 left-4',
|
|
17
|
+
'top-center': 'top-4 left-1/2 -translate-x-1/2',
|
|
18
|
+
'top-right': 'top-4 right-4',
|
|
19
|
+
'bottom-left': 'bottom-4 left-4',
|
|
20
|
+
'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
|
|
21
|
+
'bottom-right': 'bottom-4 right-4',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const isTop = (pos) => pos.startsWith('top-')
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<template v-for="pos in POSITIONS" :key="pos">
|
|
29
|
+
<div
|
|
30
|
+
v-if="store.toasts[pos]?.length"
|
|
31
|
+
class="fixed z-[9998] flex flex-col gap-2 pointer-events-none"
|
|
32
|
+
:class="positionClass[pos]"
|
|
33
|
+
>
|
|
34
|
+
<TransitionGroup :name="isTop(pos) ? 'toast-down' : 'toast-up'">
|
|
35
|
+
<ToastStandard
|
|
36
|
+
v-for="toast in store.toasts[pos]"
|
|
37
|
+
:key="toast.id"
|
|
38
|
+
:toast="toast"
|
|
39
|
+
@close="store.dismiss(toast.id)"
|
|
40
|
+
/>
|
|
41
|
+
</TransitionGroup>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<style scoped>
|
|
47
|
+
.toast-down-enter-active,
|
|
48
|
+
.toast-up-enter-active,
|
|
49
|
+
.toast-down-leave-active,
|
|
50
|
+
.toast-up-leave-active { transition: all 0.25s cubic-bezier(.2,.8,.2,1); }
|
|
51
|
+
|
|
52
|
+
.toast-down-enter-from { opacity: 0; transform: translateY(-12px) scale(0.97); }
|
|
53
|
+
.toast-up-enter-from { opacity: 0; transform: translateY(12px) scale(0.97); }
|
|
54
|
+
.toast-down-leave-to,
|
|
55
|
+
.toast-up-leave-to { opacity: 0; transform: scale(0.95); }
|
|
56
|
+
|
|
57
|
+
.toast-down-move,
|
|
58
|
+
.toast-up-move { transition: transform 0.25s ease; }
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import {
|
|
3
|
+
IconCircleCheck, IconAlertCircle, IconAlertTriangle, IconInfoCircle, IconX,
|
|
4
|
+
} from '@tabler/icons-vue'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* <ToastStandard> — Renderizador unificado de toasts.
|
|
8
|
+
*
|
|
9
|
+
* Variants: 'alert' (default) | 'notification' | 'process'
|
|
10
|
+
* Severities: success | error | warning | info
|
|
11
|
+
*
|
|
12
|
+
* El auto-dismiss se visualiza como un anillo circular alrededor del botón de
|
|
13
|
+
* cerrar (usando <Loader>).
|
|
14
|
+
*/
|
|
15
|
+
const props = defineProps({
|
|
16
|
+
toast: { type: Object, required: true },
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits(['close'])
|
|
20
|
+
|
|
21
|
+
const meta = computed(() => ({
|
|
22
|
+
success: { icon: IconCircleCheck, accent: 'text-emerald-600 dark:text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-l-emerald-500', loader: 'emerald' },
|
|
23
|
+
error: { icon: IconAlertCircle, accent: 'text-red-600 dark:text-red-400', bg: 'bg-red-500/10', border: 'border-l-red-500', loader: 'red' },
|
|
24
|
+
warning: { icon: IconAlertTriangle, accent: 'text-amber-600 dark:text-amber-400', bg: 'bg-amber-500/10', border: 'border-l-amber-500', loader: 'amber' },
|
|
25
|
+
info: { icon: IconInfoCircle, accent: 'text-blue-600 dark:text-blue-400', bg: 'bg-blue-500/10', border: 'border-l-blue-500', loader: 'blue' },
|
|
26
|
+
}[props.toast.severity] ?? {
|
|
27
|
+
icon: IconInfoCircle, accent: 'text-muted-foreground', bg: 'bg-muted', border: 'border-l-muted-foreground', loader: 'muted',
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
const isNotification = computed(() => props.toast.variant === 'notification')
|
|
31
|
+
const isProcess = computed(() => props.toast.variant === 'process')
|
|
32
|
+
const hasCountdown = computed(() => !isProcess.value && props.toast.duration && props.toast.duration > 0)
|
|
33
|
+
|
|
34
|
+
const onAction = () => {
|
|
35
|
+
try { props.toast.action?.onClick?.() } finally {
|
|
36
|
+
if (props.toast.action?.dismissOnClick !== false) emit('close')
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div
|
|
43
|
+
role="alert"
|
|
44
|
+
:class="[
|
|
45
|
+
'pointer-events-auto relative w-[22rem] max-w-[90vw] bg-card border border-card-line border-l-4 rounded-card shadow-lg overflow-hidden',
|
|
46
|
+
'flex gap-3',
|
|
47
|
+
isProcess ? 'items-start' : 'items-center',
|
|
48
|
+
isNotification ? 'p-4 pr-12' : 'p-3 pr-11',
|
|
49
|
+
meta.border,
|
|
50
|
+
]"
|
|
51
|
+
>
|
|
52
|
+
<!-- Icon -->
|
|
53
|
+
<div :class="['shrink-0 size-8 rounded-control flex items-center justify-center', meta.bg, meta.accent]">
|
|
54
|
+
<component :is="meta.icon" class="size-4" :stroke="1.75" />
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Contenido -->
|
|
58
|
+
<div class="flex-1 min-w-0">
|
|
59
|
+
<p v-if="toast.title" class="text-sm font-semibold text-foreground truncate">{{ toast.title }}</p>
|
|
60
|
+
<div
|
|
61
|
+
v-if="toast.message"
|
|
62
|
+
:class="['text-foreground leading-relaxed', toast.title ? 'text-xs text-muted-foreground mt-0.5' : 'text-sm']"
|
|
63
|
+
v-html="toast.message"
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<!-- Process progress (variant 'process') -->
|
|
67
|
+
<div v-if="isProcess" class="mt-2">
|
|
68
|
+
<Loader
|
|
69
|
+
shape="bar"
|
|
70
|
+
:value="toast.progress ?? 0"
|
|
71
|
+
:stroke="4"
|
|
72
|
+
:color="meta.loader"
|
|
73
|
+
/>
|
|
74
|
+
<div class="flex items-center justify-between mt-1.5 text-[10px] text-muted-foreground font-mono">
|
|
75
|
+
<span>{{ toast.progressLabel || '' }}</span>
|
|
76
|
+
<span>{{ Math.round(toast.progress ?? 0) }}%</span>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Action -->
|
|
81
|
+
<button
|
|
82
|
+
v-if="toast.action?.label"
|
|
83
|
+
type="button"
|
|
84
|
+
class="mt-2 text-xs font-semibold text-primary hover:text-primary-hover transition-colors"
|
|
85
|
+
@click="onAction"
|
|
86
|
+
>
|
|
87
|
+
{{ toast.action.label }}
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Close + countdown ring -->
|
|
92
|
+
<button
|
|
93
|
+
v-if="toast.closable !== false"
|
|
94
|
+
type="button"
|
|
95
|
+
class="absolute top-1/2 -translate-y-1/2 right-2 size-8 inline-flex items-center justify-center rounded-full text-foreground hover:text-foreground transition-colors group"
|
|
96
|
+
aria-label="Cerrar"
|
|
97
|
+
@click="emit('close')"
|
|
98
|
+
>
|
|
99
|
+
<Loader
|
|
100
|
+
v-if="hasCountdown"
|
|
101
|
+
shape="circle"
|
|
102
|
+
mode="countdown"
|
|
103
|
+
direction="empty"
|
|
104
|
+
:duration="toast.duration"
|
|
105
|
+
:size="32"
|
|
106
|
+
:stroke="7"
|
|
107
|
+
:color="meta.loader"
|
|
108
|
+
track="auto"
|
|
109
|
+
class="!absolute inset-0 pointer-events-none"
|
|
110
|
+
@complete="emit('close')"
|
|
111
|
+
/>
|
|
112
|
+
<IconX class="size-4 relative z-10 text-foreground group-hover:scale-110 transition-transform" :stroke="2.25" />
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
</template>
|
|
@@ -53,6 +53,20 @@ const props = defineProps({
|
|
|
53
53
|
// 'list' — compacto tipo file-tree explorer (sin tabla, sin bordes de fila)
|
|
54
54
|
variant: { type: String, default: 'table' },
|
|
55
55
|
|
|
56
|
+
// bordered=false → quita border + rounded-card del wrapper exterior
|
|
57
|
+
bordered: { type: Boolean, default: true },
|
|
58
|
+
|
|
59
|
+
// 'bottom' (default) | 'top' | 'none' — dónde aparecen reload + total + instant
|
|
60
|
+
// 'none' = sin footer, reload + instant compactos al lado de columns/export
|
|
61
|
+
infoPosition: { type: String, default: 'bottom' },
|
|
62
|
+
|
|
63
|
+
// Tamaño visible del área scrollable.
|
|
64
|
+
// 'sm' — compacta (min 18rem / max 24rem)
|
|
65
|
+
// 'md' — default (min 30rem / max 36rem)
|
|
66
|
+
// 'lg' — espaciosa (min 45rem / max 75vh)
|
|
67
|
+
// 'fit' — sin min/max, se adapta al contenedor padre
|
|
68
|
+
size: { type: String, default: 'md' },
|
|
69
|
+
|
|
56
70
|
// ─── Preview overlay panel (mismo patrón que Table.Standard) ───────────
|
|
57
71
|
// El panel se activa automáticamente si el consumer declara <slot name="preview">.
|
|
58
72
|
// Click en una fila → set previewRow → overlay absoluto sobre la derecha de la tabla.
|
|
@@ -63,6 +77,16 @@ const props = defineProps({
|
|
|
63
77
|
splitRatio: { type: Number, default: 55 },
|
|
64
78
|
})
|
|
65
79
|
|
|
80
|
+
const sizeStyle = computed(() => {
|
|
81
|
+
switch (props.size) {
|
|
82
|
+
case 'sm': return { minHeight: '18rem', maxHeight: '24rem' }
|
|
83
|
+
case 'lg': return { minHeight: '45rem', maxHeight: '75vh' }
|
|
84
|
+
case 'fit': return {}
|
|
85
|
+
case 'md':
|
|
86
|
+
default: return { minHeight: '30rem', maxHeight: '36rem' }
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
66
90
|
const emit = defineEmits([
|
|
67
91
|
'row-click', 'loaded', 'expand',
|
|
68
92
|
'update:checked', 'export',
|
|
@@ -567,6 +591,15 @@ defineExpose({
|
|
|
567
591
|
<!-- Secondary actions -->
|
|
568
592
|
<div class="ml-auto flex items-center gap-1">
|
|
569
593
|
|
|
594
|
+
<!-- Instant + Reload SIEMPRE primero cuando infoPosition === 'none' -->
|
|
595
|
+
<InfoToolbar
|
|
596
|
+
v-if="infoPosition === 'none'"
|
|
597
|
+
:show-reload="showReloadButton"
|
|
598
|
+
:is-fetching="isFetching"
|
|
599
|
+
:show-instant="isDataFromCache && cached"
|
|
600
|
+
@reload="fetchInitial"
|
|
601
|
+
/>
|
|
602
|
+
|
|
570
603
|
<!-- Columns dropdown -->
|
|
571
604
|
<div v-if="showColumns" class="relative">
|
|
572
605
|
<button
|
|
@@ -617,6 +650,7 @@ defineExpose({
|
|
|
617
650
|
</Transition>
|
|
618
651
|
</div>
|
|
619
652
|
|
|
653
|
+
|
|
620
654
|
<!-- Export dropdown -->
|
|
621
655
|
<div v-if="showExport" class="relative">
|
|
622
656
|
<button
|
|
@@ -714,9 +748,12 @@ defineExpose({
|
|
|
714
748
|
|
|
715
749
|
<!-- ── Table wrapper (rounded + scroll) ───────────────────────────────── -->
|
|
716
750
|
<div
|
|
717
|
-
class="
|
|
751
|
+
:class="[
|
|
752
|
+
'flex flex-col w-full',
|
|
753
|
+
bordered ? 'border border-card-line rounded-card overflow-hidden' : '',
|
|
754
|
+
]"
|
|
718
755
|
>
|
|
719
|
-
<div class="overflow-auto" style="
|
|
756
|
+
<div class="overflow-auto" :style="sizeStyle">
|
|
720
757
|
|
|
721
758
|
<!-- ════════════ Variante 'table' ════════════ -->
|
|
722
759
|
<table v-if="variant === 'table'" class="w-full divide-y divide-card-line" style="table-layout: auto">
|
|
@@ -843,8 +880,16 @@ defineExpose({
|
|
|
843
880
|
</div>
|
|
844
881
|
</div>
|
|
845
882
|
|
|
846
|
-
<!-- ── Footer bar (reload + total + cache badge) —
|
|
847
|
-
<div
|
|
883
|
+
<!-- ── Footer bar (reload + total + cache badge) — top/bottom/none ── -->
|
|
884
|
+
<div
|
|
885
|
+
v-if="infoPosition !== 'none'"
|
|
886
|
+
:class="[
|
|
887
|
+
'flex flex-col sm:flex-row items-center justify-between gap-y-3 sm:gap-y-0 px-4 py-3',
|
|
888
|
+
infoPosition === 'top'
|
|
889
|
+
? (bordered ? 'order-first border-b border-card-line bg-card' : 'order-first pb-4')
|
|
890
|
+
: (bordered ? 'border-t border-card-line bg-card' : 'pt-4'),
|
|
891
|
+
]"
|
|
892
|
+
>
|
|
848
893
|
<div class="flex items-center gap-x-4 flex-wrap gap-y-2">
|
|
849
894
|
<!-- Reload button -->
|
|
850
895
|
<div v-if="showReloadButton" class="flex items-center gap-x-2">
|