Setting the file. One moment.
Skill 12 · Building Admin Dashboard Customizations
Subchapter 12.3
references/forms.mdMarkdown11 KBView on GitHub
FocusModal - Use for creating new entities:
Drawer - Use for editing existing entities:
Rule of thumb: FocusModal for creating, Drawer for editing.
Data displayed in a container should not be editable directly. Instead, use an “Edit” button. This can be:
import { Button } from "@medusajs/ui"
import { PencilSquare } from "@medusajs/icons"
<div className="flex items-center justify-between px-6 py-4">
<Heading level="h2">Section Title</Heading>
<Button
size="small"
variant="secondary"
onClick={() => setOpen(true)}
>
<PencilSquare />
</Button>
</div>import { EllipsisHorizontal, PencilSquare, Plus, Trash } from "@medusajs/icons"
import { DropdownMenu, IconButton } from "@medusajs/ui"
export function DropdownMenuDemo() {
return (
<DropdownMenu>
<DropdownMenu.Trigger asChild>
<IconButton size="small" variant="transparent">
<EllipsisHorizontal />
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item className="gap-x-2">
<PencilSquare className="text-ui-fg-subtle" />
Edit
</DropdownMenu.Item>
<DropdownMenu.Item className="gap-x-2">
<Plus className="text-ui-fg-subtle" />
Add
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item className="gap-x-2">
<Trash className="text-ui-fg-subtle" />
Delete
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu>
)
}For selecting from 2-10 options (statuses, types, etc.), use the Select component:
import { Select } from "@medusajs/ui"
<Select>
<Select.Trigger>
<Select.Value placeholder="Select status" />
</Select.Trigger>
<Select.Content>
{items.map((item) => (
<Select.Item key={item.value} value={item.value}>
{item.label}
</Select.Item>
))}
</Select.Content>
</Select>For larger datasets (Products, Categories, Regions, etc.), use DataTable with FocusModal for search and pagination. See table-selection.md for the complete pattern.
import { FocusModal, Button, Input, Label } from "@medusajs/ui"
import { useState } from "react"
const MyWidget = () => {
const [open, setOpen] = useState(false)
const [formData, setFormData] = useState({ title: "" })
const handleSubmit = () => {
// Handle form submission
console.log(formData)
setOpen(false)
}
return (
<>
<Button onClick={() => setOpen(true)}>
Create New
</Button>
<FocusModal open={open} onOpenChange={setOpen}>
<FocusModal.Content>
<div className="flex h-full flex-col overflow-hidden">
<FocusModal.Header>
<div className="flex items-center justify-end gap-x-2">
<FocusModal.Close asChild>
<Button size="small" variant="secondary">
Cancel
</Button>
</FocusModal.Close>
<Button size="small" onClick={handleSubmit}>
Save
</Button>
</div>
</FocusModal.Header>
<FocusModal.Body className="flex-1 overflow-auto">
<div className="flex flex-col gap-y-4">
<div className="flex flex-col gap-y-2">
<Label>Title</Label>
<Input
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
/>
</div>
{/* More form fields */}
</div>
</FocusModal.Body>
</div>
</FocusModal.Content>
</FocusModal>
</>
)
}import { Drawer, Button, Input, Label } from "@medusajs/ui"
import { useState } from "react"
const MyWidget = ({ data }) => {
const [open, setOpen] = useState(false)
const [formData, setFormData] = useState({ title: data.title })
const handleSubmit = () => {
// Handle form submission
console.log(formData)
setOpen(false)
}
return (
<>
<Button onClick={() => setOpen(true)}>
Edit
</Button>
<Drawer open={open} onOpenChange={setOpen}>
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>Edit Settings</Drawer.Title>
</Drawer.Header>
<Drawer.Body className="flex-1 overflow-auto p-4">
<div className="flex flex-col gap-y-4">
<div className="flex flex-col gap-y-2">
<Label>Title</Label>
<Input
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
/>
</div>
{/* More form fields */}
</div>
</Drawer.Body>
<Drawer.Footer>
<div className="flex items-center justify-end gap-x-2">
<Drawer.Close asChild>
<Button size="small" variant="secondary">
Cancel
</Button>
</Drawer.Close>
<Button size="small" onClick={handleSubmit}>
Save
</Button>
</div>
</Drawer.Footer>
</Drawer.Content>
</Drawer>
</>
)
}import { FocusModal, Button, Input, Label, Text, toast } from "@medusajs/ui"
import { useState } from "react"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { sdk } from "../lib/client"
const CreateProductWidget = () => {
const [open, setOpen] = useState(false)
const [formData, setFormData
<Button
disabled={mutation.isPending}
onClick={handleAction}
>
Action
</Button><Button
isLoading={mutation.isPending}
onClick={handleSubmit}
>
Save
</Button>onSuccess: () => {
setFormData(initialState)
setErrors({})
setOpen(false)
}const handleSubmit = () => {
const errors = validateForm(formData)
if (Object.keys(errors).length > 0) {
setErrors(errors)
return
}
mutation.mutate(formData)
}<Input
value={formData.field}
onChange={(e) => {
setFormData({ ...formData, field: e.target.value })
setErrors({ ...errors, field: undefined }) // Clear error
}}
/>