{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "create-project",
  "title": "CreateProject",
  "description": "Create-project onboarding panel: project form on the left, the interactive region map on the right.",
  "dependencies": [
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json",
    "https://ui.neon.com/r/button.json",
    "https://ui.neon.com/r/input.json",
    "https://ui.neon.com/r/select.json",
    "https://ui.neon.com/r/switch.json",
    "https://ui.neon.com/r/region-select.json",
    "https://ui.neon.com/r/region-globe.json",
    "https://ui.neon.com/r/region-card.json",
    "https://ui.neon.com/r/label.json"
  ],
  "files": [
    {
      "path": "src/blocks/create-project/create-project.tsx",
      "content": "\"use client\";\n\nimport { Cancel01Icon, DicesIcon, ZapIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport type { ComponentProps, FormEvent, ReactNode } from \"react\";\nimport { useId, useRef, useState } from \"react\";\n\nimport { RegionCard } from \"@/components/region-card/region-card\";\nimport { RegionGlobe } from \"@/components/region-globe/region-globe\";\nimport type { ServerRegion } from \"@/components/region-select/region-select\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface CreateProjectValues {\n  name: string;\n  postgresVersion: string;\n  regionId: string;\n  enableAuth: boolean;\n}\n\nexport type CreateProjectProps = Omit<\n  ComponentProps<\"form\">,\n  \"onSubmit\" | \"title\"\n> & {\n  /** Regions offered in the picker and plotted on the map. */\n  regions: ServerRegion[];\n  /** Postgres versions offered, newest first. */\n  postgresVersions?: string[];\n  /** Initial Postgres version; defaults to the first offered. */\n  defaultPostgresVersion?: string;\n  /** Initial region id; defaults to the first region. */\n  defaultRegionId?: string;\n  /** Called with the form values when Create is pressed. */\n  onSubmit?: (values: CreateProjectValues) => void;\n  /** Renders the Cancel action. */\n  onCancel?: () => void;\n  /** Renders the close button in the header. */\n  onClose?: () => void;\n  /** Locks the actions and swaps the Create label. */\n  isBusy?: boolean;\n  /** Panel heading. */\n  title?: ReactNode;\n  /** Description under the Neon Auth toggle. */\n  authDescription?: ReactNode;\n  /** Measured round-trips by region id, e.g. from useRegionPing. */\n  latencies?: Record<string, number | null | undefined>;\n  /** Hide the tilted globe under the map. */\n  hideGlobe?: boolean;\n  /** Inline field errors, e.g. from a rejected create request. */\n  errors?: Partial<Record<\"name\" | \"region\", string>>;\n};\n\nconst DEFAULT_POSTGRES_VERSIONS = [\"18\", \"17\", \"16\"];\n\n/* Name generator: quiet adjective-noun pairs, Neon-style. */\nconst NAME_ADJECTIVES = [\n  \"misty\",\n  \"quiet\",\n  \"amber\",\n  \"bold\",\n  \"crimson\",\n  \"dawn\",\n  \"emerald\",\n  \"frosty\",\n  \"gentle\",\n  \"hidden\",\n  \"lunar\",\n  \"polished\",\n  \"rapid\",\n  \"silent\",\n  \"velvet\",\n  \"wandering\",\n];\nconst NAME_NOUNS = [\n  \"river\",\n  \"meadow\",\n  \"summit\",\n  \"harbor\",\n  \"canyon\",\n  \"aurora\",\n  \"thicket\",\n  \"lagoon\",\n  \"prairie\",\n  \"glacier\",\n  \"ember\",\n  \"willow\",\n  \"drift\",\n  \"cove\",\n  \"ridge\",\n  \"basin\",\n];\nconst NAME_NUMBER_MAX = 100;\n\n/** Lowest measured round-trip among selectable regions. */\nconst fastestOf = (\n  regions: ServerRegion[],\n  latencies?: Record<string, number | null | undefined>\n) => {\n  if (!latencies) {\n    return null;\n  }\n\n  let best: { id: string; ping: number } | null = null;\n\n  for (const region of regions) {\n    const ping = latencies[region.id];\n\n    const usable = !region.disabled && typeof ping === \"number\";\n\n    if (usable && (!best || ping < best.ping)) {\n      best = { id: region.id, ping };\n    }\n  }\n\n  return best;\n};\n\nconst pick = (list: string[]) =>\n  list[Math.floor(Math.random() * list.length)] ?? \"\";\n\nconst randomProjectName = () =>\n  `${pick(NAME_ADJECTIVES)}-${pick(NAME_NOUNS)}-${Math.floor(\n    Math.random() * NAME_NUMBER_MAX\n  )}`;\n\nconst DEFAULT_AUTH_DESCRIPTION = (\n  <>\n    Neon Auth adds ready-to-use authentication to your app — users and sessions\n    are stored directly in your database. You can also enable it later in\n    project settings.\n  </>\n);\n\n/** Provider-prefixed display label for a region. */\nconst regionLabelOf = (region: ServerRegion) =>\n  region.provider ? `${region.provider} ${region.name}` : region.name;\n\nconst FieldError = ({ children, id }: { children: ReactNode; id: string }) =>\n  children ? (\n    <p className=\"text-destructive text-xs\" id={id} role=\"alert\">\n      {children}\n    </p>\n  ) : null;\n\nconst NameField = ({\n  error,\n  id,\n  inputRef,\n  name,\n  onNameChange,\n}: {\n  error?: string;\n  id: string;\n  inputRef: React.RefObject<HTMLInputElement | null>;\n  name: string;\n  onNameChange: (name: string) => void;\n}) => (\n  <div className=\"flex flex-col gap-1.5\">\n    <Label htmlFor={`${id}-name`}>Project name</Label>\n    <div className=\"relative\">\n      <Input\n        aria-describedby={error ? `${id}-name-error` : undefined}\n        aria-invalid={error ? true : undefined}\n        autoCapitalize=\"off\"\n        autoComplete=\"off\"\n        className=\"pr-9\"\n        enterKeyHint=\"done\"\n        id={`${id}-name`}\n        onChange={(event) => onNameChange(event.target.value)}\n        placeholder=\"e.g., app name or customer name\"\n        ref={inputRef}\n        spellCheck={false}\n        value={name}\n      />\n      <Button\n        aria-label=\"Randomize project name\"\n        className=\"-translate-y-1/2 absolute top-1/2 right-1 text-muted-foreground hover:text-foreground\"\n        onClick={() => {\n          onNameChange(randomProjectName());\n          // Focus and select so the result is instantly replaceable —\n          // a mis-click never costs typed work.\n          requestAnimationFrame(() => {\n            inputRef.current?.focus();\n            inputRef.current?.select();\n          });\n        }}\n        size=\"icon-sm\"\n        type=\"button\"\n        variant=\"ghost\"\n      >\n        <HugeiconsIcon icon={DicesIcon} strokeWidth={2} />\n      </Button>\n    </div>\n    <FieldError id={`${id}-name-error`}>{error}</FieldError>\n  </div>\n);\n\nconst RegionField = ({\n  error,\n  fastest,\n  id,\n  onChoose,\n  regionId,\n  regions,\n  selectedRegion,\n}: {\n  error?: string;\n  fastest: { id: string; ping: number } | null;\n  id: string;\n  onChoose: (id: string) => void;\n  regionId: string;\n  regions: ServerRegion[];\n  selectedRegion?: ServerRegion;\n}) => (\n  <div className=\"flex flex-col gap-1.5\">\n    {/* min-h reserves the action's height, so the button can appear\n        without shifting the form when pings resolve. */}\n    <div className=\"flex min-h-6 items-center justify-between gap-2\">\n      <Label htmlFor={`${id}-region`}>Region</Label>\n      {fastest ? (\n        <Button\n          className=\"h-6 animate-in gap-1 px-1.5 font-mono text-[11px] text-muted-foreground fade-in-0 duration-150 hover:text-foreground motion-reduce:animate-none\"\n          onClick={() => onChoose(fastest.id)}\n          size=\"xs\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          <HugeiconsIcon\n            aria-hidden=\"true\"\n            className=\"size-3\"\n            icon={ZapIcon}\n            strokeWidth={2}\n          />\n          fastest · {fastest.ping} ms\n        </Button>\n      ) : null}\n    </div>\n    <Select\n      items={regions.map((region) => ({\n        label: regionLabelOf(region),\n        value: region.id,\n      }))}\n      onValueChange={(next) => {\n        if (typeof next === \"string\") {\n          onChoose(next);\n        }\n      }}\n      value={regionId}\n    >\n      <SelectTrigger\n        aria-describedby={error ? `${id}-region-error` : undefined}\n        aria-invalid={error ? true : undefined}\n        className=\"w-full\"\n        id={`${id}-region`}\n      >\n        <SelectValue>\n          {selectedRegion ? (\n            regionLabelOf(selectedRegion)\n          ) : (\n            <span className=\"text-muted-foreground\">Select region</span>\n          )}\n        </SelectValue>\n      </SelectTrigger>\n      <SelectContent align=\"start\" alignItemWithTrigger={false}>\n        {regions.map((region) => (\n          <SelectItem\n            disabled={region.disabled}\n            key={region.id}\n            value={region.id}\n          >\n            {regionLabelOf(region)}\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\n    <FieldError id={`${id}-region-error`}>{error}</FieldError>\n    <p className=\"text-muted-foreground text-xs\">\n      Select the region closest to your application.\n    </p>\n  </div>\n);\n\nexport const CreateProject = ({\n  authDescription = DEFAULT_AUTH_DESCRIPTION,\n  className,\n  errors,\n  hideGlobe,\n  latencies,\n  defaultPostgresVersion,\n  defaultRegionId,\n  isBusy,\n  onCancel,\n  onClose,\n  onSubmit,\n  postgresVersions = DEFAULT_POSTGRES_VERSIONS,\n  regions,\n  title = \"Create project\",\n  ...props\n}: CreateProjectProps) => {\n  const id = useId();\n  const nameRef = useRef<HTMLInputElement>(null);\n  const [name, setName] = useState(\"\");\n  const [postgresVersion, setPostgresVersion] = useState(\n    defaultPostgresVersion ?? postgresVersions[0] ?? \"\"\n  );\n  const [regionId, setRegionId] = useState(\n    defaultRegionId ?? regions[0]?.id ?? \"\"\n  );\n  const [enableAuth, setEnableAuth] = useState(false);\n  // The globe idles on a slow spin until the user commits a region\n  // (dropdown or dot click); defaults don't count as choosing.\n  const [regionTouched, setRegionTouched] = useState(false);\n  const selectedRegion = regions.find((region) => region.id === regionId);\n\n  const chooseRegion = (next: string) => {\n    setRegionId(next);\n    setRegionTouched(true);\n  };\n\n  const fastestRegion = fastestOf(regions, latencies);\n  const selectedPing = latencies?.[regionId];\n\n  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n    onSubmit?.({ enableAuth, name, postgresVersion, regionId });\n  };\n\n  return (\n    <form\n      aria-labelledby={`${id}-title`}\n      className={cn(\n        \"@container relative flex w-full flex-col overflow-hidden rounded-xl border border-border/60 bg-card\",\n        className\n      )}\n      data-slot=\"create-project\"\n      onSubmit={handleSubmit}\n      {...props}\n    >\n      {/* pointer-events-none lets globe markers under the header band\n          stay clickable; the close button re-enables its own. */}\n      <div className=\"pointer-events-none relative z-10 flex items-start justify-between gap-4 p-4 pb-0 @xl:p-6 @xl:pb-0\">\n        <h2\n          className=\"font-semibold text-foreground text-xl\"\n          id={`${id}-title`}\n        >\n          {title}\n        </h2>\n        {onClose ? (\n          <Button\n            aria-label=\"Close\"\n            className=\"pointer-events-auto\"\n            onClick={onClose}\n            size=\"icon\"\n            type=\"button\"\n            variant=\"outline\"\n          >\n            <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />\n          </Button>\n        ) : null}\n      </div>\n\n      {/* The globe floats in the panel's top right, oversized and\n          cropped by the dialog edges, console-style. Marker dots stay\n          clickable; the sphere itself is a backdrop. */}\n      {hideGlobe ? null : (\n        <div\n          className=\"-top-[30%] -right-[20%] pointer-events-none absolute z-0 hidden w-[70%] min-w-80 @3xl:block @5xl:-top-[16%] @5xl:-right-[10%] @5xl:w-[52%]\"\n          data-slot=\"create-project-globe\"\n        >\n          <RegionGlobe\n            latencies={latencies}\n            onValueChange={chooseRegion}\n            regions={regions}\n            spin={!regionTouched}\n            value={regionId}\n          />\n        </div>\n      )}\n\n      <div className=\"grid gap-6 p-4 @3xl:min-h-[24rem] @3xl:grid-cols-[minmax(0,24rem)_minmax(0,1fr)] @xl:gap-8 @xl:p-6\">\n        <div className=\"flex flex-col gap-5\">\n          <div className=\"grid gap-4 @md:grid-cols-[minmax(0,1fr)_auto]\">\n            <NameField\n              error={errors?.name}\n              id={id}\n              inputRef={nameRef}\n              name={name}\n              onNameChange={setName}\n            />\n            <div className=\"flex flex-col gap-1.5\">\n              <Label htmlFor={`${id}-pg`}>Postgres version</Label>\n              <Select\n                items={postgresVersions.map((version) => ({\n                  label: version,\n                  value: version,\n                }))}\n                onValueChange={(next) => {\n                  if (typeof next === \"string\") {\n                    setPostgresVersion(next);\n                  }\n                }}\n                value={postgresVersion}\n              >\n                <SelectTrigger className=\"w-20\" id={`${id}-pg`}>\n                  <SelectValue />\n                </SelectTrigger>\n                <SelectContent>\n                  {postgresVersions.map((version) => (\n                    <SelectItem key={version} value={version}>\n                      {version}\n                    </SelectItem>\n                  ))}\n                </SelectContent>\n              </Select>\n            </div>\n          </div>\n\n          <RegionField\n            error={errors?.region}\n            fastest={fastestRegion}\n            id={id}\n            onChoose={chooseRegion}\n            regionId={regionId}\n            regions={regions}\n            selectedRegion={selectedRegion}\n          />\n\n          <div className=\"flex flex-col gap-2\">\n            <Label htmlFor={`${id}-auth`}>Enable Neon Auth</Label>\n            <div className=\"flex items-start gap-3\">\n              <Switch\n                aria-describedby={`${id}-auth-description`}\n                checked={enableAuth}\n                id={`${id}-auth`}\n                onCheckedChange={setEnableAuth}\n              />\n              <p\n                className=\"text-muted-foreground text-sm\"\n                id={`${id}-auth-description`}\n              >\n                {authDescription}\n              </p>\n            </div>\n          </div>\n        </div>\n\n        {/* Region card: floats over the globe's lower-left, but lives\n            in the grid so the footer can never crop it. */}\n        {hideGlobe || !selectedRegion ? null : (\n          <div\n            aria-live=\"polite\"\n            className=\"pointer-events-none relative z-10 hidden @3xl:block\"\n          >\n            <RegionCard\n              className=\"absolute right-[10%] bottom-2 max-w-[85%]\"\n              key={selectedRegion.id}\n              ping={selectedPing}\n              regionId={selectedRegion.id}\n              title={regionLabelOf(selectedRegion)}\n            />\n          </div>\n        )}\n      </div>\n\n      <div className=\"relative z-10 flex flex-col-reverse justify-end gap-2 border-border/60 border-t bg-card p-4 @md:flex-row\">\n        {/* Cancel hides while busy (no false abort affordance); Create\n            reserves width so the label swap doesn't shift the footer. */}\n        {onCancel && !isBusy ? (\n          <Button\n            className=\"w-full @md:w-auto\"\n            onClick={onCancel}\n            type=\"button\"\n            variant=\"outline\"\n          >\n            Cancel\n          </Button>\n        ) : null}\n        <Button\n          className=\"w-full min-w-24 @md:w-auto\"\n          disabled={isBusy}\n          type=\"submit\"\n        >\n          {isBusy ? \"Creating…\" : \"Create\"}\n        </Button>\n      </div>\n    </form>\n  );\n};\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}