# Forms and Modal Patterns ## Contents - [FocusModal vs Drawer](#focusmodal-vs-drawer) - [Edit Button Patterns](#edit-button-patterns) - [Simple Edit Button (top right corner)](#simple-edit-button-top-right-corner) - [Dropdown Menu with Actions](#dropdown-menu-with-actions) - [Select Component for Small Datasets](#select-component-for-small-datasets) - [FocusModal Example](#focusmodal-example) - [Drawer Example](#drawer-example) - [Form with Validation and Loading States](#form-with-validation-and-loading-states) - [Key Form Patterns](#key-form-patterns) ## FocusModal vs Drawer **FocusModal** - Use for creating new entities: - Full-screen modal - More space for complex forms - Better for multi-step flows **Drawer** - Use for editing existing entities: - Side panel that slides in from right - Quick edits without losing context - Better for single-field updates **Rule of thumb:** FocusModal for creating, Drawer for editing. ## Edit Button Patterns Data displayed in a container should not be editable directly. Instead, use an "Edit" button. This can be: ### Simple Edit Button (top right corner) ```tsx import { Button } from "@medusajs/ui" import { PencilSquare } from "@medusajs/icons"
Section Title
``` ### Dropdown Menu with Actions ```tsx import { EllipsisHorizontal, PencilSquare, Plus, Trash } from "@medusajs/icons" import { DropdownMenu, IconButton } from "@medusajs/ui" export function DropdownMenuDemo() { return ( Edit Add Delete ) } ``` ## Select Component for Small Datasets For selecting from 2-10 options (statuses, types, etc.), use the Select component: ```tsx import { Select } from "@medusajs/ui" ``` **For larger datasets** (Products, Categories, Regions, etc.), use DataTable with FocusModal for search and pagination. See [table-selection.md](table-selection.md) for the complete pattern. ## FocusModal Example ```tsx 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 ( <>
setFormData({ ...formData, title: e.target.value })} />
{/* More form fields */}
) } ``` ## Drawer Example ```tsx 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 ( <> Edit Settings
setFormData({ ...formData, title: e.target.value })} />
{/* More form fields */}
) } ``` ## Form with Validation and Loading States ```tsx 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] = useState({ title: "", description: "", }) const [errors, setErrors] = useState({}) const queryClient = useQueryClient() const createProduct = useMutation({ mutationFn: (data) => sdk.admin.product.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["products"] }) toast.success("Product created successfully") setOpen(false) setFormData({ title: "", description: "" }) setErrors({}) }, onError: (error) => { toast.error(error.message || "Failed to create product") }, }) const handleSubmit = () => { // Validate const newErrors = {} if (!formData.title) newErrors.title = "Title is required" if (!formData.description) newErrors.description = "Description is required" if (Object.keys(newErrors).length > 0) { setErrors(newErrors) return } createProduct.mutate(formData) } return ( <>
{ setFormData({ ...formData, title: e.target.value }) setErrors({ ...errors, title: undefined }) }} /> {errors.title && ( {errors.title} )}
{ setFormData({ ...formData, description: e.target.value }) setErrors({ ...errors, description: undefined }) }} /> {errors.description && ( {errors.description} )}
) } ``` ## Key Form Patterns ### Always Disable Actions During Mutations ```tsx ``` ### Show Loading State on Submit Button ```tsx ``` ### Clear Form After Success ```tsx onSuccess: () => { setFormData(initialState) setErrors({}) setOpen(false) } ``` ### Validate Before Submitting ```tsx const handleSubmit = () => { const errors = validateForm(formData) if (Object.keys(errors).length > 0) { setErrors(errors) return } mutation.mutate(formData) } ``` ### Clear Field Errors on Input Change ```tsx { setFormData({ ...formData, field: e.target.value }) setErrors({ ...errors, field: undefined }) // Clear error }} /> ```