<script setup lang="ts">
const { open } = useCrouton()
// Create new item
const handleCreate = () => {
open('create', 'shopProducts')
}
// Edit existing item
const handleEdit = (productId: string) => {
open('update', 'shopProducts', [productId])
}
// Delete items
const handleDelete = (productIds: string[]) => {
open('delete', 'shopProducts', productIds)
}
</script>
<template>
<UButton @click="handleCreate">New Product</UButton>
<UButton @click="handleEdit('product-123')">Edit</UButton>
<UButton @click="handleDelete(['id1', 'id2'])" color="red">Delete</UButton>
</template>
Nuxt Crouton supports four container types for forms:
// Slideover (default)
open('create', 'shopProducts', [], 'slideover')
// Modal
open('create', 'shopProducts', [], 'modal')
// Dialog
open('delete', 'shopProducts', ['id1'], 'dialog')
// Inline
open('update', 'shopProducts', ['id1'], 'inline')
Nuxt Crouton supports nesting forms up to 5 levels deep:
<script setup lang="ts">
// Open product form
open('create', 'shopProducts')
// From inside product form, open category form
open('create', 'shopCategories') // Opens on top of product form
// Supports up to 5 levels deep
</script>
This is useful when:
<script setup lang="ts">
const { open, close, closeAll, showCrouton } = useCrouton()
// Check if any form is open
if (showCrouton.value) {
console.log('Form is open')
}
// Close current form
close()
// Close all forms
closeAll()
</script>
Generated forms accept these props:
interface Props {
action: 'create' | 'update' | 'delete' | 'view'
activeItem?: any
items?: string[]
loading: LoadingState // 'notLoading' | 'create_send' | 'update_send' | 'delete_send' | 'view_send' | 'create_open' | 'update_open' | 'delete_open' | 'view_open'
collection: string
}
The operation being performed:
create - Creating a new itemupdate - Editing an existing itemdelete - Deleting one or more itemsview - Viewing an item (read-only)The item being edited (for update action)
Array of item IDs (for delete action)
Loading state identifier
The collection name (e.g., 'shopProducts')
<!-- layers/shop/components/products/_Form.vue -->
<script setup lang="ts">
// Keep generated props
const props = defineProps<ShopProductsFormProps>()
// Add custom state
const uploadingImage = ref(false)
const imagePreview = ref<string | null>(null)
// Add custom methods
const handleImageUpload = async (file: File) => {
uploadingImage.value = true
const url = await uploadToCloudinary(file)
state.value.imageUrl = url
imagePreview.value = url
uploadingImage.value = false
}
// Keep generated mutation logic
const { create, update } = useCollectionMutation(props.collection)
</script>
<template>
<UForm @submit="handleSubmit">
<!-- Generated fields -->
<UFormField label="Name" name="name">
<UInput v-model="state.name" />
</UFormField>
<!-- Your custom field -->
<UFormField label="Product Image" name="imageUrl">
<img v-if="imagePreview" :src="imagePreview" class="w-32 h-32 object-cover" />
<UButton @click="triggerFileInput" :loading="uploadingImage">
Upload Image
</UButton>
</UFormField>
<!-- Keep generated button -->
<CroutonFormActionButton :action="action" :loading="loading" />
</UForm>
</template>
<script setup lang="ts">
const currentStep = ref(1)
const totalSteps = 3
const nextStep = () => {
if (currentStep.value < totalSteps) {
currentStep.value++
}
}
const prevStep = () => {
if (currentStep.value > 1) {
currentStep.value--
}
}
const handleSubmit = async () => {
if (currentStep.value < totalSteps) {
nextStep()
} else {
// Final submit
await create(state.value)
close()
}
}
</script>
<template>
<UForm @submit="handleSubmit">
<!-- Step indicator -->
<div class="flex justify-between mb-4">
<div v-for="step in totalSteps" :key="step"
:class="{ 'font-bold': step === currentStep }">
Step {{ step }}
</div>
</div>
<!-- Step 1: Basic info -->
<div v-if="currentStep === 1">
<UFormField label="Name" name="name">
<UInput v-model="state.name" />
</UFormField>
</div>
<!-- Step 2: Details -->
<div v-if="currentStep === 2">
<UFormField label="Description" name="description">
<UTextarea v-model="state.description" />
</UFormField>
</div>
<!-- Step 3: Pricing -->
<div v-if="currentStep === 3">
<UFormField label="Price" name="price">
<UInput v-model.number="state.price" type="number" />
</UFormField>
</div>
<!-- Navigation -->
<div class="flex justify-between">
<UButton v-if="currentStep > 1" @click="prevStep" variant="ghost">
Back
</UButton>
<UButton type="submit">
{{ currentStep < totalSteps ? 'Next' : 'Submit' }}
</UButton>
</div>
</UForm>
</template>
<template>
<UForm>
<UFormField label="Product Type" name="type">
<USelectMenu
v-model="state.type"
:options="['physical', 'digital']"
/>
</UFormField>
<!-- Show only for physical products -->
<UFormField v-if="state.type === 'physical'" label="Weight" name="weight">
<UInput v-model.number="state.weight" type="number" />
</UFormField>
<!-- Show only for digital products -->
<UFormField v-if="state.type === 'digital'" label="Download URL" name="downloadUrl">
<UInput v-model="state.downloadUrl" />
</UFormField>
</UForm>
</template>