Files
supabase/apps/studio/components/interfaces/Workers/CreateWorkerDialog.tsx
T
Claude 0a8d2601c6 fix(studio): tighten Create worker dialog spacing, move admonition below fields
Follow the design-system dialog form pattern: one padded DialogSection with
space-y-4 between fields (instead of per-field p-5), and move the "Deploys a
Deno starter worker" admonition to the bottom of the form, flush with the
dialog borders. Its own border-top divides it from the fields; border-b-0 lets
the DialogSectionSeparator below draw the single line before the snippets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQ5b58nwfCGsifA7PCheQb
2026-08-20 15:02:37 +00:00

218 lines
7.0 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm, useWatch, type Control } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
cn,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
Form,
FormControl,
FormField,
Input,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { WORKER_MAX_INSTANCES, WORKER_MIN_INSTANCES, WORKER_SIZES } from './Workers.constants'
import { CreateWorkerSchema, type CreateWorkerForm } from './Workers.schema'
import { formatSize } from './Workers.utils'
import { WorkerPromptPanel } from './WorkerPromptPanel'
import { useWorkerDeployMutation } from '@/data/workers/worker-deploy-mutation'
const FORM_ID = 'create-worker-form'
// The dashboard only deploys the Deno starter; the CLI is where other runtimes
// get selected, so the snippets below describe a Deno worker.
const DEPLOYED_RUNTIME = 'deno'
const defaultValues: CreateWorkerForm = {
name: '',
size: WORKER_SIZES[0],
access: 'private',
instances: WORKER_MIN_INSTANCES,
}
/**
* Live snippets for the values currently in the form. Subscribes on its own so
* a keystroke re-renders the snippets rather than the whole dialog.
*/
const CreateWorkerSnippets = ({ control }: { control: Control<CreateWorkerForm> }) => {
const [name, size, access, instances] = useWatch({
control,
name: ['name', 'size', 'access', 'instances'],
})
return (
<WorkerPromptPanel
input={{
name,
runtime: DEPLOYED_RUNTIME,
size,
access,
// `instances` holds the raw input string while typing, and '' when cleared.
instances: Number(instances) || WORKER_MIN_INSTANCES,
}}
/>
)
}
interface CreateWorkerDialogProps {
projectRef: string
open: boolean
onOpenChange: (open: boolean) => void
}
export const CreateWorkerDialog = ({ projectRef, open, onOpenChange }: CreateWorkerDialogProps) => {
const form = useForm<CreateWorkerForm>({
resolver: zodResolver(CreateWorkerSchema),
defaultValues,
})
const { mutate: deployWorker, isPending } = useWorkerDeployMutation({
onSuccess: (worker) => {
toast.success(`Deploying ${worker.name}`)
form.reset(defaultValues)
onOpenChange(false)
},
})
const onSubmit = (values: CreateWorkerForm) => deployWorker({ projectRef, ...values })
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent size="medium">
<DialogHeader>
<DialogTitle>Deploy a worker</DialogTitle>
</DialogHeader>
<DialogSectionSeparator />
<Form {...form}>
<form id={FORM_ID} onSubmit={form.handleSubmit(onSubmit)}>
<DialogSection className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItemLayout
label="Name"
description="Lowercase letters, numbers, and hyphens only"
>
<FormControl>
<Input {...field} placeholder="embed" autoComplete="off" />
</FormControl>
</FormItemLayout>
)}
/>
<FormField
control={form.control}
name="size"
render={({ field }) => (
<FormItemLayout label="Size" description="Fixed at deploy time">
<Select value={field.value} onValueChange={field.onChange}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
{WORKER_SIZES.map((size) => (
<SelectItem key={size} value={size}>
{formatSize(size)}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItemLayout>
)}
/>
<FormField
control={form.control}
name="access"
render={({ field }) => (
<FormItemLayout
label="Access"
description="Public workers accept requests with an anon key"
>
<Select value={field.value} onValueChange={field.onChange}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="private">Private</SelectItem>
<SelectItem value="public">Public</SelectItem>
</SelectContent>
</Select>
</FormItemLayout>
)}
/>
<FormField
control={form.control}
name="instances"
render={({ field }) => (
<FormItemLayout
label="Instances"
description={`${WORKER_MIN_INSTANCES} to ${WORKER_MAX_INSTANCES}`}
>
<FormControl>
<Input
{...field}
type="number"
min={WORKER_MIN_INSTANCES}
max={WORKER_MAX_INSTANCES}
/>
</FormControl>
</FormItemLayout>
)}
/>
</DialogSection>
{/* Bottom-of-form notice, flush with the dialog edges. Own border-top acts
as the divider from the fields; border-bottom flows into the separator below. */}
<Admonition
type="default"
title="Deploys a Deno starter worker"
description="The dashboard deploys a hello-world worker you can then edit and redeploy with the Supabase CLI."
className={cn('mb-0 rounded-none border-x-0 border-b-0')}
/>
</form>
</Form>
<DialogSectionSeparator />
<DialogSection className="space-y-2">
<p className="text-sm text-foreground-light">
Or deploy the same worker from your terminal
</p>
<CreateWorkerSnippets control={form.control} />
</DialogSection>
<DialogFooter>
<Button variant="default" disabled={isPending} onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button variant="primary" type="submit" form={FORM_ID} loading={isPending}>
Deploy worker
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}