@innertia-solutions/innertia-nuxt 0.1.11 → 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/CHANGELOG.md +38 -0
- package/app.config.ts +10 -10
- package/components/Attention.vue +269 -0
- package/components/Auth/LoginForm.vue +1 -1
- 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/MobileBlocker.vue +5 -5
- package/components/MobileLoginPicker.vue +13 -13
- 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/Organization/Picker.vue +3 -3
- 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/useAuth.js +1 -1
- package/composables/useClipboard.js +79 -0
- package/composables/useConfetti.js +194 -0
- package/composables/useContext.js +53 -5
- package/composables/useFile.js +189 -0
- package/composables/useMobileGuard.ts +21 -21
- package/composables/useModal.js +71 -0
- package/composables/useOrganization.ts +3 -3
- 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/docs/superpowers/plans/2026-05-27-nuxt-apps-to-contexts-rename.md +630 -0
- package/docs/superpowers/specs/2026-05-27-nuxt-apps-to-contexts-rename-design.md +113 -0
- package/middleware/{03.apps.global.ts → 03.contexts.global.ts} +25 -25
- package/nuxt.config.ts +1 -1
- package/package.json +13 -8
- package/stores/modal.js +48 -0
- package/stores/toast.js +156 -117
- package/composables/useApp.ts +0 -46
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { IconCopy, IconCheck } from '@tabler/icons-vue'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <CopyButton :value="..." /> — Botón con feedback visual de copia.
|
|
6
|
+
*
|
|
7
|
+
* Variants:
|
|
8
|
+
* - 'icon' (default) — solo el icono
|
|
9
|
+
* - 'inline' — input readonly con icono al lado (estilo Preline clipboard)
|
|
10
|
+
* - 'button' — botón ancho con label "Copiar"
|
|
11
|
+
*
|
|
12
|
+
* ```vue
|
|
13
|
+
* <CopyButton :value="user.id" />
|
|
14
|
+
* <CopyButton :value="apiKey" variant="inline" />
|
|
15
|
+
* <CopyButton :value="link" variant="button" label="Copiar enlace" />
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
value: { type: [String, Number], default: '' },
|
|
20
|
+
variant: { type: String, default: 'icon', validator: v => ['icon','inline','button'].includes(v) },
|
|
21
|
+
label: { type: String, default: 'Copiar' },
|
|
22
|
+
labelCopied: { type: String, default: '¡Copiado!' },
|
|
23
|
+
size: { type: String, default: 'sm', validator: v => ['xs','sm','md'].includes(v) },
|
|
24
|
+
/** Solo para variant="inline" — placeholder cuando el value está vacío. */
|
|
25
|
+
placeholder: { type: String, default: '' },
|
|
26
|
+
/** Solo para variant="inline" — oculta el value (útil para tokens/passwords). */
|
|
27
|
+
masked: { type: Boolean, default: false },
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const emit = defineEmits(['copied'])
|
|
31
|
+
|
|
32
|
+
const { copy, copied } = useClipboard({ timeout: 1500 })
|
|
33
|
+
|
|
34
|
+
const onCopy = async () => {
|
|
35
|
+
if (await copy(props.value)) emit('copied', props.value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const iconSize = computed(() => ({ xs: 12, sm: 14, md: 16 }[props.size]))
|
|
39
|
+
const btnSize = computed(() => ({ xs: 'size-6', sm: 'size-7', md: 'size-8' }[props.size]))
|
|
40
|
+
|
|
41
|
+
const displayValue = computed(() => {
|
|
42
|
+
if (!props.value) return ''
|
|
43
|
+
if (!props.masked) return String(props.value)
|
|
44
|
+
return String(props.value).replace(/./g, '•').slice(0, 32)
|
|
45
|
+
})
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<!-- Icon-only -->
|
|
50
|
+
<button
|
|
51
|
+
v-if="variant === 'icon'"
|
|
52
|
+
type="button"
|
|
53
|
+
:title="copied ? labelCopied : label"
|
|
54
|
+
:class="[btnSize, 'inline-flex items-center justify-center rounded-control transition-colors',
|
|
55
|
+
copied ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
|
|
56
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-muted-hover']"
|
|
57
|
+
@click="onCopy"
|
|
58
|
+
>
|
|
59
|
+
<IconCheck v-if="copied" :size="iconSize" :stroke="2" />
|
|
60
|
+
<IconCopy v-else :size="iconSize" :stroke="1.75" />
|
|
61
|
+
</button>
|
|
62
|
+
|
|
63
|
+
<!-- Inline input + icon (Preline clipboard style) -->
|
|
64
|
+
<div
|
|
65
|
+
v-else-if="variant === 'inline'"
|
|
66
|
+
class="inline-flex items-center gap-2 w-full innertia-field !pr-1 !py-1 cursor-pointer"
|
|
67
|
+
@click="onCopy"
|
|
68
|
+
>
|
|
69
|
+
<span class="flex-1 truncate text-sm text-foreground font-mono">
|
|
70
|
+
{{ displayValue || placeholder }}
|
|
71
|
+
</span>
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
:class="['shrink-0 inline-flex items-center justify-center size-7 rounded-control transition-colors',
|
|
75
|
+
copied ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
|
|
76
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-muted-hover']"
|
|
77
|
+
@click.stop="onCopy"
|
|
78
|
+
>
|
|
79
|
+
<IconCheck v-if="copied" :size="14" :stroke="2" />
|
|
80
|
+
<IconCopy v-else :size="14" :stroke="1.75" />
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<!-- Full button -->
|
|
85
|
+
<button
|
|
86
|
+
v-else
|
|
87
|
+
type="button"
|
|
88
|
+
:class="['inline-flex items-center gap-1.5 px-3 py-1.5 rounded-control text-xs font-medium transition-colors border',
|
|
89
|
+
copied
|
|
90
|
+
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/30'
|
|
91
|
+
: 'bg-card text-foreground border-card-line hover:bg-muted-hover']"
|
|
92
|
+
@click="onCopy"
|
|
93
|
+
>
|
|
94
|
+
<IconCheck v-if="copied" :size="14" :stroke="2" />
|
|
95
|
+
<IconCopy v-else :size="14" :stroke="1.75" />
|
|
96
|
+
{{ copied ? labelCopied : label }}
|
|
97
|
+
</button>
|
|
98
|
+
</template>
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { IconDownload, IconExternalLink, IconTrash } from '@tabler/icons-vue'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <FilesCard> — Muestra un File del backend innertia-laravel.
|
|
6
|
+
*
|
|
7
|
+
* Variantes:
|
|
8
|
+
* - 'card' (default) — card vertical con icono grande arriba, nombre + meta abajo
|
|
9
|
+
* - 'row' — fila horizontal compacta (icono + nombre + acciones a la derecha)
|
|
10
|
+
* - 'compact' — mini-row con icono pequeño + nombre (sin acciones)
|
|
11
|
+
*
|
|
12
|
+
* El file esperado matchea el shape del modelo File de innertia-laravel:
|
|
13
|
+
* { id, original_name, mime_type, extension, size, ... }
|
|
14
|
+
*
|
|
15
|
+
* Si el file es una imagen y se pasa `previewImage`, se renderiza el thumbnail
|
|
16
|
+
* usando el viewUrl en lugar del icono.
|
|
17
|
+
*/
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
file: { type: Object, required: true },
|
|
20
|
+
|
|
21
|
+
/** 'card' | 'row' | 'compact' */
|
|
22
|
+
variant: { type: String, default: 'card' },
|
|
23
|
+
|
|
24
|
+
/** Mostrar tamaño (formatSize). */
|
|
25
|
+
showSize: { type: Boolean, default: true },
|
|
26
|
+
|
|
27
|
+
/** Mostrar extensión (PDF, XLSX, etc.). */
|
|
28
|
+
showType: { type: Boolean, default: true },
|
|
29
|
+
|
|
30
|
+
/** Mostrar botón de download. */
|
|
31
|
+
downloadable: { type: Boolean, default: true },
|
|
32
|
+
|
|
33
|
+
/** Mostrar botón de ver (abre en nueva pestaña). */
|
|
34
|
+
viewable: { type: Boolean, default: false },
|
|
35
|
+
|
|
36
|
+
/** Mostrar botón de eliminar (emite `delete`). */
|
|
37
|
+
deletable: { type: Boolean, default: false },
|
|
38
|
+
|
|
39
|
+
/** Si el file es una imagen, renderizar thumbnail en lugar del icono. */
|
|
40
|
+
previewImage: { type: Boolean, default: true },
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const emit = defineEmits(['click', 'delete'])
|
|
44
|
+
|
|
45
|
+
const { download, view, viewUrl, formatSize, iconFor } = useFile()
|
|
46
|
+
|
|
47
|
+
const isImage = computed(() => props.file?.mime_type?.startsWith('image/'))
|
|
48
|
+
const showThumb = computed(() => props.previewImage && isImage.value)
|
|
49
|
+
const icon = computed(() => iconFor(props.file?.mime_type))
|
|
50
|
+
|
|
51
|
+
const handleDownload = (e) => {
|
|
52
|
+
e?.stopPropagation?.()
|
|
53
|
+
download(props.file.id, props.file.original_name)
|
|
54
|
+
}
|
|
55
|
+
const handleView = (e) => {
|
|
56
|
+
e?.stopPropagation?.()
|
|
57
|
+
view(props.file.id)
|
|
58
|
+
}
|
|
59
|
+
const handleDelete = (e) => {
|
|
60
|
+
e?.stopPropagation?.()
|
|
61
|
+
emit('delete', props.file)
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<!-- ════════════ Variante 'compact' (mini-row) ════════════ -->
|
|
67
|
+
<div
|
|
68
|
+
v-if="variant === 'compact'"
|
|
69
|
+
class="inline-flex items-center gap-2 px-2 py-1 rounded-control text-xs hover:bg-muted-hover transition-colors cursor-pointer"
|
|
70
|
+
@click="emit('click', file)"
|
|
71
|
+
>
|
|
72
|
+
<component :is="icon" :size="14" class="shrink-0 text-muted-foreground" />
|
|
73
|
+
<span class="truncate text-foreground">{{ file.original_name }}</span>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- ════════════ Variante 'row' (horizontal) ════════════ -->
|
|
77
|
+
<div
|
|
78
|
+
v-else-if="variant === 'row'"
|
|
79
|
+
class="group flex items-center gap-3 px-3 py-2 rounded-card border border-card-line bg-card hover:border-primary/40 hover:bg-muted-hover/30 transition-colors cursor-pointer"
|
|
80
|
+
@click="emit('click', file)"
|
|
81
|
+
>
|
|
82
|
+
<!-- Thumb o icono -->
|
|
83
|
+
<div class="shrink-0">
|
|
84
|
+
<img
|
|
85
|
+
v-if="showThumb"
|
|
86
|
+
:src="viewUrl(file.id)"
|
|
87
|
+
:alt="file.original_name"
|
|
88
|
+
class="size-10 rounded-control object-cover bg-muted"
|
|
89
|
+
/>
|
|
90
|
+
<div v-else class="size-10 rounded-control bg-muted flex items-center justify-center text-muted-foreground">
|
|
91
|
+
<component :is="icon" :size="20" :stroke="1.75" />
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Info -->
|
|
96
|
+
<div class="flex-1 min-w-0">
|
|
97
|
+
<p class="text-sm font-medium text-foreground truncate">{{ file.original_name }}</p>
|
|
98
|
+
<p class="text-xs text-muted-foreground mt-0.5 flex items-center gap-2">
|
|
99
|
+
<span v-if="showType && file.extension" class="uppercase">{{ file.extension }}</span>
|
|
100
|
+
<span v-if="showSize">{{ formatSize(file.size) }}</span>
|
|
101
|
+
</p>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<!-- Acciones -->
|
|
105
|
+
<div class="flex items-center gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
106
|
+
<slot name="actions" :file="file" />
|
|
107
|
+
<button
|
|
108
|
+
v-if="viewable"
|
|
109
|
+
type="button"
|
|
110
|
+
title="Ver"
|
|
111
|
+
class="inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors"
|
|
112
|
+
@click="handleView"
|
|
113
|
+
>
|
|
114
|
+
<IconExternalLink class="size-3.5" />
|
|
115
|
+
</button>
|
|
116
|
+
<button
|
|
117
|
+
v-if="downloadable"
|
|
118
|
+
type="button"
|
|
119
|
+
title="Descargar"
|
|
120
|
+
class="inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors"
|
|
121
|
+
@click="handleDownload"
|
|
122
|
+
>
|
|
123
|
+
<IconDownload class="size-3.5" />
|
|
124
|
+
</button>
|
|
125
|
+
<button
|
|
126
|
+
v-if="deletable"
|
|
127
|
+
type="button"
|
|
128
|
+
title="Eliminar"
|
|
129
|
+
class="inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
|
130
|
+
@click="handleDelete"
|
|
131
|
+
>
|
|
132
|
+
<IconTrash class="size-3.5" />
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<!-- ════════════ Variante 'card' (vertical, default) ════════════ -->
|
|
138
|
+
<div
|
|
139
|
+
v-else
|
|
140
|
+
class="group relative flex flex-col rounded-card border border-card-line bg-card hover:border-primary/40 hover:shadow-sm transition cursor-pointer overflow-hidden"
|
|
141
|
+
@click="emit('click', file)"
|
|
142
|
+
>
|
|
143
|
+
<!-- Header — MISMA proporción (aspect-video) para todas las cards.
|
|
144
|
+
Imagen: thumbnail completo. No-imagen: extensión como tipografía grande
|
|
145
|
+
+ ícono superpuesto, así llena visualmente el espacio. -->
|
|
146
|
+
<div class="relative aspect-video overflow-hidden">
|
|
147
|
+
<!-- Imagen real -->
|
|
148
|
+
<img
|
|
149
|
+
v-if="showThumb"
|
|
150
|
+
:src="viewUrl(file.id)"
|
|
151
|
+
:alt="file.original_name"
|
|
152
|
+
class="size-full object-cover"
|
|
153
|
+
/>
|
|
154
|
+
<!-- No-imagen: bg con gradient + extensión grande como tipografía dominante -->
|
|
155
|
+
<div
|
|
156
|
+
v-else
|
|
157
|
+
class="size-full bg-gradient-to-br from-muted via-muted/60 to-muted/30 flex flex-col items-center justify-center gap-1 text-muted-foreground select-none"
|
|
158
|
+
>
|
|
159
|
+
<component :is="icon" :size="44" :stroke="1.5" class="opacity-70" />
|
|
160
|
+
<span
|
|
161
|
+
v-if="file.extension"
|
|
162
|
+
class="text-2xl font-bold uppercase tracking-widest text-foreground/70"
|
|
163
|
+
>
|
|
164
|
+
{{ file.extension }}
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<!-- Chip con el tipo en imagen — sirve como label discreto -->
|
|
169
|
+
<span
|
|
170
|
+
v-if="showThumb && showType && file.extension"
|
|
171
|
+
class="absolute top-2 right-2 px-1.5 py-0.5 rounded-badge bg-card/85 backdrop-blur-sm text-[10px] font-bold uppercase tracking-wider text-foreground shadow-sm"
|
|
172
|
+
>
|
|
173
|
+
{{ file.extension }}
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<!-- Info -->
|
|
178
|
+
<div class="p-3 space-y-0.5">
|
|
179
|
+
<p class="text-sm font-medium text-foreground truncate">{{ file.original_name }}</p>
|
|
180
|
+
<p v-if="showSize" class="text-xs text-muted-foreground">{{ formatSize(file.size) }}</p>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<!-- Acciones — overlay arriba a la derecha en hover -->
|
|
184
|
+
<div class="absolute top-2 right-2 flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity bg-card/90 backdrop-blur-sm rounded-control p-0.5">
|
|
185
|
+
<slot name="actions" :file="file" />
|
|
186
|
+
<button
|
|
187
|
+
v-if="viewable"
|
|
188
|
+
type="button"
|
|
189
|
+
title="Ver"
|
|
190
|
+
class="inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors"
|
|
191
|
+
@click="handleView"
|
|
192
|
+
>
|
|
193
|
+
<IconExternalLink class="size-3.5" />
|
|
194
|
+
</button>
|
|
195
|
+
<button
|
|
196
|
+
v-if="downloadable"
|
|
197
|
+
type="button"
|
|
198
|
+
title="Descargar"
|
|
199
|
+
class="inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-foreground hover:bg-muted-hover transition-colors"
|
|
200
|
+
@click="handleDownload"
|
|
201
|
+
>
|
|
202
|
+
<IconDownload class="size-3.5" />
|
|
203
|
+
</button>
|
|
204
|
+
<button
|
|
205
|
+
v-if="deletable"
|
|
206
|
+
type="button"
|
|
207
|
+
title="Eliminar"
|
|
208
|
+
class="inline-flex items-center justify-center size-7 rounded-control text-muted-foreground hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
|
|
209
|
+
@click="handleDelete"
|
|
210
|
+
>
|
|
211
|
+
<IconTrash class="size-3.5" />
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</template>
|