A save dialog with a file name, a folder pop-up, optional tags, and Cancel and Save.
registry/macos/blocks/save-panel-01/save-panel-01.tsx
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/macos/ui/button"
import { Input } from "@/registry/macos/ui/input"
import { Label } from "@/registry/macos/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/registry/macos/ui/select"
import {
Window,
WindowContent,
WindowControls,
WindowMain,
WindowTitle,
WindowTitlebar,
} from "@/registry/macos/ui/window"
const folders = {
documents: "Documents",
desktop: "Desktop",
downloads: "Downloads",
icloud: "iCloud Drive",
} as const
type Folder = keyof typeof folders
// A save dialog: file name, destination, tags, then Cancel and Save.
export function SavePanel01({
className,
...props
}: React.ComponentProps<typeof Window>) {
const nameId = React.useId()
const tagsId = React.useId()
const [name, setName] = React.useState("Untitled.tsx")
const [folder, setFolder] = React.useState<Folder>("documents")
const [tags, setTags] = React.useState("")
const [saved, setSaved] = React.useState(false)
return (
<Window className={cn("h-[220px] w-[440px]", className)} {...props}>
<WindowMain>
<WindowTitlebar>
<WindowControls />
<WindowTitle>Save</WindowTitle>
</WindowTitlebar>
<WindowContent className="flex flex-col px-5 py-4">
{saved ? (
<div className="flex flex-1 flex-col">
<div className="flex flex-1 flex-col items-center justify-center text-center">
<p className="text-mac-body font-semibold">Saved</p>
<p className="mt-1 text-mac-subheadline text-mac-label-secondary">
{name} in {folders[folder]}
{tags ? `, tagged ${tags}` : ""}
</p>
</div>
<div className="flex justify-end">
<Button onClick={() => setSaved(false)}>Done</Button>
</div>
</div>
) : (
<form
className="flex flex-1 flex-col"
onSubmit={(event) => {
event.preventDefault()
setSaved(true)
}}
>
<div className="grid grid-cols-[72px_1fr] items-center gap-x-3 gap-y-3">
<Label htmlFor={nameId} className="justify-end text-mac-label-secondary">
Save As:
</Label>
<Input
id={nameId}
value={name}
onChange={(event) => setName(event.target.value)}
required
/>
<span className="justify-self-end text-mac-body text-mac-label-secondary">Where:</span>
<Select
value={folder}
onValueChange={(value) => value && setFolder(value as Folder)}
items={folders}
>
<SelectTrigger className="w-full" aria-label="Where">
<SelectValue />
</SelectTrigger>
<SelectContent>
{(Object.keys(folders) as Folder[]).map((value) => (
<SelectItem key={value} value={value}>
{folders[value]}
</SelectItem>
))}
</SelectContent>
</Select>
<Label htmlFor={tagsId} className="justify-end text-mac-label-secondary">
Tags:
</Label>
<Input
id={tagsId}
value={tags}
onChange={(event) => setTags(event.target.value)}
placeholder="Optional"
/>
</div>
<div className="mt-auto flex justify-end gap-2">
<Button
type="button"
variant="secondary"
onClick={() => {
setName("Untitled.tsx")
setTags("")
setFolder("documents")
}}
>
Cancel
</Button>
<Button type="submit">Save</Button>
</div>
</form>
)}
</WindowContent>
</WindowMain>
</Window>
)
}