@veristone/nuxt-v-app 0.1.1 → 0.2.0
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/Create.vue +149 -0
- package/app/components/V/A/Crud/Delete.vue +148 -0
- package/app/components/V/A/Crud/Update.vue +171 -0
- package/app/layouts/default.vue +76 -68
- package/app/pages/playground/crud.vue +936 -0
- package/app/pages/playground/index.vue +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* VACrudCreate - Veristone CRUD Create Modal
|
|
4
|
+
*
|
|
5
|
+
* Provides a reusable modal for creating new records via API.
|
|
6
|
+
* Handles form submission, loading states, and error handling.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <VACrudCreate
|
|
10
|
+
* endpoint="/api/loans"
|
|
11
|
+
* friendly-name="Loan"
|
|
12
|
+
* @created="onLoanCreated"
|
|
13
|
+
* >
|
|
14
|
+
* <template #default="{ submit, loading, error }">
|
|
15
|
+
* <form @submit.prevent="submit({ name: 'New Loan' })">
|
|
16
|
+
* <UInput v-model="formData.name" placeholder="Loan name" />
|
|
17
|
+
* <UButton type="submit" :loading="loading">Create</UButton>
|
|
18
|
+
* </form>
|
|
19
|
+
* </template>
|
|
20
|
+
* </VACrudCreate>
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
endpoint: string;
|
|
25
|
+
friendlyName: string;
|
|
26
|
+
triggerLabel?: string;
|
|
27
|
+
triggerIcon?: string;
|
|
28
|
+
triggerColor?:
|
|
29
|
+
| "error"
|
|
30
|
+
| "primary"
|
|
31
|
+
| "secondary"
|
|
32
|
+
| "success"
|
|
33
|
+
| "info"
|
|
34
|
+
| "warning"
|
|
35
|
+
| "neutral";
|
|
36
|
+
triggerVariant?: "link" | "solid" | "outline" | "soft" | "subtle" | "ghost";
|
|
37
|
+
modalTitle?: string;
|
|
38
|
+
modalSize?: "sm" | "md" | "lg" | "xl";
|
|
39
|
+
initialData?: Record<string, any>;
|
|
40
|
+
open?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Emits {
|
|
44
|
+
(e: "created", data: any): void;
|
|
45
|
+
(e: "error", error: any): void;
|
|
46
|
+
(e: "opened"): void;
|
|
47
|
+
(e: "closed"): void;
|
|
48
|
+
(e: "update:open", value: boolean): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
52
|
+
triggerLabel: undefined,
|
|
53
|
+
triggerIcon: "i-lucide-plus",
|
|
54
|
+
triggerColor: "primary",
|
|
55
|
+
triggerVariant: "solid",
|
|
56
|
+
modalTitle: undefined,
|
|
57
|
+
modalSize: "md",
|
|
58
|
+
initialData: () => ({}),
|
|
59
|
+
open: undefined,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const emit = defineEmits<Emits>();
|
|
63
|
+
|
|
64
|
+
const { create, loading, errorState } = useVCrud(props.endpoint);
|
|
65
|
+
const internalOpen = ref(false);
|
|
66
|
+
|
|
67
|
+
// Hybrid internal/external state via computed getter/setter
|
|
68
|
+
const isOpen = computed({
|
|
69
|
+
get: () => props.open ?? internalOpen.value,
|
|
70
|
+
set: (val) => {
|
|
71
|
+
emit("update:open", val);
|
|
72
|
+
internalOpen.value = val;
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Watch for open state changes to emit lifecycle events
|
|
77
|
+
watch(isOpen, (val) => {
|
|
78
|
+
if (val) {
|
|
79
|
+
emit("opened");
|
|
80
|
+
} else {
|
|
81
|
+
emit("closed");
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Computed defaults for labels
|
|
86
|
+
const computedTriggerLabel = computed(() => {
|
|
87
|
+
return props.triggerLabel ?? `Create ${props.friendlyName}`;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const computedModalTitle = computed(() => {
|
|
91
|
+
return props.modalTitle ?? `Create ${props.friendlyName}`;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Submit handler
|
|
95
|
+
const handleSubmit = async (data: Record<string, any>) => {
|
|
96
|
+
try {
|
|
97
|
+
const result = await create(data);
|
|
98
|
+
emit("created", result);
|
|
99
|
+
isOpen.value = false;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
emit("error", err);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<UModal v-model:open="isOpen">
|
|
108
|
+
<UButton
|
|
109
|
+
:label="computedTriggerLabel"
|
|
110
|
+
:icon="triggerIcon"
|
|
111
|
+
:color="triggerColor"
|
|
112
|
+
:variant="triggerVariant"
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
<template #header>
|
|
116
|
+
<div class="flex items-center justify-between w-full">
|
|
117
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
|
118
|
+
{{ computedModalTitle }}
|
|
119
|
+
</h3>
|
|
120
|
+
<UButton
|
|
121
|
+
icon="i-lucide-x"
|
|
122
|
+
color="neutral"
|
|
123
|
+
variant="ghost"
|
|
124
|
+
size="sm"
|
|
125
|
+
@click="isOpen = false"
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
130
|
+
<template #body>
|
|
131
|
+
<div class="p-4 space-y-4">
|
|
132
|
+
<slot :submit="handleSubmit" :loading="loading" :error="errorState">
|
|
133
|
+
<div class="space-y-4">
|
|
134
|
+
<p class="text-gray-500 italic">
|
|
135
|
+
Provide form content via default slot
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
138
|
+
</slot>
|
|
139
|
+
|
|
140
|
+
<div
|
|
141
|
+
v-if="errorState"
|
|
142
|
+
class="p-3 rounded bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 text-sm"
|
|
143
|
+
>
|
|
144
|
+
{{ errorState }}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</template>
|
|
148
|
+
</UModal>
|
|
149
|
+
</template>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* VACrudDelete - Delete confirmation component with CRUD integration
|
|
4
|
+
*
|
|
5
|
+
* Provides a delete button with confirmation modal that integrates with useVCrud.
|
|
6
|
+
* Handles API deletion, loading states, and error handling.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <VACrudDelete
|
|
10
|
+
* endpoint="/api/users"
|
|
11
|
+
* :record-id="userId"
|
|
12
|
+
* item-name="User"
|
|
13
|
+
* @deleted="onUserDeleted"
|
|
14
|
+
* />
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(
|
|
18
|
+
defineProps<{
|
|
19
|
+
endpoint: string;
|
|
20
|
+
recordId: string | number;
|
|
21
|
+
itemName?: string;
|
|
22
|
+
triggerLabel?: string;
|
|
23
|
+
triggerIcon?: string;
|
|
24
|
+
triggerColor?:
|
|
25
|
+
| "error"
|
|
26
|
+
| "primary"
|
|
27
|
+
| "secondary"
|
|
28
|
+
| "success"
|
|
29
|
+
| "info"
|
|
30
|
+
| "warning"
|
|
31
|
+
| "neutral";
|
|
32
|
+
triggerVariant?: "link" | "solid" | "outline" | "soft" | "subtle" | "ghost";
|
|
33
|
+
triggerSize?: "xs" | "sm" | "md" | "lg" | "xl";
|
|
34
|
+
confirmTitle?: string;
|
|
35
|
+
confirmMessage?: string;
|
|
36
|
+
open?: boolean;
|
|
37
|
+
}>(),
|
|
38
|
+
{
|
|
39
|
+
itemName: "this item",
|
|
40
|
+
triggerIcon: "i-lucide-trash-2",
|
|
41
|
+
triggerColor: "error",
|
|
42
|
+
triggerVariant: "solid",
|
|
43
|
+
triggerSize: "xs",
|
|
44
|
+
confirmTitle: "Delete Item",
|
|
45
|
+
open: undefined,
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const emit = defineEmits<{
|
|
50
|
+
deleted: [id: string | number];
|
|
51
|
+
error: [error: any];
|
|
52
|
+
opened: [];
|
|
53
|
+
closed: [];
|
|
54
|
+
"update:open": [value: boolean];
|
|
55
|
+
}>();
|
|
56
|
+
|
|
57
|
+
const { remove, loading } = useVCrud(props.endpoint);
|
|
58
|
+
|
|
59
|
+
const isOpen = computed({
|
|
60
|
+
get: () => props.open,
|
|
61
|
+
set: (val) => {
|
|
62
|
+
emit("update:open", val ?? false);
|
|
63
|
+
if (val) {
|
|
64
|
+
emit("opened");
|
|
65
|
+
} else {
|
|
66
|
+
emit("closed");
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const confirmMessage = computed(() => {
|
|
72
|
+
return (
|
|
73
|
+
props.confirmMessage ||
|
|
74
|
+
`Are you sure you want to delete ${props.itemName}? This cannot be undone.`
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const handleDelete = async () => {
|
|
79
|
+
try {
|
|
80
|
+
await remove(props.recordId);
|
|
81
|
+
emit("deleted", props.recordId);
|
|
82
|
+
isOpen.value = false;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
emit("error", error);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<UModal v-model:open="isOpen">
|
|
91
|
+
<UButton
|
|
92
|
+
:icon="triggerIcon"
|
|
93
|
+
:color="triggerColor as any"
|
|
94
|
+
:variant="triggerVariant as any"
|
|
95
|
+
:size="triggerSize as any"
|
|
96
|
+
:label="triggerLabel"
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<template #header>
|
|
100
|
+
<div class="flex items-center justify-between w-full">
|
|
101
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
|
102
|
+
{{ confirmTitle }}
|
|
103
|
+
</h3>
|
|
104
|
+
<UButton
|
|
105
|
+
icon="i-lucide-x"
|
|
106
|
+
color="neutral"
|
|
107
|
+
variant="ghost"
|
|
108
|
+
size="sm"
|
|
109
|
+
@click="isOpen = false"
|
|
110
|
+
/>
|
|
111
|
+
</div>
|
|
112
|
+
</template>
|
|
113
|
+
|
|
114
|
+
<!-- Modal Content -->
|
|
115
|
+
<template #body>
|
|
116
|
+
<div class="p-4 space-y-4">
|
|
117
|
+
<div class="flex items-start gap-4">
|
|
118
|
+
<div
|
|
119
|
+
class="shrink-0 p-2 rounded-full bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400"
|
|
120
|
+
>
|
|
121
|
+
<UIcon name="i-lucide-alert-triangle" class="w-6 h-6" />
|
|
122
|
+
</div>
|
|
123
|
+
<div class="flex-1">
|
|
124
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
125
|
+
{{ confirmMessage }}
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="flex justify-end gap-3">
|
|
131
|
+
<UButton
|
|
132
|
+
label="Cancel"
|
|
133
|
+
color="neutral"
|
|
134
|
+
variant="ghost"
|
|
135
|
+
@click="isOpen = false"
|
|
136
|
+
/>
|
|
137
|
+
<UButton
|
|
138
|
+
label="Delete"
|
|
139
|
+
color="error"
|
|
140
|
+
variant="solid"
|
|
141
|
+
:loading="loading"
|
|
142
|
+
@click="handleDelete"
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</template>
|
|
147
|
+
</UModal>
|
|
148
|
+
</template>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* VACrudUpdate - Veristone CRUD Update Modal
|
|
4
|
+
*
|
|
5
|
+
* Provides a reusable modal for editing records via API.
|
|
6
|
+
* Handles data fetching, form state management, and submission.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```vue
|
|
10
|
+
* <VACrudUpdate
|
|
11
|
+
* endpoint="/api/users"
|
|
12
|
+
* :record-id="userId"
|
|
13
|
+
* friendly-name="User"
|
|
14
|
+
* @updated="onUserUpdated"
|
|
15
|
+
* >
|
|
16
|
+
* <template #default="{ submit, loading, error, data, isDirty }">
|
|
17
|
+
* <form @submit.prevent="submit(data)">
|
|
18
|
+
* <UFormGroup label="Name">
|
|
19
|
+
* <UInput v-model="data.name" />
|
|
20
|
+
* </UFormGroup>
|
|
21
|
+
* <UButton type="submit" :loading="loading">Save</UButton>
|
|
22
|
+
* </form>
|
|
23
|
+
* </template>
|
|
24
|
+
* </VACrudUpdate>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
endpoint: string;
|
|
30
|
+
recordId: string | number;
|
|
31
|
+
friendlyName?: string;
|
|
32
|
+
triggerLabel?: string;
|
|
33
|
+
triggerIcon?: string;
|
|
34
|
+
triggerColor?:
|
|
35
|
+
| "neutral"
|
|
36
|
+
| "error"
|
|
37
|
+
| "primary"
|
|
38
|
+
| "secondary"
|
|
39
|
+
| "success"
|
|
40
|
+
| "info"
|
|
41
|
+
| "warning";
|
|
42
|
+
triggerVariant?: "ghost" | "link" | "solid" | "outline" | "soft" | "subtle";
|
|
43
|
+
modalTitle?: string;
|
|
44
|
+
modalSize?: "sm" | "md" | "lg" | "xl";
|
|
45
|
+
open?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
49
|
+
friendlyName: "Record",
|
|
50
|
+
triggerLabel: "Edit",
|
|
51
|
+
triggerIcon: "i-lucide-pencil",
|
|
52
|
+
triggerColor: "neutral",
|
|
53
|
+
triggerVariant: "ghost",
|
|
54
|
+
modalSize: "md",
|
|
55
|
+
open: undefined,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const emit = defineEmits<{
|
|
59
|
+
updated: [data: any];
|
|
60
|
+
error: [error: any];
|
|
61
|
+
opened: [];
|
|
62
|
+
closed: [];
|
|
63
|
+
"update:open": [value: boolean];
|
|
64
|
+
}>();
|
|
65
|
+
|
|
66
|
+
// Composables
|
|
67
|
+
const { findOne, update, loading, errorState, formData, isFormDirty } =
|
|
68
|
+
useVCrud(props.endpoint);
|
|
69
|
+
|
|
70
|
+
// State
|
|
71
|
+
const internalOpen = ref(false);
|
|
72
|
+
const isFetching = ref(false);
|
|
73
|
+
|
|
74
|
+
// Computed modal title
|
|
75
|
+
const computedModalTitle = computed(() => {
|
|
76
|
+
return props.modalTitle || `Edit ${props.friendlyName}`;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Hybrid open state (internal + external v-model)
|
|
80
|
+
const isOpen = computed({
|
|
81
|
+
get: () => (props.open !== undefined ? props.open : internalOpen.value),
|
|
82
|
+
set: (val) => {
|
|
83
|
+
emit("update:open", val);
|
|
84
|
+
internalOpen.value = val;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Watch for modal open to fetch data
|
|
89
|
+
watch(isOpen, async (val) => {
|
|
90
|
+
if (val) {
|
|
91
|
+
emit("opened");
|
|
92
|
+
if (props.recordId) {
|
|
93
|
+
isFetching.value = true;
|
|
94
|
+
try {
|
|
95
|
+
await findOne(props.recordId);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// Error handled by composable
|
|
98
|
+
} finally {
|
|
99
|
+
isFetching.value = false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
emit("closed");
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Submit handler
|
|
108
|
+
const submit = async (data: Record<string, any>) => {
|
|
109
|
+
try {
|
|
110
|
+
const result = await update(props.recordId, data);
|
|
111
|
+
emit("updated", result);
|
|
112
|
+
isOpen.value = false;
|
|
113
|
+
} catch (e) {
|
|
114
|
+
emit("error", e);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<template>
|
|
120
|
+
<UModal v-model:open="isOpen">
|
|
121
|
+
<UButton
|
|
122
|
+
:label="triggerLabel"
|
|
123
|
+
:icon="triggerIcon"
|
|
124
|
+
:color="triggerColor"
|
|
125
|
+
:variant="triggerVariant"
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
<template #header>
|
|
129
|
+
<div class="flex items-center justify-between w-full">
|
|
130
|
+
<h3 class="text-base font-semibold text-gray-900 dark:text-white">
|
|
131
|
+
{{ computedModalTitle }}
|
|
132
|
+
</h3>
|
|
133
|
+
<UButton
|
|
134
|
+
icon="i-lucide-x"
|
|
135
|
+
color="neutral"
|
|
136
|
+
variant="ghost"
|
|
137
|
+
size="sm"
|
|
138
|
+
@click="isOpen = false"
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
142
|
+
|
|
143
|
+
<template #body>
|
|
144
|
+
<div class="p-4 space-y-4">
|
|
145
|
+
<div v-if="isFetching" class="space-y-4">
|
|
146
|
+
<USkeleton class="h-4 w-full" />
|
|
147
|
+
<USkeleton class="h-4 w-3/4" />
|
|
148
|
+
<USkeleton class="h-4 w-1/2" />
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div v-else>
|
|
152
|
+
<slot
|
|
153
|
+
:submit="submit"
|
|
154
|
+
:loading="loading"
|
|
155
|
+
:error="errorState"
|
|
156
|
+
:data="formData"
|
|
157
|
+
:isDirty="isFormDirty"
|
|
158
|
+
:isFetching="isFetching"
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div
|
|
163
|
+
v-if="errorState"
|
|
164
|
+
class="rounded bg-red-50 p-3 text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400"
|
|
165
|
+
>
|
|
166
|
+
{{ errorState }}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</template>
|
|
170
|
+
</UModal>
|
|
171
|
+
</template>
|
package/app/layouts/default.vue
CHANGED
|
@@ -1,103 +1,108 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { NavigationMenuItem } from
|
|
2
|
+
import type { NavigationMenuItem } from "@nuxt/ui";
|
|
3
3
|
|
|
4
|
-
const route = useRoute()
|
|
4
|
+
const route = useRoute();
|
|
5
5
|
|
|
6
6
|
const links = ref<NavigationMenuItem[][]>([
|
|
7
7
|
[
|
|
8
8
|
{
|
|
9
|
-
to:
|
|
10
|
-
icon:
|
|
11
|
-
label:
|
|
9
|
+
to: "/",
|
|
10
|
+
icon: "i-lucide-home",
|
|
11
|
+
label: "Overview",
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
to:
|
|
15
|
-
icon:
|
|
16
|
-
label:
|
|
14
|
+
to: "/playground",
|
|
15
|
+
icon: "i-lucide-blocks",
|
|
16
|
+
label: "Playground",
|
|
17
17
|
open: true,
|
|
18
18
|
children: [
|
|
19
19
|
{
|
|
20
|
-
to:
|
|
21
|
-
icon:
|
|
22
|
-
label:
|
|
20
|
+
to: "/playground",
|
|
21
|
+
icon: "i-lucide-layout-grid",
|
|
22
|
+
label: "Components",
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
to:
|
|
26
|
-
icon:
|
|
27
|
-
label:
|
|
25
|
+
to: "/playground/cards",
|
|
26
|
+
icon: "i-lucide-credit-card",
|
|
27
|
+
label: "Cards",
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
to:
|
|
31
|
-
icon:
|
|
32
|
-
label:
|
|
30
|
+
to: "/playground/charts",
|
|
31
|
+
icon: "i-lucide-bar-chart-3",
|
|
32
|
+
label: "Charts",
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
to:
|
|
36
|
-
icon:
|
|
37
|
-
label:
|
|
35
|
+
to: "/playground/blocks",
|
|
36
|
+
icon: "i-lucide-layout-template",
|
|
37
|
+
label: "Blocks",
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
to:
|
|
41
|
-
icon:
|
|
42
|
-
label:
|
|
40
|
+
to: "/playground/dashboard",
|
|
41
|
+
icon: "i-lucide-layout-dashboard",
|
|
42
|
+
label: "Dashboard",
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
|
-
to:
|
|
46
|
-
icon:
|
|
47
|
-
label:
|
|
45
|
+
to: "/playground/tables",
|
|
46
|
+
icon: "i-lucide-table-2",
|
|
47
|
+
label: "Tables",
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
|
-
to:
|
|
51
|
-
icon:
|
|
52
|
-
label:
|
|
50
|
+
to: "/playground/base",
|
|
51
|
+
icon: "i-lucide-box",
|
|
52
|
+
label: "Base Components",
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
|
-
to:
|
|
56
|
-
icon:
|
|
57
|
-
label:
|
|
55
|
+
to: "/playground/buttons",
|
|
56
|
+
icon: "i-lucide-mouse-pointer-click",
|
|
57
|
+
label: "Buttons",
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
|
-
to:
|
|
61
|
-
icon:
|
|
62
|
-
label:
|
|
60
|
+
to: "/playground/formatters",
|
|
61
|
+
icon: "i-lucide-text-cursor-input",
|
|
62
|
+
label: "Formatters",
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
|
-
to:
|
|
66
|
-
icon:
|
|
67
|
-
label:
|
|
65
|
+
to: "/playground/states",
|
|
66
|
+
icon: "i-lucide-loader",
|
|
67
|
+
label: "States",
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
|
-
to:
|
|
71
|
-
icon:
|
|
72
|
-
label:
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
to: "/playground/modals",
|
|
71
|
+
icon: "i-lucide-panel-right",
|
|
72
|
+
label: "Modals",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
to: "/playground/crud",
|
|
76
|
+
icon: "i-lucide-database",
|
|
77
|
+
label: "CRUD",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
]);
|
|
78
83
|
|
|
79
84
|
const groups = computed(() => [
|
|
80
85
|
{
|
|
81
|
-
id:
|
|
82
|
-
label:
|
|
83
|
-
items: links.value.flat()
|
|
86
|
+
id: "links",
|
|
87
|
+
label: "Go to",
|
|
88
|
+
items: links.value.flat(),
|
|
84
89
|
},
|
|
85
90
|
{
|
|
86
|
-
id:
|
|
87
|
-
label:
|
|
91
|
+
id: "code",
|
|
92
|
+
label: "Code",
|
|
88
93
|
items: [
|
|
89
94
|
{
|
|
90
|
-
id:
|
|
91
|
-
label:
|
|
92
|
-
icon:
|
|
95
|
+
id: "source",
|
|
96
|
+
label: "View page source",
|
|
97
|
+
icon: "i-simple-icons-github",
|
|
93
98
|
to: `https://github.com/nuxt-ui-templates/dashboard/blob/main/app/pages${
|
|
94
|
-
route.path ===
|
|
99
|
+
route.path === "/" ? "/index" : route.path
|
|
95
100
|
}.vue`,
|
|
96
|
-
target:
|
|
97
|
-
}
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
])
|
|
101
|
+
target: "_blank",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
101
106
|
</script>
|
|
102
107
|
|
|
103
108
|
<template>
|
|
@@ -114,18 +119,22 @@ const groups = computed(() => [
|
|
|
114
119
|
class="flex items-center gap-2 px-3 py-2"
|
|
115
120
|
:class="collapsed ? 'justify-center' : ''"
|
|
116
121
|
>
|
|
117
|
-
<div
|
|
118
|
-
|
|
122
|
+
<div
|
|
123
|
+
class="size-8 rounded-lg bg-primary-500/10 flex items-center justify-center shrink-0"
|
|
124
|
+
>
|
|
125
|
+
<UIcon
|
|
126
|
+
name="i-lucide-layout-dashboard"
|
|
127
|
+
class="size-5 text-primary-500"
|
|
128
|
+
/>
|
|
119
129
|
</div>
|
|
120
|
-
<span v-if="!collapsed" class="font-semibold text-lg truncate"
|
|
130
|
+
<span v-if="!collapsed" class="font-semibold text-lg truncate"
|
|
131
|
+
>VA Playground</span
|
|
132
|
+
>
|
|
121
133
|
</div>
|
|
122
134
|
</template>
|
|
123
135
|
|
|
124
136
|
<template #default="{ collapsed }">
|
|
125
|
-
<VaLayoutSideNav
|
|
126
|
-
:items="links[0]!"
|
|
127
|
-
:collapsed="collapsed"
|
|
128
|
-
/>
|
|
137
|
+
<VaLayoutSideNav :items="links[0]!" :collapsed="collapsed" />
|
|
129
138
|
|
|
130
139
|
<!-- Accounts section commented out - requires /analytics.svg asset
|
|
131
140
|
<div class="space-y-1 mt-4">
|
|
@@ -155,7 +164,6 @@ const groups = computed(() => [
|
|
|
155
164
|
</div>
|
|
156
165
|
</div>
|
|
157
166
|
-->
|
|
158
|
-
|
|
159
167
|
</template>
|
|
160
168
|
|
|
161
169
|
<template #footer="{ collapsed }">
|