# Mobile Event Edit Modal — Design Spec
> Date: 2025-05-24
> Status: Approved

## Problem

The `EventDialog` renders a Radix `Dialog` at all breakpoints. On mobile it opens as a vertically-centred overlay that is taller than the viewport, leaving the sticky footer below the fold and making form fields hard to scroll to. The current internal `isMobile` patches (single-column grid, stacked footer) do not fix the core issue: the container itself is unsuitable for mobile.

## Approved Approach: Design C — Desktop Dialog + Mobile Guided Drawer

### Desktop (≥ 768 px)

No change. The existing `Dialog` with all three sections visible on one scrollable form stays as-is. The visual treatment (28 px title, mono section kickers, `bg-card` panel, `max-w-2xl`) is preserved.

### Mobile (< 768 px)

Replace the modal with a Vaul/shadcn `Drawer` that breaks the same form into **three guided steps**, each rendered in the drawer body with a sticky footer. The top sheet slides up from the bottom with a handle and snaps to ~86 % of viewport height.

#### Steps

| Step | Title | Fields |
|------|-------|--------|
| 1 | Event Details | title, description, location, URL |
| 2 | Schedule | all-day, start, end, duration chips |
| 3 | Recurrence | recurrence picker |

#### Step footer buttons

| Position | Step 1 | Step 2 | Step 3 |
|----------|--------|--------|--------|
| Left (secondary) | Cancel | Back | Back |
| Right (primary) | Next → | Next → | Save |

"Next" validates only fields that **belong to the current step** before advancing. "Save" performs full validation across all steps before submitting.

#### Progress indicator

Three slim bars below the drawer header (full-width at `px-4`). The active step bar uses `foreground`, inactive bars use `muted`.

#### AI draft banner

When `dialogSource === "ai"` and `editingId` is null the warning banner appears at the **top of each step body**, not once at the top of a single long form. This surfaces the review reminder exactly when the user is looking at that section.

#### Error locality

Validation errors are displayed under their fields within their owning step. If `onSave` triggers and any step has an error, the drawer jumps to the **lowest-numbered step that contains an error**.

## Component Architecture

### New component: `useEventDialogStep`

A thin hook inside `event-dialog.tsx` that owns mobile step state:

```ts
const [step, setStep] = useState<1 | 2 | 3>(1)
const advance = () => setStep(s => Math.min(s + 1, 3) as 1|2|3)
const retreat = () => setStep(s => Math.max(s - 1, 1) as 1|2|3)
const reset   = () => setStep(1)
```

Reset is called alongside `handleOpenChange(false)` so re-opening always starts at step 1.

### Drawer installation

```bash
pnpm dlx shadcn@latest add drawer
```

Adds `src/components/ui/drawer.tsx` (Vaul wrapper) following the project's existing shadcn conventions.

### `EventDialog` refactor

```
EventDialog
├── useIsMobile()  →  isDesktop branch
├── isDesktop=true  →  <Dialog> (unchanged)
└── isDesktop=false →  <Drawer>
    ├── DrawerContent
    │   ├── handle bar
    │   ├── DrawerHeader (title, step badge, progress bars)
    │   ├── AI draft banner (if applicable)
    │   ├── step body (one of three field groups, scrollable)
    │   └── sticky DrawerFooter (Back/Cancel | Next/Save)
    └── step state from useEventDialogStep
```

The three field group JSX blocks are extracted into private helper components `DetailsStep`, `ScheduleStep`, `RecurrenceStep`. These are pure-presentational and accept `control`, `register`, `errors`, `watch` values as props — no form ownership of their own.

### Shared form instance

`useForm` stays at the `EventDialog` level, shared across all steps. No per-step forms, no field re-registration on step change.

## Behaviour Details

- **Drawer scroll**: the step body is a `ScrollArea` (or `overflow-y-auto` div) inside the drawer; the footer is `position: sticky bottom-0` outside the scroll container.
- **Keyboard / virtual keyboard**: Vaul handles `window.visualViewport` resize natively so fields scroll into view on iOS without extra work.
- **Closing**: tapping the overlay or swiping the drawer down triggers `handleOpenChange(false)` (same logic as the existing dialog close).
- **No `isMobile` patches inside DialogContent**: once the drawer handles mobile, the internal `isMobile` guards in `DialogContent`, `DialogFooter`, and the grid class conditional inside `event-dialog.tsx` are removed to keep the desktop path clean.

## Files Changed

| File | Change |
|------|--------|
| `src/components/ui/drawer.tsx` | **New** — shadcn Drawer component (Vaul) |
| `src/components/event-dialog.tsx` | Refactor: add step state, conditional Drawer render on mobile |
| `src/components/ui/dialog.tsx` | Remove `isMobile` fork from `DialogContent` and `DialogFooter` |

## Out of Scope

- Tablet breakpoints (768–1024 px): treated as desktop.
- Animations beyond Vaul's built-in slide-up.
- Drag-to-resize the drawer (Vaul snap points are a future enhancement).
- Any changes to desktop dialog layout or field design.
