@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.
Files changed (51) hide show
  1. package/components/Attention.vue +269 -0
  2. package/components/Chart/index.vue +193 -0
  3. package/components/Container.vue +116 -0
  4. package/components/CopyButton.vue +98 -0
  5. package/components/Files/Card.vue +215 -0
  6. package/components/Files/Preview.vue +675 -0
  7. package/components/Files/Upload.vue +408 -0
  8. package/components/Files/UploadModal.vue +83 -0
  9. package/components/Forms/InputPin.vue +162 -0
  10. package/components/InfoToolbar.vue +54 -0
  11. package/components/Kanban/Standard.vue +588 -0
  12. package/components/Loader.vue +358 -0
  13. package/components/Modal/Base.vue +6 -0
  14. package/components/Modal/Confirm.vue +89 -0
  15. package/components/Modal/DeleteConfirm.vue +14 -31
  16. package/components/Modal/Form.vue +76 -0
  17. package/components/Modal/Host.vue +58 -0
  18. package/components/Modal/Prompt.vue +74 -0
  19. package/components/Modal.vue +119 -34
  20. package/components/Table/Standard.vue +198 -25
  21. package/components/Table/index.vue +169 -369
  22. package/components/Table/views/ViewCards.vue +74 -0
  23. package/components/Table/views/ViewDatabase.vue +236 -0
  24. package/components/Table/views/ViewTable.vue +299 -0
  25. package/components/Toast/Alert.vue +16 -111
  26. package/components/Toast/Container.vue +4 -29
  27. package/components/Toast/Notification.vue +8 -43
  28. package/components/Toast/Process.vue +8 -86
  29. package/components/Toast/Stack.vue +59 -0
  30. package/components/Toast/Standard.vue +115 -0
  31. package/components/Tree/Standard.vue +49 -4
  32. package/components/Workflow/Board.vue +356 -0
  33. package/components/Workflow/Builder.vue +454 -0
  34. package/components/Workflow/Form.vue +526 -0
  35. package/components/Workflow/Viewer.vue +225 -0
  36. package/components/Workflow/edges/TransitionEdge.vue +84 -0
  37. package/components/Workflow/nodes/StepNode.vue +88 -0
  38. package/composables/useClipboard.js +79 -0
  39. package/composables/useConfetti.js +194 -0
  40. package/composables/useFile.js +189 -0
  41. package/composables/useModal.js +71 -0
  42. package/composables/useToast.js +38 -65
  43. package/composables/useWorkflow/form.js +162 -0
  44. package/composables/useWorkflow/index.js +18 -0
  45. package/composables/useWorkflow/view.js +220 -0
  46. package/composables/useWorkflowLayout.js +162 -0
  47. package/css/tokens.css +10 -10
  48. package/nuxt.config.ts +1 -1
  49. package/package.json +13 -8
  50. package/stores/modal.js +48 -0
  51. 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
- import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
61
-
62
- const props = defineProps({ toast: Object });
63
-
64
- const progressWidth = ref(100)
65
- const startTime = ref(null)
66
- let animationFrame = null
67
-
68
- // Computed para detectar cambios en duration
69
- const duration = computed(() => props.toast?.duration || 0)
70
-
71
- // Función para actualizar el progreso
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
- const toastStore = useToastStore()
3
-
4
- const positions = [
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
- <template v-for="pos in positions" :key="pos">
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
- defineProps({ toast: Object });
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
- defineProps({ toast: Object });
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="border border-card-line rounded-card overflow-hidden flex flex-col w-full"
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="min-height: 30rem; max-height: 36rem">
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) — DENTRO del wrapper ── -->
847
- <div class="flex flex-col sm:flex-row items-center justify-between gap-y-3 sm:gap-y-0 px-4 py-3 border-t border-card-line bg-card">
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">