{
  "$schema": "https://ui.shadcn.com/schema/registry.json",
  "name": "uiblocks",
  "homepage": "https://uiblocks.org",
  "items": [
    {
      "name": "ai-01",
      "type": "registry:block",
      "title": "AI Chat with Voice Input",
      "description": "AI Chat with Voice Input. AI prompt inputs and assistant interfaces with model selection and media attachments.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/ai/ai-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-01/ai-01.tsx",
          "content": "'use client';\n\nimport {\n  IconMicrophone,\n  IconPaperclip,\n  IconPlus,\n  IconSearch,\n  IconSend,\n  IconSparkles,\n  IconWaveSine,\n} from '@tabler/icons-react';\nimport type React from 'react';\nimport { useRef, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport default function Ai01() {\n  const [message, setMessage] = useState('');\n  const [isExpanded, setIsExpanded] = useState(false);\n  const textareaRef = useRef<HTMLTextAreaElement>(null);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const send = () => {\n    if (!message.trim()) {\n      return;\n    }\n\n    setMessage('');\n    setIsExpanded(false);\n\n    if (textareaRef.current) {\n      textareaRef.current.style.height = 'auto';\n    }\n  };\n\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    send();\n  };\n\n  const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n    setMessage(e.target.value);\n\n    if (textareaRef.current) {\n      textareaRef.current.style.height = 'auto';\n      textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;\n    }\n\n    setIsExpanded(e.target.value.length > 100 || e.target.value.includes('\\n'));\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === 'Enter' && !e.shiftKey) {\n      e.preventDefault();\n      send();\n    }\n  };\n\n  return (\n    <div className=\"w-full\">\n      <h1 className=\"mx-auto mb-6 max-w-2xl text-balance text-center font-semibold text-3xl text-foreground leading-tight tracking-tight\">\n        How can I help you today?\n      </h1>\n\n      <form className=\"group/composer w-full\" onSubmit={handleSubmit}>\n        <input className=\"sr-only\" multiple ref={fileInputRef} type=\"file\" />\n\n        <div\n          className={cn(\n            'mx-auto w-full max-w-2xl cursor-text overflow-clip bg-background bg-clip-padding p-2.5 shadow-[0_0_0_1px_oklch(0_0_0/0.08),0_1px_2px_-1px_oklch(0_0_0/0.08),0_4px_12px_-2px_oklch(0_0_0/0.08)] dark:bg-muted/50 dark:shadow-[0_0_0_1px_oklch(1_0_0/0.1)]',\n            isExpanded\n              ? \"grid rounded-[28px] [grid-template-areas:'header'_'primary'_'footer'] [grid-template-columns:1fr] [grid-template-rows:auto_1fr_auto]\"\n              : \"grid rounded-full [grid-template-areas:'header_header_header'_'leading_primary_trailing'_'._footer_.'] [grid-template-columns:auto_1fr_auto] [grid-template-rows:auto_1fr_auto]\"\n          )}\n        >\n          <div\n            className={cn(\n              'flex overflow-x-hidden',\n              isExpanded\n                ? 'px-2 pt-1.5 pb-2'\n                : '-my-2.5 min-h-14 items-center px-1.5'\n            )}\n            style={{ gridArea: 'primary' }}\n          >\n            <div className=\"max-h-52 flex-1 overflow-auto\">\n              <Textarea\n                className=\"scrollbar-thin min-h-0 resize-none rounded-none border-0 p-0 text-base placeholder:text-muted-foreground focus-visible:ring-0 focus-visible:ring-offset-0 md:text-base dark:bg-transparent\"\n                onChange={handleTextareaChange}\n                onKeyDown={handleKeyDown}\n                placeholder=\"Ask anything\"\n                ref={textareaRef}\n                rows={1}\n                value={message}\n              />\n            </div>\n          </div>\n\n          <div\n            className={cn('flex items-center', { hidden: isExpanded })}\n            style={{ gridArea: 'leading' }}\n          >\n            <DropdownMenu>\n              <DropdownMenuTrigger\n                render={\n                  <Button\n                    aria-label=\"Add attachments\"\n                    className=\"rounded-full transition-[scale,background-color] duration-150 ease-out active:scale-[0.96]\"\n                    size=\"icon-lg\"\n                    type=\"button\"\n                    variant=\"ghost\"\n                  />\n                }\n              >\n                <IconPlus\n                  className=\"size-5 text-muted-foreground\"\n                  stroke={1.5}\n                />\n              </DropdownMenuTrigger>\n\n              <DropdownMenuContent\n                align=\"start\"\n                className=\"w-64 rounded-[16px] p-1.5\"\n              >\n                <DropdownMenuGroup className=\"space-y-1\">\n                  <DropdownMenuItem\n                    className=\"rounded-[10px] px-2.5 py-2\"\n                    onClick={() => fileInputRef.current?.click()}\n                  >\n                    <IconPaperclip\n                      className=\"opacity-60\"\n                      size={20}\n                      stroke={1.5}\n                    />\n                    Add photos & files\n                  </DropdownMenuItem>\n                  <DropdownMenuItem\n                    className=\"rounded-[10px] px-2.5 py-2\"\n                    onClick={() => {}}\n                  >\n                    <IconSparkles\n                      className=\"opacity-60\"\n                      size={20}\n                      stroke={1.5}\n                    />\n                    Agent mode\n                  </DropdownMenuItem>\n                  <DropdownMenuItem\n                    className=\"rounded-[10px] px-2.5 py-2\"\n                    onClick={() => {}}\n                  >\n                    <IconSearch className=\"opacity-60\" size={20} stroke={1.5} />\n                    Deep Research\n                  </DropdownMenuItem>\n                </DropdownMenuGroup>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </div>\n\n          <div\n            className=\"flex items-center gap-2\"\n            style={{ gridArea: isExpanded ? 'footer' : 'trailing' }}\n          >\n            <div className=\"ms-auto flex items-center gap-1.5\">\n              <Button\n                aria-label=\"Record audio message\"\n                className=\"rounded-full transition-[scale,background-color] duration-150 ease-out active:scale-[0.96]\"\n                size=\"icon-lg\"\n                type=\"button\"\n                variant=\"ghost\"\n              >\n                <IconMicrophone\n                  className=\"size-5 text-muted-foreground\"\n                  stroke={1.5}\n                />\n              </Button>\n\n              <Button\n                aria-label=\"Audio visualization\"\n                className=\"rounded-full transition-[scale,background-color] duration-150 ease-out active:scale-[0.96]\"\n                size=\"icon-lg\"\n                type=\"button\"\n                variant=\"ghost\"\n              >\n                <IconWaveSine\n                  className=\"size-5 text-muted-foreground\"\n                  stroke={1.5}\n                />\n              </Button>\n\n              <Button\n                aria-label=\"Send message\"\n                className=\"rounded-full transition-[scale,opacity,background-color] duration-150 ease-out active:scale-[0.96] disabled:opacity-40\"\n                disabled={!message.trim()}\n                size=\"icon-lg\"\n                type=\"submit\"\n              >\n                <IconSend className=\"size-4.5\" stroke={1.5} />\n              </Button>\n            </div>\n          </div>\n        </div>\n      </form>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/ai-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/ai-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-01/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-01/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "ai",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/ai-01/ai-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/ai/ai-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "ai",
          "prompt",
          "chat",
          "assistant",
          "人工智能",
          "提示词",
          "聊天",
          "助手"
        ],
        "contentGuidelines": [
          "Provide your own generation endpoint, cancellation, and error handling.",
          "Test a long prompt, empty submission, and pending generation state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/ai-01/ai-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/ai-01"
      }
    },
    {
      "name": "ai-02",
      "type": "registry:block",
      "title": "AI Chat with Model Selection",
      "description": "AI Chat with Model Selection. AI prompt inputs and assistant interfaces with model selection and media attachments.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/ai/ai-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-02/ai-02.tsx",
          "content": "'use client';\n\nimport {\n  IconAlertTriangle,\n  IconArrowUp,\n  IconCloud,\n  IconFileSpark,\n  IconGauge,\n  IconPhotoScan,\n} from '@tabler/icons-react';\nimport { useRef, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\n\nconst PROMPTS = [\n  {\n    icon: IconFileSpark,\n    text: 'Write documentation',\n    prompt:\n      'Write comprehensive documentation for this codebase, including setup instructions, API references, and usage examples.',\n  },\n  {\n    icon: IconGauge,\n    text: 'Optimize performance',\n    prompt:\n      'Analyze the codebase for performance bottlenecks and suggest optimizations to improve loading times and runtime efficiency.',\n  },\n  {\n    icon: IconAlertTriangle,\n    text: 'Find and fix 3 bugs',\n    prompt:\n      'Scan through the codebase to identify and fix 3 critical bugs, providing detailed explanations for each fix.',\n  },\n];\n\nconst MODELS = [\n  {\n    value: 'gpt-5',\n    name: 'GPT-5',\n    description: 'Most advanced model',\n    max: true,\n  },\n  {\n    value: 'gpt-4o',\n    name: 'GPT-4o',\n    description: 'Fast and capable',\n  },\n  {\n    value: 'claude-3.5',\n    name: 'Claude 3.5 Sonnet',\n    description: 'Great for coding tasks',\n  },\n];\n\nexport default function Ai02() {\n  const [inputValue, setInputValue] = useState('');\n  const [modelValue, setModelValue] = useState(MODELS[0].value);\n  const selectedModel = MODELS.find((m) => m.value === modelValue) ?? MODELS[0];\n  const inputRef = useRef<HTMLTextAreaElement>(null);\n\n  const handlePromptClick = (prompt: string) => {\n    if (inputRef.current) {\n      inputRef.current.value = prompt;\n      setInputValue(prompt);\n      inputRef.current.focus();\n    }\n  };\n\n  const maxBadge = (\n    <span className=\"inline-flex h-[15px] items-center rounded-[4px] border border-border/80 bg-muted/40 px-[5px]\">\n      <span\n        className=\"font-bold text-[9px] uppercase leading-none tracking-[0.06em]\"\n        style={{\n          background:\n            'linear-gradient(to right, rgb(129, 161, 193), rgb(125, 124, 155))',\n          WebkitBackgroundClip: 'text',\n          WebkitTextFillColor: 'transparent',\n        }}\n      >\n        MAX\n      </span>\n    </span>\n  );\n\n  return (\n    <div className=\"flex w-[calc(42rem-5rem)] flex-col gap-4\">\n      <div className=\"flex min-h-[120px] cursor-text flex-col rounded-2xl border border-border bg-card shadow-lg\">\n        <div className=\"relative max-h-[258px] flex-1 overflow-y-auto\">\n          <Textarea\n            className=\"min-h-[48.4px] w-full resize-none whitespace-pre-wrap break-words border-0 bg-transparent! p-3 text-[16px] text-foreground shadow-none outline-none transition-[padding] duration-200 ease-in-out focus-visible:ring-0 focus-visible:ring-offset-0\"\n            onChange={(e) => setInputValue(e.target.value)}\n            placeholder=\"Ask anything\"\n            ref={inputRef}\n            value={inputValue}\n          />\n        </div>\n\n        <div className=\"flex min-h-[40px] items-center gap-2 p-2 pb-1\">\n          <div className=\"flex size-7 items-center justify-center rounded-full bg-muted\">\n            <IconCloud className=\"size-4 text-muted-foreground\" />\n          </div>\n\n          <Select\n            onValueChange={(value) => value && setModelValue(value)}\n            value={modelValue}\n          >\n            <SelectTrigger\n              className=\"h-7 rounded-full border-transparent bg-transparent px-2 font-medium text-foreground text-sm shadow-none transition-[background-color,color] hover:bg-muted aria-expanded:bg-muted dark:bg-transparent dark:hover:bg-muted\"\n              size=\"sm\"\n            >\n              <SelectValue>\n                <span className=\"flex items-center gap-1.5\">\n                  <span>{selectedModel.name}</span>\n                  {selectedModel.max && maxBadge}\n                </span>\n              </SelectValue>\n            </SelectTrigger>\n            <SelectContent\n              align=\"start\"\n              alignItemWithTrigger={false}\n              className=\"w-64 p-1\"\n              sideOffset={6}\n            >\n              {MODELS.map((model) => (\n                <SelectItem\n                  className=\"rounded-md py-1.5\"\n                  key={model.value}\n                  value={model.value}\n                >\n                  <span className=\"flex flex-col gap-0.5\">\n                    <span className=\"flex items-center gap-1.5 font-medium\">\n                      {model.name}\n                      {model.max && maxBadge}\n                    </span>\n                    <span className=\"text-muted-foreground text-xs\">\n                      {model.description}\n                    </span>\n                  </span>\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n\n          <div className=\"ml-auto flex items-center gap-1.5\">\n            <Button\n              aria-label=\"Attach images\"\n              className=\"rounded-full text-muted-foreground transition-[background-color,color] hover:text-foreground\"\n              size=\"icon-sm\"\n              title=\"Attach images\"\n              variant=\"ghost\"\n            >\n              <IconPhotoScan className=\"size-4.5\" />\n            </Button>\n\n            <Button\n              aria-label=\"Send message\"\n              className=\"rounded-full transition-[background-color,scale,opacity] active:not-disabled:scale-[0.96]\"\n              disabled={!inputValue}\n              size=\"icon-sm\"\n            >\n              <IconArrowUp className=\"size-4\" />\n            </Button>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex flex-wrap justify-center gap-2\">\n        {PROMPTS.map((button) => {\n          const IconComponent = button.icon;\n          return (\n            <Button\n              className=\"h-8 gap-1.5 rounded-full border-border bg-background pr-3.5 pl-2.5 font-normal text-foreground text-sm shadow-[0_1px_2px_oklch(0_0_0/0.05)] transition-[background-color,color,scale,box-shadow] duration-150 ease-out hover:bg-muted active:scale-[0.96] dark:bg-muted/40 dark:hover:bg-muted\"\n              key={button.text}\n              onClick={() => handlePromptClick(button.prompt)}\n              variant=\"outline\"\n            >\n              <IconComponent\n                className=\"size-4 text-muted-foreground transition-colors duration-150 group-hover/button:text-foreground\"\n                strokeWidth={1.75}\n              />\n              {button.text}\n            </Button>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/ai-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/ai-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-02/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-02/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "ai",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/ai-02/ai-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/ai/ai-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "ai",
          "prompt",
          "chat",
          "assistant",
          "人工智能",
          "提示词",
          "聊天",
          "助手"
        ],
        "contentGuidelines": [
          "Provide your own generation endpoint, cancellation, and error handling.",
          "Test a long prompt, empty submission, and pending generation state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/ai-02/ai-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/ai-02"
      }
    },
    {
      "name": "ai-03",
      "type": "registry:block",
      "title": "AI Chat Compact Interface",
      "description": "AI Chat Compact Interface. AI prompt inputs and assistant interfaces with model selection and media attachments.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/ai/ai-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-03/ai-03.tsx",
          "content": "'use client';\n\nimport {\n  IconBolt,\n  IconChevronDown,\n  IconCircle,\n  IconCircleDashed,\n  IconCloud,\n  IconCode,\n  IconDeviceLaptop,\n  IconHistory,\n  IconPaperclip,\n  IconPlus,\n  IconProgress,\n  IconRobot,\n  IconSend,\n  IconUser,\n  IconWand,\n  IconWorld,\n} from '@tabler/icons-react';\nimport { useRef, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport default function Ai03() {\n  const [input, setInput] = useState('');\n  const [selectedModel, setSelectedModel] = useState('Local');\n  const [selectedAgent, setSelectedAgent] = useState('Agent');\n  const [selectedPerformance, setSelectedPerformance] = useState('High');\n  const [autoMode, setAutoMode] = useState(false);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    if (input.trim()) {\n    }\n  };\n\n  return (\n    <div className=\"w-xl\">\n      <div className=\"overflow-hidden rounded-2xl border border-border bg-background\">\n        <input\n          className=\"sr-only\"\n          multiple\n          onChange={(e) => {}}\n          ref={fileInputRef}\n          type=\"file\"\n        />\n\n        <div className=\"grow px-3 pt-3 pb-2\">\n          <form onSubmit={handleSubmit}>\n            <Textarea\n              className=\"max-h-[25vh] min-h-10 w-full resize-none border-0 border-none bg-transparent! p-0 text-foreground text-sm placeholder-muted-foreground shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0\"\n              onChange={(e) => setInput(e.target.value)}\n              onInput={(e) => {\n                const target = e.target as HTMLTextAreaElement;\n                target.style.height = 'auto';\n                target.style.height = target.scrollHeight + 'px';\n              }}\n              placeholder=\"Ask anything\"\n              rows={1}\n              value={input}\n            />\n          </form>\n        </div>\n\n        <div className=\"mb-2 flex items-center justify-between px-2\">\n          <div className=\"flex items-center gap-1\">\n            <DropdownMenu>\n              <DropdownMenuTrigger\n                render={\n                  <Button\n                    className=\"h-7 w-7 rounded-full border border-border p-0 hover:bg-accent\"\n                    size=\"sm\"\n                    variant=\"ghost\"\n                  />\n                }\n              >\n                <IconPlus className=\"size-3\" />\n              </DropdownMenuTrigger>\n\n              <DropdownMenuContent\n                align=\"start\"\n                className=\"max-w-xs rounded-2xl p-1.5\"\n              >\n                <DropdownMenuGroup className=\"space-y-1\">\n                  <DropdownMenuItem\n                    className=\"rounded-[calc(1rem-6px)] text-xs\"\n                    onClick={() => fileInputRef.current?.click()}\n                  >\n                    <IconPaperclip className=\"opacity-60\" size={16} />\n                    Attach Files\n                  </DropdownMenuItem>\n                  <DropdownMenuItem\n                    className=\"rounded-[calc(1rem-6px)] text-xs\"\n                    onClick={() => {}}\n                  >\n                    <IconCode className=\"opacity-60\" size={16} />\n                    Code Interpreter\n                  </DropdownMenuItem>\n                  <DropdownMenuItem\n                    className=\"rounded-[calc(1rem-6px)] text-xs\"\n                    onClick={() => {}}\n                  >\n                    <IconWorld className=\"opacity-60\" size={16} />\n                    Web Search\n                  </DropdownMenuItem>\n                  <DropdownMenuItem\n                    className=\"rounded-[calc(1rem-6px)] text-xs\"\n                    onClick={() => {}}\n                  >\n                    <IconHistory className=\"opacity-60\" size={16} />\n                    Chat History\n                  </DropdownMenuItem>\n                </DropdownMenuGroup>\n              </DropdownMenuContent>\n            </DropdownMenu>\n\n            <Button\n              className={cn(\n                'h-7 rounded-full border border-border px-2 hover:bg-accent ',\n                {\n                  'border-primary/30 bg-primary/10 text-primary': autoMode,\n                  'text-muted-foreground': !autoMode,\n                }\n              )}\n              onClick={() => setAutoMode(!autoMode)}\n              size=\"sm\"\n              variant=\"ghost\"\n            >\n              <IconWand className=\"size-3\" />\n              <span className=\"text-xs\">Auto</span>\n            </Button>\n          </div>\n\n          <div>\n            <Button\n              className=\"size-7 rounded-full bg-primary p-0 disabled:cursor-not-allowed disabled:opacity-50\"\n              disabled={!input.trim()}\n              onClick={handleSubmit}\n              type=\"submit\"\n            >\n              <IconSend className=\"size-3 fill-primary\" />\n            </Button>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"flex items-center gap-0 pt-2\">\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <Button\n                className=\"h-6 rounded-full border border-transparent px-2 text-muted-foreground text-xs hover:bg-accent\"\n                size=\"sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <IconDeviceLaptop className=\"size-3\" />\n            <span>{selectedModel}</span>\n            <IconChevronDown className=\"size-3\" />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"start\"\n            className=\"max-w-xs rounded-2xl border-border bg-popover p-1.5\"\n          >\n            <DropdownMenuGroup className=\"space-y-1\">\n              <DropdownMenuItem\n                className=\"rounded-[calc(1rem-6px)] text-xs\"\n                onClick={() => setSelectedModel('Local')}\n              >\n                <IconDeviceLaptop className=\"opacity-60\" size={16} />\n                Local\n              </DropdownMenuItem>\n              <DropdownMenuItem\n                className=\"rounded-[calc(1rem-6px)] text-xs\"\n                onClick={() => setSelectedModel('Cloud')}\n              >\n                <IconCloud className=\"opacity-60\" size={16} />\n                Cloud\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n          </DropdownMenuContent>\n        </DropdownMenu>\n\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <Button\n                className=\"h-6 rounded-full border border-transparent px-2 text-muted-foreground text-xs hover:bg-accent\"\n                size=\"sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <IconUser className=\"size-3\" />\n            <span>{selectedAgent}</span>\n            <IconChevronDown className=\"size-3\" />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"start\"\n            className=\"max-w-xs rounded-2xl border-border bg-popover p-1.5\"\n          >\n            <DropdownMenuGroup className=\"space-y-1\">\n              <DropdownMenuItem\n                className=\"rounded-[calc(1rem-6px)] text-xs\"\n                onClick={() => setSelectedAgent('Agent')}\n              >\n                <IconUser className=\"opacity-60\" size={16} />\n                Agent\n              </DropdownMenuItem>\n              <DropdownMenuItem\n                className=\"rounded-[calc(1rem-6px)] text-xs\"\n                onClick={() => setSelectedAgent('Assistant')}\n              >\n                <IconRobot className=\"opacity-60\" size={16} />\n                Assistant\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n          </DropdownMenuContent>\n        </DropdownMenu>\n\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <Button\n                className=\"h-6 rounded-full border border-transparent px-2 text-muted-foreground text-xs hover:bg-accent\"\n                size=\"sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <IconBolt className=\"size-3\" />\n            <span>{selectedPerformance}</span>\n            <IconChevronDown className=\"size-3\" />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"start\"\n            className=\"max-w-xs rounded-2xl border-border bg-popover p-1.5\"\n          >\n            <DropdownMenuGroup className=\"space-y-1\">\n              <DropdownMenuItem\n                className=\"rounded-[calc(1rem-6px)] text-xs\"\n                onClick={() => setSelectedPerformance('High')}\n              >\n                <IconCircle className=\"opacity-60\" size={16} />\n                High\n              </DropdownMenuItem>\n              <DropdownMenuItem\n                className=\"rounded-[calc(1rem-6px)] text-xs\"\n                onClick={() => setSelectedPerformance('Medium')}\n              >\n                <IconProgress className=\"opacity-60\" size={16} />\n                Medium\n              </DropdownMenuItem>\n              <DropdownMenuItem\n                className=\"rounded-[calc(1rem-6px)] text-xs\"\n                onClick={() => setSelectedPerformance('Low')}\n              >\n                <IconCircleDashed className=\"opacity-60\" size={16} />\n                Low\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n          </DropdownMenuContent>\n        </DropdownMenu>\n\n        <div className=\"flex-1\" />\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/ai-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/ai-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-03/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-03/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-03/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "ai",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/ai-03/ai-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/ai/ai-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "ai",
          "prompt",
          "chat",
          "assistant",
          "人工智能",
          "提示词",
          "聊天",
          "助手"
        ],
        "contentGuidelines": [
          "Provide your own generation endpoint, cancellation, and error handling.",
          "Test a long prompt, empty submission, and pending generation state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/ai-03/ai-03.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/ai-03"
      }
    },
    {
      "name": "ai-04",
      "type": "registry:block",
      "title": "AI Chat with File Attachments",
      "description": "AI Chat with File Attachments. AI prompt inputs and assistant interfaces with model selection and media attachments.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/ai/ai-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-04/ai-04.tsx",
          "content": "\"use client\";\n\nimport {\n  IconAdjustmentsHorizontal,\n  IconArrowUp,\n  IconBrandFigma,\n  IconCamera,\n  IconCirclePlus,\n  IconClipboard,\n  IconFileUpload,\n  IconHistory,\n  IconLayoutDashboard,\n  IconLink,\n  IconPaperclip,\n  IconPlayerPlay,\n  IconPlus,\n  IconSparkles,\n  IconTemplate,\n  IconX,\n} from \"@tabler/icons-react\";\nimport { useRef, useState } from \"react\";\nimport { Badge } from \"./shared/components/ui/badge\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport { Label } from \"./shared/components/ui/label\";\nimport { Switch } from \"./shared/components/ui/switch\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\nimport { cn } from \"./shared/lib/utils\";\n\ninterface AttachedFile {\n  id: string;\n  name: string;\n  file: File;\n  preview?: string;\n}\n\nconst ACTIONS = [\n  { id: \"clone-screenshot\", icon: IconCamera, label: \"Clone a Screenshot\" },\n  { id: \"import-figma\", icon: IconBrandFigma, label: \"Import from Figma\" },\n  { id: \"upload-project\", icon: IconFileUpload, label: \"Upload a Project\" },\n  { id: \"landing-page\", icon: IconLayoutDashboard, label: \"Landing Page\" },\n];\n\nexport default function Ai04({\n  onSubmit,\n}: {\n  onSubmit?: (prompt: string) => void;\n}) {\n  const [prompt, setPrompt] = useState(\"\");\n  const [isDragOver, setIsDragOver] = useState(false);\n  const [attachedFiles, setAttachedFiles] = useState<AttachedFile[]>([]);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const [settings, setSettings] = useState({\n    autoComplete: true,\n    streaming: false,\n    showHistory: false,\n  });\n\n  const generateFileId = () => Math.random().toString(36).substring(7);\n  const processFiles = (files: File[]) => {\n    for (const file of files) {\n      const fileId = generateFileId();\n      const attachedFile: AttachedFile = {\n        id: fileId,\n        name: file.name,\n        file,\n      };\n\n      if (file.type.startsWith(\"image/\")) {\n        const reader = new FileReader();\n        reader.onload = () => {\n          setAttachedFiles((prev) =>\n            prev.map((f) =>\n              f.id === fileId ? { ...f, preview: reader.result as string } : f\n            )\n          );\n        };\n        reader.readAsDataURL(file);\n      }\n\n      setAttachedFiles((prev) => [...prev, attachedFile]);\n    }\n  };\n  const submitPrompt = () => {\n    if (prompt.trim() && onSubmit) {\n      onSubmit(prompt.trim());\n      setPrompt(\"\");\n    }\n  };\n  const updateSetting = (key: keyof typeof settings, value: boolean) => {\n    setSettings((prev) => ({ ...prev, [key]: value }));\n  };\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    submitPrompt();\n  };\n  const handleDragOver = (e: React.DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(true);\n  };\n  const handleDragLeave = (e: React.DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(false);\n  };\n  const handleDrop = (e: React.DragEvent) => {\n    e.preventDefault();\n    setIsDragOver(false);\n\n    const files = Array.from(e.dataTransfer.files);\n    if (files.length > 0) {\n      processFiles(files);\n    }\n  };\n  const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n    setPrompt(e.target.value);\n  };\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === \"Enter\" && !e.shiftKey) {\n      e.preventDefault();\n      submitPrompt();\n    }\n  };\n\n  const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const files = Array.from(e.target.files || []);\n    processFiles(files);\n\n    if (fileInputRef.current) {\n      fileInputRef.current.value = \"\";\n    }\n  };\n\n  const handleRemoveFile = (fileId: string) => {\n    setAttachedFiles((prev) => prev.filter((file) => file.id !== fileId));\n  };\n\n  return (\n    <div className=\"mx-auto flex w-full flex-col gap-4\">\n      <h1 className=\"text-balance text-pretty text-center font-heading font-semibold text-[29px] text-foreground tracking-tighter sm:text-[32px] md:text-[46px]\">\n        Prompt. Refine. Ship.\n      </h1>\n      <h2 className=\"-my-5 text-balance pb-4 text-center text-muted-foreground text-xl\">\n        Build real, working software just by describing it\n      </h2>\n\n      <div className=\"relative z-10 mx-auto flex w-full max-w-2xl flex-col content-center\">\n        <form\n          className=\"overflow-visible rounded-xl border p-2 transition-colors duration-200 focus-within:border-ring\"\n          onDragLeave={handleDragLeave}\n          onDragOver={handleDragOver}\n          onDrop={handleDrop}\n          onSubmit={handleSubmit}\n        >\n          {attachedFiles.length > 0 && (\n            <div className=\"relative mb-2 flex w-fit items-center gap-2 overflow-hidden\">\n              {attachedFiles.map((file) => (\n                <Badge\n                  className=\"group relative h-6 max-w-30 cursor-pointer overflow-hidden px-0 text-[13px] transition-colors hover:bg-accent\"\n                  key={file.id}\n                  variant=\"outline\"\n                >\n                  <span className=\"flex h-full items-center gap-1.5 overflow-hidden pl-1 font-normal\">\n                    <div className=\"relative flex h-4 min-w-4 items-center justify-center\">\n                      {file.preview ? (\n                        <img\n                          alt={file.name}\n                          className=\"absolute inset-0 h-4 w-4 rounded border object-cover\"\n                          height={16}\n                          src={file.preview}\n                          width={16}\n                        />\n                      ) : (\n                        <IconPaperclip className=\"opacity-60\" size={12} />\n                      )}\n                    </div>\n                    <span className=\"inline overflow-hidden truncate pr-1.5\">\n                      {file.name}\n                    </span>\n                  </span>\n                  <button\n                    className=\"absolute right-1 z-10 rounded-sm p-0.5 text-muted-foreground opacity-0 focus-visible:bg-accent focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-background group-hover:opacity-100\"\n                    onClick={() => handleRemoveFile(file.id)}\n                    type=\"button\"\n                  >\n                    <IconX size={12} />\n                  </button>\n                </Badge>\n              ))}\n            </div>\n          )}\n          <Textarea\n            className=\"max-h-50 min-h-12 resize-none rounded-none border-none bg-transparent! p-0 text-sm shadow-none focus-visible:border-transparent focus-visible:ring-0\"\n            onChange={handleTextareaChange}\n            onKeyDown={handleKeyDown}\n            placeholder=\"Ask anything\"\n            value={prompt}\n          />\n\n          <div className=\"flex items-center gap-1\">\n            <div className=\"flex items-end gap-0.5 sm:gap-1\">\n              <input\n                className=\"sr-only\"\n                multiple\n                onChange={handleFileSelect}\n                ref={fileInputRef}\n                type=\"file\"\n              />\n\n              <DropdownMenu>\n                <DropdownMenuTrigger\n                  render={\n                    <Button\n                      aria-label=\"Add attachments\"\n                      className=\"ml-[-2px] rounded-md\"\n                      size=\"icon-sm\"\n                      type=\"button\"\n                      variant=\"ghost\"\n                    />\n                  }\n                >\n                  <IconPlus size={16} />\n                </DropdownMenuTrigger>\n                <DropdownMenuContent\n                  align=\"start\"\n                  className=\"max-w-xs rounded-2xl p-1.5\"\n                >\n                  <DropdownMenuGroup className=\"space-y-1\">\n                    <DropdownMenuItem\n                      className=\"rounded-md text-xs\"\n                      onClick={() => fileInputRef.current?.click()}\n                    >\n                      <div className=\"flex items-center gap-2\">\n                        <IconPaperclip\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <span>Attach Files</span>\n                      </div>\n                    </DropdownMenuItem>\n                    <DropdownMenuItem className=\"rounded-md text-xs\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconLink className=\"text-muted-foreground\" size={16} />\n                        <span>Import from URL</span>\n                      </div>\n                    </DropdownMenuItem>\n                    <DropdownMenuItem className=\"rounded-md text-xs\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconClipboard\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <span>Paste from Clipboard</span>\n                      </div>\n                    </DropdownMenuItem>\n                    <DropdownMenuItem className=\"rounded-md text-xs\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconTemplate\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <span>Use Template</span>\n                      </div>\n                    </DropdownMenuItem>\n                  </DropdownMenuGroup>\n                </DropdownMenuContent>\n              </DropdownMenu>\n\n              <DropdownMenu>\n                <DropdownMenuTrigger\n                  render={\n                    <Button\n                      aria-label=\"Adjust settings\"\n                      className=\"rounded-md\"\n                      size=\"icon-sm\"\n                      type=\"button\"\n                      variant=\"ghost\"\n                    />\n                  }\n                >\n                  <IconAdjustmentsHorizontal size={16} />\n                </DropdownMenuTrigger>\n                <DropdownMenuContent\n                  align=\"start\"\n                  className=\"w-48 rounded-2xl p-3\"\n                >\n                  <DropdownMenuGroup className=\"space-y-3\">\n                    <div className=\"flex items-center justify-between\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconSparkles\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <Label className=\"text-xs\">Auto-complete</Label>\n                      </div>\n                      <Switch\n                        checked={settings.autoComplete}\n                        className=\"scale-75\"\n                        onCheckedChange={(value) =>\n                          updateSetting(\"autoComplete\", value)\n                        }\n                      />\n                    </div>\n\n                    <div className=\"flex items-center justify-between\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconPlayerPlay\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <Label className=\"text-xs\">Streaming</Label>\n                      </div>\n                      <Switch\n                        checked={settings.streaming}\n                        className=\"scale-75\"\n                        onCheckedChange={(value) =>\n                          updateSetting(\"streaming\", value)\n                        }\n                      />\n                    </div>\n\n                    <div className=\"flex items-center justify-between\">\n                      <div className=\"flex items-center gap-2\">\n                        <IconHistory\n                          className=\"text-muted-foreground\"\n                          size={16}\n                        />\n                        <Label className=\"text-xs\">Show History</Label>\n                      </div>\n                      <Switch\n                        checked={settings.showHistory}\n                        className=\"scale-75\"\n                        onCheckedChange={(value) =>\n                          updateSetting(\"showHistory\", value)\n                        }\n                      />\n                    </div>\n                  </DropdownMenuGroup>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </div>\n\n            <div className=\"ml-auto flex items-center gap-0.5 sm:gap-1\">\n              <Button\n                aria-label=\"Send message\"\n                className=\"rounded-md\"\n                disabled={!prompt.trim()}\n                size=\"icon-sm\"\n                type=\"submit\"\n                variant=\"default\"\n              >\n                <IconArrowUp size={16} />\n              </Button>\n            </div>\n          </div>\n\n          <div\n            className={cn(\n              \"pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-[inherit] border border-border border-dashed bg-muted text-foreground text-sm transition-opacity duration-200\",\n              isDragOver ? \"opacity-100\" : \"opacity-0\"\n            )}\n          >\n            <span className=\"flex w-full items-center justify-center gap-1 font-medium\">\n              <IconCirclePlus className=\"min-w-4\" size={16} />\n              Drop files here to add as attachments\n            </span>\n          </div>\n        </form>\n      </div>\n\n      <div className=\"mx-auto flex min-h-0 max-w-250 shrink-0 flex-wrap items-center justify-center gap-3\">\n        {ACTIONS.map((action) => (\n          <Button\n            className=\"gap-2 rounded-full\"\n            key={action.id}\n            size=\"sm\"\n            variant=\"outline\"\n          >\n            <action.icon size={16} />\n            {action.label}\n          </Button>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/ai-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/ai-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.2.0):\n- Replaced the Next.js image component with a standard image element for local attachment previews.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-04/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-04/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-04/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-04/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/switch.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-04/shared/components/ui/switch.tsx",
          "content": "'use client';\n\nimport { Switch as SwitchPrimitive } from '@base-ui/react/switch';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Switch({\n  className,\n  size = 'default',\n  ...props\n}: SwitchPrimitive.Root.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SwitchPrimitive.Root\n      className={cn(\n        'peer group/switch after:-inset-x-3 after:-inset-y-2 relative inline-flex shrink-0 items-center rounded-full border border-transparent outline-none transition-all after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=sm]:h-[14px] data-[size=default]:w-[32px] data-[size=sm]:w-[24px] data-disabled:cursor-not-allowed data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-50 dark:data-unchecked:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-size={size}\n      data-slot=\"switch\"\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        className=\"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground\"\n        data-slot=\"switch-thumb\"\n      />\n    </SwitchPrimitive.Root>\n  );\n}\n\nexport { Switch };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-04/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "ai",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/ai-04/ai-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/ai/ai-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.2.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f",
          "modifications": [
            "Replaced the Next.js image component with a standard image element for local attachment previews."
          ]
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "ai",
          "prompt",
          "chat",
          "assistant",
          "人工智能",
          "提示词",
          "聊天",
          "助手"
        ],
        "contentGuidelines": [
          "Provide your own generation endpoint, cancellation, and error handling.",
          "Test a long prompt, empty submission, and pending generation state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/ai-04/ai-04.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/ai-04"
      }
    },
    {
      "name": "ai-05",
      "type": "registry:block",
      "title": "AI Elements Chat",
      "description": "AI Elements Chat. AI prompt inputs and assistant interfaces with model selection and media attachments.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "ai@6.0.319",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0",
        "streamdown@2.7.1",
        "use-stick-to-bottom@1.1.6"
      ],
      "files": [
        {
          "path": "content/components/ai/ai-05.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/ai-05.tsx",
          "content": "'use client';\n\n// Install AI Elements components:\n// npx ai-elements@latest add conversation message prompt-input\n\nimport {\n  IconAdjustmentsHorizontal,\n  IconBolt,\n  IconPaperclip,\n  IconPlus,\n} from '@tabler/icons-react';\nimport type { ChatStatus } from 'ai';\nimport { useState } from 'react';\nimport {\n  Conversation,\n  ConversationContent,\n  ConversationScrollButton,\n} from \"./shared/components/ai-elements/conversation\";\nimport {\n  Message,\n  MessageContent,\n  MessageResponse,\n} from \"./shared/components/ai-elements/message\";\nimport {\n  PromptInput,\n  PromptInputButton,\n  PromptInputFooter,\n  PromptInputSubmit,\n  PromptInputTextarea,\n  PromptInputTools,\n} from \"./shared/components/ai-elements/prompt-input\";\nimport { Button } from \"./shared/components/ui/button\";\nimport { cn } from \"./shared/lib/utils\";\n\ninterface DemoMessage {\n  id: string;\n  role: 'user' | 'assistant';\n  content: string;\n}\n\nconst INITIAL_MESSAGES: DemoMessage[] = [\n  {\n    id: 'intro',\n    role: 'assistant',\n    content:\n      'Hi, I am your workspace assistant. I can draft copy, summarize documents, and turn notes into tasks. What are you working on?',\n  },\n  {\n    id: 'question',\n    role: 'user',\n    content: 'Summarize the Q3 launch plan in three bullets.',\n  },\n  {\n    id: 'answer',\n    role: 'assistant',\n    content:\n      '- **Ship the new onboarding** by Sept 18 with an A/B test on pricing.\\n- **Open EU and APAC** with localized checkout.\\n- **Launch the partner API** in private beta with 12 partners.',\n  },\n];\n\nconst SUGGESTIONS = [\n  'Draft a launch announcement',\n  'List open risks',\n  'Turn this into tasks',\n];\n\nconst RESPONSES = [\n  'Here is a quick outline you can reuse:\\n\\n1. Swap the mock response with a real API call.\\n2. Stream tokens into `MessageResponse`.\\n3. Keep the layout as-is for a consistent UI.',\n  'If you want multi-model support, add a small model selector to the prompt footer and pass the selection to your backend.',\n  'You can also inject tools like file upload or voice input by adding buttons to the prompt footer.',\n];\n\nexport default function Ai05() {\n  const [messages, setMessages] = useState<DemoMessage[]>(INITIAL_MESSAGES);\n  const [inputValue, setInputValue] = useState('');\n  const [status, setStatus] = useState<ChatStatus>('ready');\n\n  const handleSend = (text: string) => {\n    const trimmed = text.trim();\n    if (!trimmed || status !== 'ready') {\n      return;\n    }\n\n    setMessages((prev) => [\n      ...prev,\n      { id: `user-${Date.now()}`, role: 'user', content: trimmed },\n    ]);\n    setInputValue('');\n    setStatus('submitted');\n\n    window.setTimeout(() => {\n      setMessages((prev) => [\n        ...prev,\n        {\n          id: `assistant-${Date.now()}`,\n          role: 'assistant',\n          content: RESPONSES[messages.length % RESPONSES.length],\n        },\n      ]);\n      setStatus('ready');\n    }, 1200);\n  };\n\n  const handleReset = () => {\n    setMessages(INITIAL_MESSAGES.slice(0, 1));\n    setInputValue('');\n    setStatus('ready');\n  };\n\n  return (\n    <div className=\"w-full px-4\">\n      <div className=\"mx-auto flex h-[560px] w-full max-w-2xl flex-col overflow-hidden rounded-3xl bg-card shadow-[0_0_0_1px_oklch(0_0_0/0.06),0_1px_2px_oklch(0_0_0/0.04),0_12px_32px_-12px_oklch(0_0_0/0.18)] dark:shadow-[0_0_0_1px_oklch(1_0_0/0.1),0_12px_32px_-12px_oklch(0_0_0/0.6)]\">\n        <header className=\"flex items-center justify-between gap-4 px-5 py-4\">\n          <div className=\"flex flex-col\">\n            <span className=\"font-semibold text-sm leading-tight\">\n              Assistant\n            </span>\n            <span className=\"inline-flex items-center gap-1.5 text-muted-foreground text-xs\">\n              <span className=\"size-1.5 rounded-full bg-emerald-500\" />\n              Online\n            </span>\n          </div>\n          <div className=\"flex items-center gap-1\">\n            <Button\n              className=\"h-8 gap-1.5 px-2.5 text-xs\"\n              onClick={handleReset}\n              size=\"sm\"\n              variant=\"ghost\"\n            >\n              <IconPlus className=\"size-4\" stroke={1.5} />\n              New chat\n            </Button>\n            <Button\n              aria-label=\"Settings\"\n              className=\"size-8\"\n              size=\"icon\"\n              title=\"Settings\"\n              variant=\"ghost\"\n            >\n              <IconAdjustmentsHorizontal className=\"size-4\" stroke={1.5} />\n            </Button>\n          </div>\n        </header>\n\n        <Conversation className=\"border-border/60 border-t\">\n          <ConversationContent className=\"gap-5 px-5 py-5\">\n            {messages.map((message) => (\n              <Message from={message.role} key={message.id}>\n                <MessageContent\n                  className={cn(\n                    'leading-relaxed',\n                    message.role === 'assistant' && 'max-w-prose',\n                    message.role === 'user' &&\n                      'group-[.is-user]:rounded-2xl group-[.is-user]:rounded-br-md group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground'\n                  )}\n                >\n                  {message.role === 'assistant' ? (\n                    <MessageResponse>{message.content}</MessageResponse>\n                  ) : (\n                    <p className=\"whitespace-pre-wrap text-pretty\">\n                      {message.content}\n                    </p>\n                  )}\n                </MessageContent>\n              </Message>\n            ))}\n            {status === 'submitted' && (\n              <Message from=\"assistant\">\n                <output\n                  aria-label=\"Assistant is typing\"\n                  className=\"flex h-7 items-center gap-1\"\n                >\n                  {[0, 1, 2].map((dot) => (\n                    <span\n                      className=\"size-1.5 animate-bounce rounded-full bg-muted-foreground/60\"\n                      key={dot}\n                      style={{ animationDelay: `${dot * 150}ms` }}\n                    />\n                  ))}\n                </output>\n              </Message>\n            )}\n          </ConversationContent>\n          <ConversationScrollButton />\n        </Conversation>\n\n        <div className=\"flex flex-col gap-3 p-3\">\n          <div className=\"flex flex-wrap gap-2 px-1\">\n            {SUGGESTIONS.map((suggestion) => (\n              <button\n                className=\"rounded-full border border-border/70 bg-background px-3 py-1.5 text-muted-foreground text-xs transition-[color,background-color,scale] hover:bg-muted hover:text-foreground active:scale-[0.96] disabled:pointer-events-none disabled:opacity-50\"\n                disabled={status !== 'ready'}\n                key={suggestion}\n                onClick={() => handleSend(suggestion)}\n                type=\"button\"\n              >\n                {suggestion}\n              </button>\n            ))}\n          </div>\n          <PromptInput\n            className=\"[&>[data-slot=input-group]]:rounded-xl [&>[data-slot=input-group]]:border-border [&>[data-slot=input-group]]:bg-muted/40 [&>[data-slot=input-group]]:shadow-none\"\n            onSubmit={(message) => handleSend(message.text)}\n          >\n            <PromptInputTextarea\n              onChange={(event) => setInputValue(event.currentTarget.value)}\n              placeholder=\"Message Assistant...\"\n              value={inputValue}\n            />\n            <PromptInputFooter>\n              <PromptInputTools>\n                <PromptInputButton aria-label=\"Attach file\">\n                  <IconPaperclip className=\"size-4\" stroke={1.5} />\n                </PromptInputButton>\n                <PromptInputButton aria-label=\"Quick prompt\">\n                  <IconBolt className=\"size-4\" stroke={1.5} />\n                </PromptInputButton>\n              </PromptInputTools>\n              <PromptInputSubmit\n                className=\"rounded-full transition-[scale,opacity] active:scale-[0.96]\"\n                disabled={!inputValue.trim() || status !== 'ready'}\n                status={status}\n              />\n            </PromptInputFooter>\n          </PromptInput>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/ai-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/ai-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ai-elements/conversation.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/shared/components/ai-elements/conversation.tsx",
          "content": "'use client';\n\nimport { ArrowDownIcon } from 'lucide-react';\nimport type { ComponentProps } from 'react';\nimport { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';\nimport { Button } from \"../ui/button\";\nimport { cn } from \"../../lib/utils\";\n\nexport type ConversationProps = ComponentProps<typeof StickToBottom>;\n\nexport const Conversation = ({ className, ...props }: ConversationProps) => (\n  <StickToBottom\n    className={cn('relative flex-1 overflow-y-hidden', className)}\n    initial=\"smooth\"\n    resize=\"smooth\"\n    role=\"log\"\n    {...props}\n  />\n);\n\nexport type ConversationContentProps = ComponentProps<\n  typeof StickToBottom.Content\n>;\n\nexport const ConversationContent = ({\n  className,\n  ...props\n}: ConversationContentProps) => (\n  <StickToBottom.Content\n    className={cn('flex flex-col gap-8 p-4', className)}\n    {...props}\n  />\n);\n\nexport type ConversationScrollButtonProps = ComponentProps<typeof Button>;\n\nexport const ConversationScrollButton = ({\n  className,\n  ...props\n}: ConversationScrollButtonProps) => {\n  const { isAtBottom, scrollToBottom } = useStickToBottomContext();\n\n  if (isAtBottom) {\n    return null;\n  }\n\n  return (\n    <Button\n      className={cn(\n        'absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full',\n        className\n      )}\n      onClick={() => scrollToBottom()}\n      size=\"icon\"\n      type=\"button\"\n      variant=\"outline\"\n      {...props}\n    >\n      <ArrowDownIcon className=\"size-4\" />\n    </Button>\n  );\n};\n"
        },
        {
          "path": "components/ai-elements/message.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/shared/components/ai-elements/message.tsx",
          "content": "'use client';\n\nimport type { UIMessage } from 'ai';\nimport type { ComponentProps, HTMLAttributes } from 'react';\nimport { memo } from 'react';\nimport { Streamdown } from 'streamdown';\nimport { cn } from \"../../lib/utils\";\n\nexport type MessageProps = HTMLAttributes<HTMLDivElement> & {\n  from: UIMessage['role'];\n};\n\nexport const Message = ({ className, from, ...props }: MessageProps) => (\n  <div\n    className={cn(\n      'group flex w-full max-w-[95%] flex-col gap-2',\n      from === 'user' ? 'is-user ml-auto justify-end' : 'is-assistant',\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type MessageContentProps = HTMLAttributes<HTMLDivElement>;\n\nexport const MessageContent = ({\n  children,\n  className,\n  ...props\n}: MessageContentProps) => (\n  <div\n    className={cn(\n      'flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm',\n      'group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground',\n      'group-[.is-assistant]:text-foreground',\n      className\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\nexport type MessageResponseProps = ComponentProps<typeof Streamdown>;\n\nexport const MessageResponse = memo(\n  ({ className, ...props }: MessageResponseProps) => (\n    <Streamdown\n      className={cn(\n        'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',\n        className\n      )}\n      {...props}\n    />\n  ),\n  (prev, next) => prev.children === next.children\n);\n\nMessageResponse.displayName = 'MessageResponse';\n"
        },
        {
          "path": "components/ai-elements/prompt-input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/shared/components/ai-elements/prompt-input.tsx",
          "content": "'use client';\n\nimport type { ChatStatus, FileUIPart } from 'ai';\nimport { CornerDownLeftIcon, Loader2Icon, SquareIcon, XIcon } from 'lucide-react';\nimport {\n  Children,\n  type ComponentProps,\n  type FormEvent,\n  type HTMLAttributes,\n  type KeyboardEventHandler,\n  useState,\n} from 'react';\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupTextarea,\n} from \"../ui/input-group\";\nimport { cn } from \"../../lib/utils\";\n\nexport type PromptInputMessage = {\n  text: string;\n  files: FileUIPart[];\n};\n\nexport type PromptInputProps = Omit<\n  HTMLAttributes<HTMLFormElement>,\n  'onSubmit'\n> & {\n  onSubmit: (\n    message: PromptInputMessage,\n    event: FormEvent<HTMLFormElement>\n  ) => void | Promise<void>;\n};\n\nexport const PromptInput = ({\n  className,\n  onSubmit,\n  children,\n  ...props\n}: PromptInputProps) => (\n  <form\n    className={cn('w-full', className)}\n    onSubmit={(event) => {\n      event.preventDefault();\n      const data = new FormData(event.currentTarget);\n      void onSubmit(\n        { text: String(data.get('message') ?? ''), files: [] },\n        event\n      );\n    }}\n    {...props}\n  >\n    <InputGroup className=\"overflow-hidden\">{children}</InputGroup>\n  </form>\n);\n\nexport type PromptInputTextareaProps = ComponentProps<\n  typeof InputGroupTextarea\n>;\n\nexport const PromptInputTextarea = ({\n  className,\n  placeholder = 'What would you like to know?',\n  ...props\n}: PromptInputTextareaProps) => {\n  const [isComposing, setIsComposing] = useState(false);\n\n  const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = (event) => {\n    props.onKeyDown?.(event);\n    if (\n      event.defaultPrevented ||\n      event.key !== 'Enter' ||\n      event.shiftKey ||\n      isComposing ||\n      event.nativeEvent.isComposing\n    ) {\n      return;\n    }\n\n    event.preventDefault();\n    const button = event.currentTarget.form?.querySelector(\n      'button[type=\"submit\"]'\n    );\n    if (!(button instanceof HTMLButtonElement) || !button.disabled) {\n      event.currentTarget.form?.requestSubmit();\n    }\n  };\n\n  return (\n    <InputGroupTextarea\n      className={cn('field-sizing-content max-h-48 min-h-16', className)}\n      name=\"message\"\n      onCompositionEnd={() => setIsComposing(false)}\n      onCompositionStart={() => setIsComposing(true)}\n      {...props}\n      onKeyDown={handleKeyDown}\n    />\n  );\n};\n\nexport type PromptInputFooterProps = Omit<\n  ComponentProps<typeof InputGroupAddon>,\n  'align'\n>;\n\nexport const PromptInputFooter = ({\n  className,\n  ...props\n}: PromptInputFooterProps) => (\n  <InputGroupAddon\n    align=\"block-end\"\n    className={cn('justify-between gap-1', className)}\n    {...props}\n  />\n);\n\nexport type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputTools = ({\n  className,\n  ...props\n}: PromptInputToolsProps) => (\n  <div className={cn('flex items-center gap-1', className)} {...props} />\n);\n\nexport type PromptInputButtonProps = ComponentProps<typeof InputGroupButton>;\n\nexport const PromptInputButton = ({\n  variant = 'ghost',\n  className,\n  size,\n  ...props\n}: PromptInputButtonProps) => (\n  <InputGroupButton\n    className={cn(className)}\n    size={size ?? (Children.count(props.children) > 1 ? 'sm' : 'icon-sm')}\n    type=\"button\"\n    variant={variant}\n    {...props}\n  />\n);\n\nexport type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {\n  status?: ChatStatus;\n};\n\nexport const PromptInputSubmit = ({\n  className,\n  variant = 'default',\n  size = 'icon-sm',\n  status,\n  children,\n  ...props\n}: PromptInputSubmitProps) => {\n  let icon = <CornerDownLeftIcon className=\"size-4\" />;\n\n  if (status === 'submitted') {\n    icon = <Loader2Icon className=\"size-4 animate-spin\" />;\n  } else if (status === 'streaming') {\n    icon = <SquareIcon className=\"size-4\" />;\n  } else if (status === 'error') {\n    icon = <XIcon className=\"size-4\" />;\n  }\n\n  return (\n    <InputGroupButton\n      aria-label=\"Submit\"\n      className={cn(className)}\n      size={size}\n      type=\"submit\"\n      variant={variant}\n      {...props}\n    >\n      {children ?? icon}\n    </InputGroupButton>\n  );\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/ai-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "ai",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/ai-05/ai-05.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/ai/ai-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "ai",
          "prompt",
          "chat",
          "assistant",
          "人工智能",
          "提示词",
          "聊天",
          "助手"
        ],
        "contentGuidelines": [
          "Provide your own generation endpoint, cancellation, and error handling.",
          "Test a long prompt, empty submission, and pending generation state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/ai-05/ai-05.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/ai-05"
      }
    },
    {
      "name": "chat-01",
      "type": "registry:block",
      "title": "Chat Basic",
      "description": "Chat Basic. AI chat interfaces with message history, prompt controls, and conversation navigation.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@shadcn/react@0.3.1",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/chat/chat-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/chat-01.tsx",
          "content": "'use client';\n\nimport {\n  IconArrowUp,\n  IconCopy,\n  IconMicrophone,\n  IconPaperclip,\n  IconPhoto,\n  IconPlayerStopFilled,\n  IconPlus,\n  IconRefresh,\n  IconThumbDown,\n  IconThumbUp,\n  IconWorldSearch,\n} from '@tabler/icons-react';\nimport type React from 'react';\nimport { useState } from 'react';\nimport { Bubble, BubbleContent } from \"./shared/components/ui/bubble\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput,\n} from \"./shared/components/ui/input-group\";\nimport {\n  Message,\n  MessageContent,\n  MessageFooter,\n} from \"./shared/components/ui/message\";\nimport {\n  MessageScroller,\n  MessageScrollerButton,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerProvider,\n  MessageScrollerViewport,\n} from \"./shared/components/ui/message-scroller\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\nimport { cn } from \"./shared/lib/utils\";\n\ntype Turn = {\n  id: string;\n  role: 'user' | 'assistant';\n  text: string;\n  streaming?: boolean;\n};\n\nconst thread: Turn[] = [\n  {\n    id: '1',\n    role: 'user',\n    text: 'Is it true that a day on Venus is longer than its year?',\n  },\n  {\n    id: '2',\n    role: 'assistant',\n    text: 'Yes. Venus takes 243 Earth days to rotate once, but only 225 days to orbit the Sun, so its day is longer than its year.\\n\\nIt also spins in the opposite direction to most planets, which means the Sun rises in the west there.',\n  },\n  { id: '3', role: 'user', text: 'How hot does it get on the surface?' },\n  {\n    id: '4',\n    role: 'assistant',\n    text: 'The surface sits around 465°C, hot enough to melt lead. The atmosphere traps so much heat that Venus stays hotter than Mercury, even though Mercury is much closer to the Sun.',\n  },\n];\n\nconst replies = [\n  'Good question. Venus has no moons and no rings, which makes it unusual among the inner planets. Most explanations point to its slow, retrograde spin: any moon it once had would have drifted inward and been pulled apart by tidal forces.\\n\\nMercury is the only other moonless planet in the solar system.',\n  'The clouds are mostly sulfuric acid droplets, sitting about 50 to 70 km above the surface. They reflect roughly 75% of incoming sunlight, which is why Venus is the brightest object in our sky after the Moon.\\n\\nBelow the clouds the air is almost entirely carbon dioxide, with pressure about 92 times that of Earth at sea level.',\n  'Several landers have made it down. The Soviet Venera 7 was the first to transmit from the surface in 1970, and Venera 13 survived for 127 minutes in 1982, long enough to send back the first color photos.\\n\\nNothing has lasted more than a couple of hours; the heat and pressure destroy electronics quickly.',\n];\n\nconst thinkDelay = 600;\nconst wordDelay = 35;\n\nconst easeOut = 'ease-[cubic-bezier(0.23,1,0.32,1)]';\nconst press = `${easeOut} transition-[scale,background-color] duration-150 active:scale-[0.96]`;\nconst enter = `${easeOut} motion-reduce:slide-in-from-bottom-0 fade-in slide-in-from-bottom-1 animate-in duration-200`;\nconst swap = `${easeOut} fade-in zoom-in-95 animate-in duration-150`;\n\nexport default function Chat01() {\n  const [turns, setTurns] = useState(thread);\n  const [draft, setDraft] = useState('');\n  const [listening, setListening] = useState(false);\n  const [replyIndex, setReplyIndex] = useState(0);\n  const [edit, setEdit] = useState<{ id: string; text: string } | null>(null);\n  const streaming = turns.some((t) => t.streaming);\n\n  const stream = (replyId: string) => {\n    const words = replies[replyIndex % replies.length].split(' ');\n    setReplyIndex(replyIndex + 1);\n    setTurns((prev) =>\n      prev.map((t) =>\n        t.id === replyId ? { ...t, text: '', streaming: true } : t\n      )\n    );\n    let shown = 0;\n    setTimeout(() => {\n      const timer = setInterval(() => {\n        shown += 1;\n        const done = shown >= words.length;\n        setTurns((prev) =>\n          prev.map((t) =>\n            t.id === replyId\n              ? {\n                  ...t,\n                  text: words.slice(0, shown).join(' '),\n                  streaming: !done,\n                }\n              : t\n          )\n        );\n        if (done) {\n          clearInterval(timer);\n        }\n      }, wordDelay);\n    }, thinkDelay);\n  };\n\n  const send = (e: React.FormEvent) => {\n    e.preventDefault();\n    const text = draft.trim();\n    if (!text || streaming) {\n      return;\n    }\n    const replyId = crypto.randomUUID();\n    setTurns([\n      ...turns,\n      { id: crypto.randomUUID(), role: 'user', text },\n      { id: replyId, role: 'assistant', text: '' },\n    ]);\n    setDraft('');\n    stream(replyId);\n  };\n\n  const resend = () => {\n    const text = edit?.text.trim();\n    if (!(edit && text) || streaming) {\n      return;\n    }\n    const at = turns.findIndex((t) => t.id === edit.id);\n    const replyId = crypto.randomUUID();\n    setTurns([\n      ...turns.slice(0, at),\n      { id: edit.id, role: 'user', text },\n      { id: replyId, role: 'assistant', text: '' },\n    ]);\n    setEdit(null);\n    stream(replyId);\n  };\n\n  const editLast = (e: React.KeyboardEvent) => {\n    const last = turns.findLast((t) => t.role === 'user');\n    if (e.key === 'ArrowUp' && !draft && last) {\n      e.preventDefault();\n      setEdit({ id: last.id, text: last.text });\n    }\n  };\n\n  const body = (turn: Turn) => {\n    const mine = turn.role === 'user';\n    if (edit?.id === turn.id) {\n      return (\n        <>\n          <Textarea\n            aria-label=\"Edit message\"\n            autoFocus\n            className={cn(\n              enter,\n              'min-h-0 w-md max-w-full resize-none rounded-[22px] border-0 bg-muted px-4 py-3 font-medium text-[15px]/6 focus-visible:ring-0 md:text-[15px]/6'\n            )}\n            onChange={(e) => setEdit({ ...edit, text: e.target.value })}\n            onKeyDown={(e) => {\n              if (e.key === 'Escape') {\n                setEdit(null);\n              }\n              if (e.key === 'Enter' && !e.shiftKey) {\n                e.preventDefault();\n                resend();\n              }\n            }}\n            value={edit.text}\n          />\n          <div className={cn(enter, 'flex items-center gap-1.5 self-end')}>\n            <Button\n              aria-label=\"Copy\"\n              className={cn(press, 'rounded-full')}\n              onClick={() => navigator.clipboard.writeText(turn.text)}\n              size=\"icon-sm\"\n              variant=\"ghost\"\n            >\n              <IconCopy stroke={1.6} />\n            </Button>\n            <Button\n              className={cn(press, 'rounded-full')}\n              onClick={() => setEdit(null)}\n              size=\"sm\"\n              variant=\"secondary\"\n            >\n              Cancel\n            </Button>\n            <Button\n              className={cn(press, 'rounded-full')}\n              disabled={!edit.text.trim() || streaming}\n              onClick={resend}\n              size=\"sm\"\n            >\n              Send\n            </Button>\n          </div>\n        </>\n      );\n    }\n    if (turn.streaming && !turn.text) {\n      return (\n        <span className=\"shimmer text-[15px]/6 text-muted-foreground\">\n          Thinking…\n        </span>\n      );\n    }\n    return turn.text.split('\\n\\n').map((paragraph) => (\n      <Bubble\n        align={mine ? 'end' : 'start'}\n        className={\n          mine\n            ? '-mx-2.5 -my-1 box-content max-w-md cursor-pointer rounded-xl px-2.5 py-1 text-right font-medium transition-colors duration-150 ease-out hover:bg-muted'\n            : undefined\n        }\n        key={paragraph}\n        onDoubleClick={\n          mine ? () => setEdit({ id: turn.id, text: turn.text }) : undefined\n        }\n        title={mine ? 'Double-click to edit' : undefined}\n        variant=\"ghost\"\n      >\n        <BubbleContent className=\"text-[15px]/6\">{paragraph}</BubbleContent>\n      </Bubble>\n    ));\n  };\n\n  return (\n    <div className=\"flex h-dvh w-full flex-col bg-background\">\n      <MessageScrollerProvider autoScroll>\n        <MessageScroller>\n          <MessageScrollerViewport>\n            <MessageScrollerContent className=\"mx-auto w-full max-w-2xl gap-3.5 px-1.5 pt-12 pb-4\">\n              {turns.map((turn) => {\n                const mine = turn.role === 'user';\n                return (\n                  <MessageScrollerItem\n                    className={cn(\n                      'px-2.5',\n                      mine && 'pt-5.5 pb-1 first:pt-1',\n                      !thread.some((t) => t.id === turn.id) && enter\n                    )}\n                    key={turn.id}\n                    messageId={turn.id}\n                  >\n                    <Message align={mine ? 'end' : 'start'}>\n                      <MessageContent className=\"gap-2\">\n                        {body(turn)}\n                        {!(mine || turn.streaming) && (\n                          <MessageFooter className={cn(enter, 'gap-0.5')}>\n                            <Button\n                              aria-label=\"Copy\"\n                              className={press}\n                              onClick={() =>\n                                navigator.clipboard.writeText(turn.text)\n                              }\n                              size=\"icon-sm\"\n                              variant=\"ghost\"\n                            >\n                              <IconCopy stroke={1.6} />\n                            </Button>\n                            <Button\n                              aria-label=\"Good response\"\n                              className={press}\n                              size=\"icon-sm\"\n                              variant=\"ghost\"\n                            >\n                              <IconThumbUp stroke={1.6} />\n                            </Button>\n                            <Button\n                              aria-label=\"Bad response\"\n                              className={press}\n                              size=\"icon-sm\"\n                              variant=\"ghost\"\n                            >\n                              <IconThumbDown stroke={1.6} />\n                            </Button>\n                            <Button\n                              aria-label=\"Regenerate\"\n                              className={press}\n                              onClick={() => stream(turn.id)}\n                              size=\"icon-sm\"\n                              variant=\"ghost\"\n                            >\n                              <IconRefresh stroke={1.6} />\n                            </Button>\n                          </MessageFooter>\n                        )}\n                      </MessageContent>\n                    </Message>\n                  </MessageScrollerItem>\n                );\n              })}\n            </MessageScrollerContent>\n          </MessageScrollerViewport>\n          <MessageScrollerButton className={easeOut} />\n        </MessageScroller>\n      </MessageScrollerProvider>\n\n      <form\n        className=\"mx-auto flex w-full max-w-2xl flex-col items-center gap-2.5 px-4 pt-3 pb-4\"\n        onSubmit={send}\n      >\n        <InputGroup className=\"h-13 rounded-full border-0 bg-muted px-2.5 has-[[data-slot=input-group-control]:focus-visible]:ring-0\">\n          <InputGroupAddon>\n            <DropdownMenu>\n              <DropdownMenuTrigger\n                render={\n                  <InputGroupButton\n                    aria-label=\"Add attachment\"\n                    className={cn(press, 'rounded-full hover:bg-foreground/10')}\n                    size=\"icon-sm\"\n                  />\n                }\n              >\n                <IconPlus stroke={1.8} />\n              </DropdownMenuTrigger>\n              <DropdownMenuContent\n                align=\"start\"\n                className=\"w-60 rounded-[20px] p-2 shadow-[0_0_0_1px_oklch(0_0_0/0.06),0_8px_24px_-6px_oklch(0_0_0/0.14)]\"\n                sideOffset={10}\n              >\n                <DropdownMenuGroup className=\"flex flex-col gap-0.5\">\n                  <DropdownMenuItem className=\"gap-3 rounded-xl px-3 py-2.5 text-[15px] [&>svg]:text-muted-foreground\">\n                    <IconPaperclip className=\"size-4.5\" stroke={1.6} />\n                    Add files\n                  </DropdownMenuItem>\n                  <DropdownMenuItem className=\"gap-3 rounded-xl px-3 py-2.5 text-[15px] [&>svg]:text-muted-foreground\">\n                    <IconPhoto className=\"size-4.5\" stroke={1.6} />\n                    Add photos\n                  </DropdownMenuItem>\n                  <DropdownMenuItem className=\"gap-3 rounded-xl px-3 py-2.5 text-[15px] [&>svg]:text-muted-foreground\">\n                    <IconWorldSearch className=\"size-4.5\" stroke={1.6} />\n                    Search the web\n                  </DropdownMenuItem>\n                </DropdownMenuGroup>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </InputGroupAddon>\n          <InputGroupInput\n            className=\"text-[15px] md:text-[15px]\"\n            onChange={(e) => setDraft(e.target.value)}\n            onKeyDown={editLast}\n            placeholder={listening ? 'Listening…' : 'Ask anything'}\n            value={draft}\n          />\n          <InputGroupAddon align=\"inline-end\">\n            {draft.trim() ? (\n              <InputGroupButton\n                aria-label=\"Send\"\n                className={cn(swap, press, 'rounded-full')}\n                disabled={streaming}\n                size=\"icon-sm\"\n                type=\"submit\"\n                variant=\"default\"\n              >\n                <IconArrowUp stroke={2} />\n              </InputGroupButton>\n            ) : (\n              <InputGroupButton\n                aria-label={listening ? 'Stop listening' : 'Voice input'}\n                aria-pressed={listening}\n                className={cn(\n                  swap,\n                  press,\n                  'rounded-full',\n                  listening\n                    ? 'bg-destructive/15 text-destructive hover:bg-destructive/25'\n                    : 'hover:bg-foreground/10'\n                )}\n                onClick={() => setListening(!listening)}\n                size=\"icon-sm\"\n              >\n                {listening ? (\n                  <IconPlayerStopFilled className=\"animate-pulse motion-reduce:animate-none\" />\n                ) : (\n                  <IconMicrophone stroke={1.8} />\n                )}\n              </InputGroupButton>\n            )}\n          </InputGroupAddon>\n        </InputGroup>\n        <p className=\"text-muted-foreground text-xs\">\n          AI can make mistakes. Check important info.\n        </p>\n      </form>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/chat-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/chat-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/bubble.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/components/ui/bubble.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction BubbleGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex min-w-0 flex-col gap-2', className)}\n      data-slot=\"bubble-group\"\n      {...props}\n    />\n  );\n}\n\nconst bubbleVariants = cva(\n  'group/bubble relative flex w-fit min-w-0 max-w-[80%] flex-col gap-1 data-[variant=ghost]:max-w-full data-[align=end]:self-end group-data-[align=end]/message:self-end',\n  {\n    variants: {\n      variant: {\n        default:\n          '*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80',\n        secondary:\n          '*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]',\n        muted:\n          '*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]',\n        tinted:\n          '*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]',\n        outline:\n          '*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30',\n        ghost:\n          'border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50',\n        destructive:\n          '*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Bubble({\n  variant = 'default',\n  align = 'start',\n  className,\n  ...props\n}: React.ComponentProps<'div'> &\n  VariantProps<typeof bubbleVariants> & {\n    align?: 'start' | 'end';\n  }) {\n  return (\n    <div\n      className={cn(bubbleVariants({ variant }), className)}\n      data-align={align}\n      data-slot=\"bubble\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction BubbleContent({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'wrap-break-word w-fit min-w-0 max-w-full overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed group-data-[align=end]/bubble:self-end [button,a]:outline-none [button,a]:transition-colors [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50 [button]:text-left',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'bubble-content',\n    },\n  });\n}\n\nconst bubbleReactionsVariants = cva(\n  'absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0',\n  {\n    variants: {\n      side: {\n        top: '-translate-y-3/4 top-0',\n        bottom: 'bottom-0 translate-y-3/4',\n      },\n      align: {\n        start: 'left-3',\n        end: 'right-3',\n      },\n    },\n    defaultVariants: {\n      side: 'bottom',\n      align: 'end',\n    },\n  }\n);\n\nfunction BubbleReactions({\n  side = 'bottom',\n  align = 'end',\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  align?: 'start' | 'end';\n  side?: 'top' | 'bottom';\n}) {\n  return (\n    <div\n      className={cn(bubbleReactionsVariants({ side, align }), className)}\n      data-align={align}\n      data-side={side}\n      data-slot=\"bubble-reactions\"\n      {...props}\n    />\n  );\n}\n\nexport { BubbleGroup, Bubble, BubbleContent, BubbleReactions };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/message-scroller.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/components/ui/message-scroller.tsx",
          "content": "'use client';\n\nimport {\n  MessageScroller as MessageScrollerPrimitive,\n  useMessageScroller,\n  useMessageScrollerScrollable,\n  useMessageScrollerVisibility,\n} from '@shadcn/react/message-scroller';\nimport { ArrowDownIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction MessageScrollerProvider(\n  props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>\n) {\n  return <MessageScrollerPrimitive.Provider {...props} />;\n}\n\nfunction MessageScroller({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) {\n  return (\n    <MessageScrollerPrimitive.Root\n      className={cn(\n        'group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden',\n        className\n      )}\n      data-slot=\"message-scroller\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerViewport({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) {\n  return (\n    <MessageScrollerPrimitive.Viewport\n      className={cn(\n        'scroll-fade-b scrollbar-thin scrollbar-gutter-stable data-autoscrolling:scrollbar-thumb-transparent data-autoscrolling:scrollbar-track-transparent size-full min-h-0 min-w-0 overflow-y-auto overscroll-contain contain-content data-pending-scroll:invisible',\n        className\n      )}\n      data-slot=\"message-scroller-viewport\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) {\n  return (\n    <MessageScrollerPrimitive.Content\n      className={cn('flex h-max min-h-full flex-col gap-6', className)}\n      data-slot=\"message-scroller-content\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerItem({\n  className,\n  scrollAnchor = false,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) {\n  return (\n    <MessageScrollerPrimitive.Item\n      className={cn(\n        'min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]',\n        className\n      )}\n      data-slot=\"message-scroller-item\"\n      scrollAnchor={scrollAnchor}\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerButton({\n  direction = 'end',\n  className,\n  children,\n  render,\n  variant = 'secondary',\n  size = 'icon-sm',\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Button> &\n  Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {\n  return (\n    <MessageScrollerPrimitive.Button\n      className={cn(\n        '-translate-x-1/2 data-[direction=start]:data-[active=false]:-translate-y-full absolute inset-s-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[direction=end]:data-[active=false]:translate-y-full data-[active=false]:pointer-events-none data-[direction=start]:top-4 data-[direction=end]:bottom-4 data-[active=true]:translate-y-0 data-[active=false]:scale-95 data-[active=true]:scale-100 data-[active=false]:opacity-0 data-[active=true]:opacity-100 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180',\n        className\n      )}\n      data-direction={direction}\n      data-size={size}\n      data-slot=\"message-scroller-button\"\n      data-variant={variant}\n      direction={direction}\n      render={render ?? <Button size={size} variant={variant} />}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <ArrowDownIcon />\n          <span className=\"sr-only\">\n            {direction === 'end' ? 'Scroll to end' : 'Scroll to start'}\n          </span>\n        </>\n      )}\n    </MessageScrollerPrimitive.Button>\n  );\n}\n\nexport {\n  MessageScrollerProvider,\n  MessageScroller,\n  MessageScrollerViewport,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerButton,\n  useMessageScroller,\n  useMessageScrollerScrollable,\n  useMessageScrollerVisibility,\n};\n"
        },
        {
          "path": "components/ui/message.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/components/ui/message.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction MessageGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex min-w-0 flex-col gap-2', className)}\n      data-slot=\"message-group\"\n      {...props}\n    />\n  );\n}\n\nfunction Message({\n  className,\n  align = 'start',\n  ...props\n}: React.ComponentProps<'div'> & { align?: 'start' | 'end' }) {\n  return (\n    <div\n      className={cn(\n        'group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse',\n        className\n      )}\n      data-align={align}\n      data-slot=\"message\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageAvatar({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group-has-data-[slot=message-footer]/message:-translate-y-8 flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted',\n        className\n      )}\n      data-slot=\"message-avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'wrap-break-word flex w-full min-w-0 flex-col gap-2.5 group-data-[align=end]/message:*:data-slot:self-end',\n        className\n      )}\n      data-slot=\"message-content\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex min-w-0 max-w-full items-center px-3 font-medium text-muted-foreground text-xs group-has-data-[variant=ghost]/message:px-0',\n        className\n      )}\n      data-slot=\"message-header\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex min-w-0 max-w-full items-center px-3 font-medium text-muted-foreground text-xs group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end',\n        className\n      )}\n      data-slot=\"message-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  MessageGroup,\n  Message,\n  MessageAvatar,\n  MessageContent,\n  MessageFooter,\n  MessageHeader,\n};\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "chat",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/chat-01/chat-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/chat/chat-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "chat",
          "ai",
          "conversation",
          "messages",
          "聊天",
          "对话",
          "消息",
          "助手"
        ],
        "contentGuidelines": [
          "Connect message streaming and persistence to your application.",
          "Test empty history, long responses, and failed requests."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/chat-01/chat-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/chat-01"
      }
    },
    {
      "name": "chat-02",
      "type": "registry:block",
      "title": "Chat with Model Menu",
      "description": "Chat with Model Menu. AI chat interfaces with message history, prompt controls, and conversation navigation.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@shadcn/react@0.3.1",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/chat/chat-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/chat-02.tsx",
          "content": "'use client';\n\nimport {\n  IconArrowUp,\n  IconChevronDown,\n  IconCopy,\n  IconDots,\n  IconMicrophone,\n  IconPaperclip,\n  IconPencil,\n  IconPhoto,\n  IconPlayerStopFilled,\n  IconPlus,\n  IconRefresh,\n  IconThumbDown,\n  IconThumbUp,\n  IconVolume,\n  IconWorldSearch,\n} from '@tabler/icons-react';\nimport type React from 'react';\nimport { useState } from 'react';\nimport { Bubble, BubbleContent } from \"./shared/components/ui/bubble\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupTextarea,\n} from \"./shared/components/ui/input-group\";\nimport {\n  Message,\n  MessageContent,\n  MessageFooter,\n} from \"./shared/components/ui/message\";\nimport {\n  MessageScroller,\n  MessageScrollerButton,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerProvider,\n  MessageScrollerViewport,\n} from \"./shared/components/ui/message-scroller\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\nimport { cn } from \"./shared/lib/utils\";\n\ntype Turn = {\n  id: string;\n  role: 'user' | 'assistant';\n  text: string;\n  streaming?: boolean;\n};\n\nconst thread: Turn[] = [\n  {\n    id: '1',\n    role: 'user',\n    text: 'Is it true that a day on Venus is longer than its year?',\n  },\n  {\n    id: '2',\n    role: 'assistant',\n    text: 'Yes. Venus takes 243 Earth days to rotate once, but only 225 days to orbit the Sun, so its day is longer than its year.\\n\\nIt also spins in the opposite direction to most planets, which means the Sun rises in the west there.',\n  },\n  { id: '3', role: 'user', text: 'How hot does it get on the surface?' },\n  {\n    id: '4',\n    role: 'assistant',\n    text: 'The surface sits around 465°C, hot enough to melt lead. The atmosphere traps so much heat that Venus stays hotter than Mercury, even though Mercury is much closer to the Sun.',\n  },\n  {\n    id: '5',\n    role: 'user',\n    text: 'How long would a person survive there without a suit?',\n  },\n  {\n    id: '6',\n    role: 'assistant',\n    text: \"A few seconds at most. The pressure down there is 92 times Earth's, similar to being 900 meters underwater, and the heat alone would be fatal almost immediately.\\n\\nFor reference, the Soviet Venera landers were built for those conditions and still failed within about two hours.\",\n  },\n];\n\nconst replies = [\n  'Good question. Venus has no moons and no rings, which makes it unusual among the inner planets. Most explanations point to its slow, retrograde spin: any moon it once had would have drifted inward and been pulled apart by tidal forces.\\n\\nMercury is the only other moonless planet in the solar system.',\n  'The clouds are mostly sulfuric acid droplets, sitting about 50 to 70 km above the surface. They reflect roughly 75% of incoming sunlight, which is why Venus is the brightest object in our sky after the Moon.\\n\\nBelow the clouds the air is almost entirely carbon dioxide, with pressure about 92 times that of Earth at sea level.',\n  'Several landers have made it down. The Soviet Venera 7 was the first to transmit from the surface in 1970, and Venera 13 survived for 127 minutes in 1982, long enough to send back the first color photos.\\n\\nNothing has lasted more than a couple of hours; the heat and pressure destroy electronics quickly.',\n];\n\nconst levels = [\n  { name: 'Instant', hint: '5.5' },\n  { name: 'Medium' },\n  { name: 'High' },\n  { name: 'Extra High' },\n  { name: 'Pro' },\n];\n\nconst models = [\n  'GPT-6',\n  'GPT-5.6 Sol',\n  'GPT-6 Astra',\n  'GPT-5.6 Terra',\n  'GPT-5.6 Luna',\n];\n\nconst thinkDelay = 600;\nconst wordDelay = 35;\n\nconst easeOut = 'ease-[cubic-bezier(0.23,1,0.32,1)]';\nconst press = `${easeOut} transition-[scale,background-color] duration-150 active:scale-[0.96] motion-reduce:active:scale-100`;\nconst enter = `${easeOut} motion-reduce:slide-in-from-bottom-0 fade-in slide-in-from-bottom-1 animate-in duration-200`;\nconst swap = `${easeOut} fade-in zoom-in-95 motion-reduce:zoom-in-100 animate-in duration-150`;\nconst item = 'rounded-lg px-2.5 py-1.75 text-[13px]';\n\nexport default function Chat02() {\n  const [turns, setTurns] = useState(thread);\n  const [draft, setDraft] = useState('');\n  const [tall, setTall] = useState(false);\n  const [listening, setListening] = useState(false);\n  const [level, setLevel] = useState('Extra High');\n  const [model, setModel] = useState('GPT-5.6 Sol');\n  const [replyIndex, setReplyIndex] = useState(0);\n  const [edit, setEdit] = useState<{ id: string; text: string } | null>(null);\n  const streaming = turns.some((t) => t.streaming);\n\n  const stream = (replyId: string) => {\n    const words = replies[replyIndex % replies.length].split(' ');\n    setReplyIndex(replyIndex + 1);\n    setTurns((prev) =>\n      prev.map((t) =>\n        t.id === replyId ? { ...t, text: '', streaming: true } : t\n      )\n    );\n    let shown = 0;\n    setTimeout(() => {\n      const timer = setInterval(() => {\n        shown += 1;\n        const done = shown >= words.length;\n        setTurns((prev) =>\n          prev.map((t) =>\n            t.id === replyId\n              ? {\n                  ...t,\n                  text: words.slice(0, shown).join(' '),\n                  streaming: !done,\n                }\n              : t\n          )\n        );\n        if (done) {\n          clearInterval(timer);\n        }\n      }, wordDelay);\n    }, thinkDelay);\n  };\n\n  const send = (e: React.FormEvent) => {\n    e.preventDefault();\n    const text = draft.trim();\n    if (!text || streaming) {\n      return;\n    }\n    const replyId = crypto.randomUUID();\n    setTurns([\n      ...turns,\n      { id: crypto.randomUUID(), role: 'user', text },\n      { id: replyId, role: 'assistant', text: '' },\n    ]);\n    setDraft('');\n    setTall(false);\n    stream(replyId);\n  };\n\n  const resend = () => {\n    const text = edit?.text.trim();\n    if (!(edit && text) || streaming) {\n      return;\n    }\n    const at = turns.findIndex((t) => t.id === edit.id);\n    const reply = turns[at + 1];\n    setTurns(turns.map((t) => (t.id === edit.id ? { ...t, text } : t)));\n    setEdit(null);\n    if (reply?.role === 'assistant') {\n      stream(reply.id);\n    }\n  };\n\n  const body = (turn: Turn, align: 'start' | 'end') => {\n    if (edit?.id === turn.id) {\n      return (\n        <>\n          <Textarea\n            aria-label=\"Edit message\"\n            autoFocus\n            className={cn(\n              enter,\n              'min-h-0 w-md max-w-full resize-none rounded-[18px] border-0 bg-muted px-3.5 py-2.5 text-[15px]/6 focus-visible:ring-0 md:text-[15px]/6'\n            )}\n            onChange={(e) => setEdit({ ...edit, text: e.target.value })}\n            onKeyDown={(e) => {\n              if (e.key === 'Escape') {\n                setEdit(null);\n              }\n              if (e.key === 'Enter' && !e.shiftKey) {\n                e.preventDefault();\n                resend();\n              }\n            }}\n            value={edit.text}\n          />\n          <div className={cn(enter, 'flex items-center gap-1.5 self-end')}>\n            <Button\n              className={cn(press, 'rounded-full')}\n              onClick={() => setEdit(null)}\n              size=\"sm\"\n              variant=\"secondary\"\n            >\n              Cancel\n            </Button>\n            <Button\n              className={cn(press, 'rounded-full')}\n              disabled={!edit.text.trim() || streaming}\n              onClick={resend}\n              size=\"sm\"\n            >\n              Send\n            </Button>\n          </div>\n        </>\n      );\n    }\n    if (turn.streaming && !turn.text) {\n      return (\n        <span className=\"shimmer text-[15px]/6 text-muted-foreground\">\n          Thinking…\n        </span>\n      );\n    }\n    return turn.text.split('\\n\\n').map((paragraph) => (\n      <Bubble\n        align={align}\n        className=\"max-w-md data-[variant=ghost]:max-w-full\"\n        key={paragraph}\n        variant={turn.role === 'user' ? 'muted' : 'ghost'}\n      >\n        <BubbleContent className=\"rounded-[18px] px-3.5 py-1.75 text-[15px]/5.5 group-data-[variant=ghost]/bubble:text-[15px]/6\">\n          {paragraph}\n        </BubbleContent>\n      </Bubble>\n    ));\n  };\n\n  const action = (\n    label: string,\n    icon: React.ReactNode,\n    onClick?: () => void\n  ) => (\n    <Button\n      aria-label={label}\n      className={cn(press, 'rounded-lg')}\n      onClick={onClick}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n    >\n      {icon}\n    </Button>\n  );\n\n  return (\n    <div className=\"flex h-dvh w-full flex-col bg-background\">\n      <MessageScrollerProvider autoScroll>\n        <MessageScroller>\n          <MessageScrollerViewport>\n            <MessageScrollerContent className=\"mx-auto w-full max-w-2xl gap-4.5 px-4 pt-10 pb-4\">\n              {turns.map((turn) => {\n                const mine = turn.role === 'user';\n                const align = mine ? 'end' : 'start';\n                return (\n                  <MessageScrollerItem\n                    className={cn(\n                      mine && 'pt-4.5 first:pt-0',\n                      !thread.some((t) => t.id === turn.id) && enter\n                    )}\n                    key={turn.id}\n                    messageId={turn.id}\n                  >\n                    <Message align={align}>\n                      <MessageContent className=\"gap-2.5\">\n                        {body(turn, align)}\n                        {turn.text && edit?.id !== turn.id && (\n                          <MessageFooter className=\"-mx-2 gap-0.5\">\n                            {action('Copy', <IconCopy stroke={1.6} />, () =>\n                              navigator.clipboard.writeText(turn.text)\n                            )}\n                            {mine\n                              ? action(\n                                  'Edit',\n                                  <IconPencil stroke={1.6} />,\n                                  () =>\n                                    setEdit({ id: turn.id, text: turn.text })\n                                )\n                              : action(\n                                  'Regenerate',\n                                  <IconRefresh stroke={1.6} />,\n                                  () => stream(turn.id)\n                                )}\n                            {!mine && (\n                              <DropdownMenu>\n                                <DropdownMenuTrigger\n                                  render={action(\n                                    'More',\n                                    <IconDots stroke={1.6} />\n                                  )}\n                                />\n                                <DropdownMenuContent\n                                  align=\"start\"\n                                  className=\"w-44 rounded-[14px] p-1.5\"\n                                >\n                                  <DropdownMenuGroup className=\"flex flex-col gap-0.5\">\n                                    <DropdownMenuItem\n                                      className={cn(item, 'gap-2.5')}\n                                    >\n                                      <IconThumbUp stroke={1.6} />\n                                      Good response\n                                    </DropdownMenuItem>\n                                    <DropdownMenuItem\n                                      className={cn(item, 'gap-2.5')}\n                                    >\n                                      <IconThumbDown stroke={1.6} />\n                                      Bad response\n                                    </DropdownMenuItem>\n                                    <DropdownMenuItem\n                                      className={cn(item, 'gap-2.5')}\n                                    >\n                                      <IconVolume stroke={1.6} />\n                                      Read aloud\n                                    </DropdownMenuItem>\n                                  </DropdownMenuGroup>\n                                </DropdownMenuContent>\n                              </DropdownMenu>\n                            )}\n                          </MessageFooter>\n                        )}\n                      </MessageContent>\n                    </Message>\n                  </MessageScrollerItem>\n                );\n              })}\n            </MessageScrollerContent>\n          </MessageScrollerViewport>\n          <MessageScrollerButton\n            className={cn(\n              easeOut,\n              'bottom-2 h-7 gap-1.5 rounded-full border-0 px-3.5 font-medium text-muted-foreground text-xs shadow-[0_0_0_1px_oklch(0_0_0/0.06),0_2px_8px_oklch(0_0_0/0.08)] transition-[translate,scale,opacity,background-color] hover:text-foreground active:not-aria-[haspopup]:translate-y-0 data-[active=true]:active:scale-[0.96] dark:shadow-[0_0_0_1px_oklch(1_0_0/0.1),0_2px_8px_oklch(0_0_0/0.3)]'\n            )}\n            size=\"sm\"\n          >\n            <IconChevronDown className=\"size-3.5\" stroke={1.75} />\n            Jump to latest\n          </MessageScrollerButton>\n        </MessageScroller>\n      </MessageScrollerProvider>\n\n      <form\n        className=\"mx-auto flex w-full max-w-2xl flex-col items-center gap-2.5 px-4 pt-2 pb-3.5\"\n        onSubmit={send}\n      >\n        <InputGroup\n          className={cn(\n            'min-h-11 items-end rounded-[22px] bg-background px-0.5 shadow-xs has-[[data-slot=input-group-control]:focus-visible]:ring-0',\n            tall && 'flex-wrap rounded-3xl'\n          )}\n        >\n          <InputGroupAddon\n            className={cn('pb-1.25 pl-2.5', tall && 'order-none')}\n          >\n            <DropdownMenu>\n              <DropdownMenuTrigger\n                render={\n                  <InputGroupButton\n                    aria-label=\"Add attachment\"\n                    className={cn(press, 'rounded-full')}\n                    size=\"icon-sm\"\n                  />\n                }\n              >\n                <IconPlus stroke={1.8} />\n              </DropdownMenuTrigger>\n              <DropdownMenuContent\n                align=\"start\"\n                className=\"w-56 rounded-[14px] p-1.5\"\n                sideOffset={10}\n              >\n                <DropdownMenuGroup className=\"flex flex-col gap-0.5\">\n                  <DropdownMenuItem className={cn(item, 'gap-2.5')}>\n                    <IconPaperclip stroke={1.6} />\n                    Add files\n                  </DropdownMenuItem>\n                  <DropdownMenuItem className={cn(item, 'gap-2.5')}>\n                    <IconPhoto stroke={1.6} />\n                    Add photos\n                  </DropdownMenuItem>\n                  <DropdownMenuItem className={cn(item, 'gap-2.5')}>\n                    <IconWorldSearch stroke={1.6} />\n                    Search the web\n                  </DropdownMenuItem>\n                </DropdownMenuGroup>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </InputGroupAddon>\n          <InputGroupTextarea\n            className={cn(\n              'max-h-48 min-h-0 py-2.5 pl-1 text-[15px]/6 md:text-[15px]/6',\n              tall && '-order-1 basis-full px-2.75 py-1.5'\n            )}\n            onChange={(e) => {\n              setDraft(e.target.value);\n              setTall(e.target.scrollHeight > 44);\n            }}\n            onKeyDown={(e) => {\n              if (e.key === 'Enter' && !e.shiftKey) {\n                e.preventDefault();\n                e.currentTarget.form?.requestSubmit();\n              }\n            }}\n            placeholder={listening ? 'Listening…' : 'Ask anything'}\n            rows={1}\n            value={draft}\n          />\n          <InputGroupAddon\n            align=\"inline-end\"\n            className={cn('gap-1.5 pr-2.5 pb-1.25', tall && 'ml-auto')}\n          >\n            {draft.trim() ? (\n              <InputGroupButton\n                aria-label=\"Send\"\n                className={cn(swap, press, 'rounded-full')}\n                disabled={streaming}\n                size=\"icon-sm\"\n                type=\"submit\"\n                variant=\"default\"\n              >\n                <IconArrowUp stroke={2} />\n              </InputGroupButton>\n            ) : (\n              <InputGroupButton\n                aria-label={listening ? 'Stop listening' : 'Voice input'}\n                aria-pressed={listening}\n                className={cn(\n                  swap,\n                  press,\n                  'rounded-full',\n                  listening &&\n                    'bg-destructive/15 text-destructive hover:bg-destructive/25'\n                )}\n                onClick={() => setListening(!listening)}\n                size=\"icon-sm\"\n              >\n                {listening ? (\n                  <IconPlayerStopFilled className=\"animate-pulse motion-reduce:animate-none\" />\n                ) : (\n                  <IconMicrophone stroke={1.8} />\n                )}\n              </InputGroupButton>\n            )}\n            <DropdownMenu>\n              <DropdownMenuTrigger\n                render={\n                  <InputGroupButton\n                    aria-label=\"Intelligence level\"\n                    className={cn(\n                      press,\n                      'h-8 gap-1.25 rounded-full bg-muted px-3 font-medium text-[13px] text-muted-foreground hover:bg-muted'\n                    )}\n                    size=\"sm\"\n                  />\n                }\n              >\n                {level}\n                <IconChevronDown className=\"size-3\" stroke={2} />\n              </DropdownMenuTrigger>\n              <DropdownMenuContent\n                align=\"end\"\n                className=\"w-49 rounded-[14px] p-1.5\"\n                side=\"top\"\n                sideOffset={10}\n              >\n                <DropdownMenuGroup className=\"flex flex-col gap-0.5\">\n                  <DropdownMenuLabel className=\"px-2.5 pt-1 pb-0.5 text-[11px]\">\n                    Intelligence\n                  </DropdownMenuLabel>\n                  <DropdownMenuRadioGroup\n                    onValueChange={setLevel}\n                    value={level}\n                  >\n                    {levels.map((l) => (\n                      <DropdownMenuRadioItem\n                        className={cn(item, 'pr-8 pl-2.5')}\n                        key={l.name}\n                        value={l.name}\n                      >\n                        {l.name}\n                        {l.hint && (\n                          <span className=\"text-muted-foreground text-xs\">\n                            {l.hint}\n                          </span>\n                        )}\n                      </DropdownMenuRadioItem>\n                    ))}\n                  </DropdownMenuRadioGroup>\n                  <DropdownMenuSeparator className=\"mx-1.5 my-1\" />\n                  <DropdownMenuSub>\n                    <DropdownMenuSubTrigger\n                      className={cn(\n                        item,\n                        '[&>svg]:size-3.5 [&>svg]:text-muted-foreground'\n                      )}\n                    >\n                      {model}\n                    </DropdownMenuSubTrigger>\n                    <DropdownMenuSubContent className=\"w-44 rounded-[14px] p-1.5\">\n                      <DropdownMenuRadioGroup\n                        className=\"flex flex-col gap-0.5\"\n                        onValueChange={setModel}\n                        value={model}\n                      >\n                        {models.map((m) => (\n                          <DropdownMenuRadioItem\n                            className={cn(item, 'pr-8 pl-2.5')}\n                            key={m}\n                            value={m}\n                          >\n                            {m}\n                          </DropdownMenuRadioItem>\n                        ))}\n                      </DropdownMenuRadioGroup>\n                    </DropdownMenuSubContent>\n                  </DropdownMenuSub>\n                </DropdownMenuGroup>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </InputGroupAddon>\n        </InputGroup>\n        <p className=\"text-muted-foreground text-xs\">\n          AI can make mistakes. Check important info.\n        </p>\n      </form>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/chat-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/chat-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/bubble.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/components/ui/bubble.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction BubbleGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex min-w-0 flex-col gap-2', className)}\n      data-slot=\"bubble-group\"\n      {...props}\n    />\n  );\n}\n\nconst bubbleVariants = cva(\n  'group/bubble relative flex w-fit min-w-0 max-w-[80%] flex-col gap-1 data-[variant=ghost]:max-w-full data-[align=end]:self-end group-data-[align=end]/message:self-end',\n  {\n    variants: {\n      variant: {\n        default:\n          '*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80',\n        secondary:\n          '*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]',\n        muted:\n          '*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]',\n        tinted:\n          '*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]',\n        outline:\n          '*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30',\n        ghost:\n          'border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50',\n        destructive:\n          '*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Bubble({\n  variant = 'default',\n  align = 'start',\n  className,\n  ...props\n}: React.ComponentProps<'div'> &\n  VariantProps<typeof bubbleVariants> & {\n    align?: 'start' | 'end';\n  }) {\n  return (\n    <div\n      className={cn(bubbleVariants({ variant }), className)}\n      data-align={align}\n      data-slot=\"bubble\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction BubbleContent({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'wrap-break-word w-fit min-w-0 max-w-full overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed group-data-[align=end]/bubble:self-end [button,a]:outline-none [button,a]:transition-colors [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50 [button]:text-left',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'bubble-content',\n    },\n  });\n}\n\nconst bubbleReactionsVariants = cva(\n  'absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0',\n  {\n    variants: {\n      side: {\n        top: '-translate-y-3/4 top-0',\n        bottom: 'bottom-0 translate-y-3/4',\n      },\n      align: {\n        start: 'left-3',\n        end: 'right-3',\n      },\n    },\n    defaultVariants: {\n      side: 'bottom',\n      align: 'end',\n    },\n  }\n);\n\nfunction BubbleReactions({\n  side = 'bottom',\n  align = 'end',\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  align?: 'start' | 'end';\n  side?: 'top' | 'bottom';\n}) {\n  return (\n    <div\n      className={cn(bubbleReactionsVariants({ side, align }), className)}\n      data-align={align}\n      data-side={side}\n      data-slot=\"bubble-reactions\"\n      {...props}\n    />\n  );\n}\n\nexport { BubbleGroup, Bubble, BubbleContent, BubbleReactions };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/message-scroller.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/components/ui/message-scroller.tsx",
          "content": "'use client';\n\nimport {\n  MessageScroller as MessageScrollerPrimitive,\n  useMessageScroller,\n  useMessageScrollerScrollable,\n  useMessageScrollerVisibility,\n} from '@shadcn/react/message-scroller';\nimport { ArrowDownIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction MessageScrollerProvider(\n  props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>\n) {\n  return <MessageScrollerPrimitive.Provider {...props} />;\n}\n\nfunction MessageScroller({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) {\n  return (\n    <MessageScrollerPrimitive.Root\n      className={cn(\n        'group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden',\n        className\n      )}\n      data-slot=\"message-scroller\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerViewport({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) {\n  return (\n    <MessageScrollerPrimitive.Viewport\n      className={cn(\n        'scroll-fade-b scrollbar-thin scrollbar-gutter-stable data-autoscrolling:scrollbar-thumb-transparent data-autoscrolling:scrollbar-track-transparent size-full min-h-0 min-w-0 overflow-y-auto overscroll-contain contain-content data-pending-scroll:invisible',\n        className\n      )}\n      data-slot=\"message-scroller-viewport\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) {\n  return (\n    <MessageScrollerPrimitive.Content\n      className={cn('flex h-max min-h-full flex-col gap-6', className)}\n      data-slot=\"message-scroller-content\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerItem({\n  className,\n  scrollAnchor = false,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) {\n  return (\n    <MessageScrollerPrimitive.Item\n      className={cn(\n        'min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]',\n        className\n      )}\n      data-slot=\"message-scroller-item\"\n      scrollAnchor={scrollAnchor}\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerButton({\n  direction = 'end',\n  className,\n  children,\n  render,\n  variant = 'secondary',\n  size = 'icon-sm',\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Button> &\n  Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {\n  return (\n    <MessageScrollerPrimitive.Button\n      className={cn(\n        '-translate-x-1/2 data-[direction=start]:data-[active=false]:-translate-y-full absolute inset-s-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[direction=end]:data-[active=false]:translate-y-full data-[active=false]:pointer-events-none data-[direction=start]:top-4 data-[direction=end]:bottom-4 data-[active=true]:translate-y-0 data-[active=false]:scale-95 data-[active=true]:scale-100 data-[active=false]:opacity-0 data-[active=true]:opacity-100 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180',\n        className\n      )}\n      data-direction={direction}\n      data-size={size}\n      data-slot=\"message-scroller-button\"\n      data-variant={variant}\n      direction={direction}\n      render={render ?? <Button size={size} variant={variant} />}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <ArrowDownIcon />\n          <span className=\"sr-only\">\n            {direction === 'end' ? 'Scroll to end' : 'Scroll to start'}\n          </span>\n        </>\n      )}\n    </MessageScrollerPrimitive.Button>\n  );\n}\n\nexport {\n  MessageScrollerProvider,\n  MessageScroller,\n  MessageScrollerViewport,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerButton,\n  useMessageScroller,\n  useMessageScrollerScrollable,\n  useMessageScrollerVisibility,\n};\n"
        },
        {
          "path": "components/ui/message.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/components/ui/message.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction MessageGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex min-w-0 flex-col gap-2', className)}\n      data-slot=\"message-group\"\n      {...props}\n    />\n  );\n}\n\nfunction Message({\n  className,\n  align = 'start',\n  ...props\n}: React.ComponentProps<'div'> & { align?: 'start' | 'end' }) {\n  return (\n    <div\n      className={cn(\n        'group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse',\n        className\n      )}\n      data-align={align}\n      data-slot=\"message\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageAvatar({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group-has-data-[slot=message-footer]/message:-translate-y-8 flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted',\n        className\n      )}\n      data-slot=\"message-avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'wrap-break-word flex w-full min-w-0 flex-col gap-2.5 group-data-[align=end]/message:*:data-slot:self-end',\n        className\n      )}\n      data-slot=\"message-content\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex min-w-0 max-w-full items-center px-3 font-medium text-muted-foreground text-xs group-has-data-[variant=ghost]/message:px-0',\n        className\n      )}\n      data-slot=\"message-header\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex min-w-0 max-w-full items-center px-3 font-medium text-muted-foreground text-xs group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end',\n        className\n      )}\n      data-slot=\"message-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  MessageGroup,\n  Message,\n  MessageAvatar,\n  MessageContent,\n  MessageFooter,\n  MessageHeader,\n};\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "chat",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/chat-02/chat-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/chat/chat-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "chat",
          "ai",
          "conversation",
          "messages",
          "聊天",
          "对话",
          "消息",
          "助手"
        ],
        "contentGuidelines": [
          "Connect message streaming and persistence to your application.",
          "Test empty history, long responses, and failed requests."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/chat-02/chat-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/chat-02"
      }
    },
    {
      "name": "chat-03",
      "type": "registry:block",
      "title": "Chat Application",
      "description": "Chat Application. AI chat interfaces with message history, prompt controls, and conversation navigation.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@shadcn/react@0.3.1",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cmdk@1.1.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/chat/chat-03/app/chat/data.json",
          "type": "registry:file",
          "target": "@components/uiblocks/chat-03/app/chat/data.json",
          "content": "{\n  \"user\": {\n    \"name\": \"Ephraim Duncan\",\n    \"email\": \"ephraim@duncan.land\",\n    \"plan\": \"Pro\",\n    \"renews\": \"Oct 12\",\n    \"avatar\": \"https://uiblocks.org/avatar-01.png\"\n  },\n  \"models\": [\n    {\n      \"vendor\": \"openai\",\n      \"name\": \"GPT-6 Astra\",\n      \"hint\": \"Most capable for complex reasoning and coding\",\n      \"context\": \"1M\"\n    },\n    {\n      \"vendor\": \"openai\",\n      \"name\": \"GPT-5.6 Sol\",\n      \"hint\": \"Flagship for complex professional work\",\n      \"context\": \"1M\"\n    },\n    {\n      \"vendor\": \"openai\",\n      \"name\": \"GPT-5.6 Terra\",\n      \"hint\": \"Balances intelligence and cost\",\n      \"context\": \"1M\"\n    },\n    {\n      \"vendor\": \"openai\",\n      \"name\": \"GPT-5.6 Luna\",\n      \"hint\": \"Fastest and most affordable\",\n      \"context\": \"128K\"\n    },\n    {\n      \"vendor\": \"anthropic\",\n      \"name\": \"Claude Fable 5.1\",\n      \"hint\": \"Demanding reasoning and long-horizon agentic work\",\n      \"context\": \"1M\"\n    },\n    {\n      \"vendor\": \"anthropic\",\n      \"name\": \"Claude Opus 5\",\n      \"hint\": \"Complex agentic coding and enterprise work\",\n      \"context\": \"1M\"\n    },\n    {\n      \"vendor\": \"anthropic\",\n      \"name\": \"Claude Sonnet 5\",\n      \"hint\": \"Best combination of speed and intelligence\",\n      \"context\": \"1M\"\n    },\n    {\n      \"vendor\": \"anthropic\",\n      \"name\": \"Claude Haiku 4.5\",\n      \"hint\": \"Fastest with near-frontier intelligence\",\n      \"context\": \"200K\"\n    }\n  ],\n  \"replies\": [\n    {\n      \"reasoning\": \"The user wants a price check. Compare the quote against regional install averages, then weigh it against the payback window from earlier in the thread.\",\n      \"text\": \"Your quote lands near the middle of the range for a cold-climate unit of that size. The equipment line is fair; the labor line is about 10% above typical. Ask the installer to itemize ductwork changes, since that is where the extra usually hides. With the rebate applied, payback still comes in under 8 years.\"\n    },\n    {\n      \"reasoning\": \"Short factual answer. Note the trade-off between efficiency and noise so the user can decide.\",\n      \"text\": \"Most modern units run between 40 and 60 dB at the outdoor fan, roughly the level of a quiet conversation. Placing the unit away from bedroom windows and on a vibration pad makes a bigger difference than the model choice.\"\n    },\n    {\n      \"reasoning\": \"The user is asking about maintenance. List the few things that matter and skip the marketing extras.\",\n      \"text\": \"Very little. Clean or swap the filter every one to three months, keep leaves and snow off the outdoor unit, and book a professional check once a year to confirm refrigerant levels. That is the whole list.\"\n    }\n  ],\n  \"threads\": [\n    {\n      \"id\": \"design-tokens\",\n      \"title\": \"Design system tokens\",\n      \"preview\": \"Naming scheme for spacing and color\",\n      \"updated\": \"3d\",\n      \"group\": \"week\",\n      \"pinned\": true,\n      \"turns\": [\n        {\n          \"id\": \"dt-1\",\n          \"role\": \"user\",\n          \"text\": \"Should spacing tokens be named by size or by role?\"\n        },\n        {\n          \"id\": \"dt-2\",\n          \"role\": \"assistant\",\n          \"thought\": 4,\n          \"reasoning\": \"Both approaches are common. Recommend a two-layer scheme so the team gets stable role names without losing raw sizes.\",\n          \"text\": \"Use both, in two layers. Keep a raw scale (space-1 through space-12) and put role tokens on top of it (space-inline, space-stack, space-section). Components reference roles, so a scale change never touches component code.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"auth-refactor\",\n      \"title\": \"Auth refactor plan\",\n      \"preview\": \"Session cookies to short-lived JWTs\",\n      \"updated\": \"5d\",\n      \"group\": \"week\",\n      \"pinned\": true,\n      \"turns\": [\n        {\n          \"id\": \"ar-1\",\n          \"role\": \"user\",\n          \"text\": \"What is the safest order to migrate sessions to short-lived tokens?\"\n        },\n        {\n          \"id\": \"ar-2\",\n          \"role\": \"assistant\",\n          \"thought\": 6,\n          \"reasoning\": \"Migration needs a dual-read period. Outline steps that let the old and new paths coexist before cutting over.\",\n          \"text\": \"Issue both for a release, read both, then stop issuing the old one. Start by adding token issuance next to the existing session write, make every auth check accept either, and only after a full expiry window remove the session path.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"heat-pump\",\n      \"title\": \"Heat pump research\",\n      \"preview\": \"Payback math on replacing a gas boiler\",\n      \"updated\": \"2h\",\n      \"group\": \"today\",\n      \"turns\": [\n        {\n          \"id\": \"hp-1\",\n          \"role\": \"user\",\n          \"text\": \"How do heat pumps keep working when it's freezing outside?\"\n        },\n        {\n          \"id\": \"hp-2\",\n          \"role\": \"assistant\",\n          \"thought\": 8,\n          \"reasoning\": \"Explain the physics briefly: refrigerant boiling point is far below freezing, so there is still heat to extract. Give one concrete efficiency figure at a cold temperature.\",\n          \"text\": \"Even freezing air holds heat. A heat pump's refrigerant boils far below 0 °C, so it can absorb outdoor heat, compress it to raise the temperature, and release it inside. Modern cold-climate units keep 250–300% efficiency at −15 °C.\"\n        },\n        {\n          \"id\": \"hp-3\",\n          \"role\": \"user\",\n          \"text\": \"Is it worth replacing a gas boiler mid-life?\"\n        },\n        {\n          \"id\": \"hp-4\",\n          \"role\": \"assistant\",\n          \"thought\": 12,\n          \"reasoning\": \"This depends on the price ratio between electricity and gas, the remaining boiler life, and rebates. Give the rule of thumb and the three inputs.\",\n          \"text\": \"Usually yes, if your electricity-to-gas price ratio is under about 3.5. The math hinges on three numbers: your boiler's remaining life, local energy prices, and available rebates. Start with the rebates — in many regions they cover 30–40% of the install, which shortens payback to 6–8 years.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"sql-churn\",\n      \"title\": \"SQL for churn cohort\",\n      \"preview\": \"Window function draft, not yet run\",\n      \"updated\": \"5h\",\n      \"group\": \"today\",\n      \"turns\": [\n        {\n          \"id\": \"sc-1\",\n          \"role\": \"user\",\n          \"text\": \"Write a query that groups users by signup month and shows how many are still active 90 days later.\"\n        },\n        {\n          \"id\": \"sc-2\",\n          \"role\": \"assistant\",\n          \"thought\": 9,\n          \"reasoning\": \"Cohort by signup month, then join activity at day 90. Keep it to one CTE so it is easy to read.\",\n          \"text\": \"Group by date_trunc of the signup date, then left join an activity subquery filtered to events within 90 days of signup. Count distinct users in both, and divide for the retention rate. I drafted it with a single CTE so you can swap the window without touching the join.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"lisbon\",\n      \"title\": \"Trip to Lisbon in October\",\n      \"preview\": \"Four days, mostly on foot\",\n      \"updated\": \"2h\",\n      \"group\": \"today\",\n      \"turns\": [\n        {\n          \"id\": \"li-1\",\n          \"role\": \"user\",\n          \"text\": \"Is October a good month for Lisbon?\"\n        },\n        {\n          \"id\": \"li-2\",\n          \"role\": \"assistant\",\n          \"thought\": 3,\n          \"reasoning\": \"Weather and crowds are the two things the user cares about. Keep it short.\",\n          \"text\": \"One of the best. Days sit around 22 °C, the summer crowds are gone, and the sea is still warm enough to swim. Pack a light layer for evenings and expect a few short showers late in the month.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"q3-report\",\n      \"title\": \"Q3 report outline\",\n      \"preview\": \"Three sections drafted, intro missing\",\n      \"updated\": \"1d\",\n      \"group\": \"yesterday\",\n      \"turns\": [\n        {\n          \"id\": \"q3-1\",\n          \"role\": \"user\",\n          \"text\": \"Give me a three-part outline for a quarterly report to the board.\"\n        },\n        {\n          \"id\": \"q3-2\",\n          \"role\": \"assistant\",\n          \"thought\": 5,\n          \"reasoning\": \"Boards want results, then risks, then asks. Map the three sections to that order.\",\n          \"text\": \"Results first, then risks, then asks. Open with the three numbers that moved and why, follow with the two things that could go wrong next quarter, and close with the decisions you need from the board. Keep each section to one page.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"dockerfile\",\n      \"title\": \"Dockerfile multi-stage build\",\n      \"preview\": \"Trim the final image below 100 MB\",\n      \"updated\": \"1d\",\n      \"group\": \"yesterday\",\n      \"turns\": [\n        {\n          \"id\": \"df-1\",\n          \"role\": \"user\",\n          \"text\": \"Why is my Node image 1.2 GB?\"\n        },\n        {\n          \"id\": \"df-2\",\n          \"role\": \"assistant\",\n          \"thought\": 4,\n          \"reasoning\": \"Most likely a single-stage build carrying dev dependencies and the full base image. Recommend a multi-stage build on a slim base.\",\n          \"text\": \"You are shipping the build toolchain. Build in one stage on the full image, then copy only the output and production node_modules into a second stage based on node:slim or distroless. That alone usually lands under 150 MB.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"naming\",\n      \"title\": \"Naming a side project\",\n      \"preview\": \"Short, spellable, domain available\",\n      \"updated\": \"1d\",\n      \"group\": \"yesterday\",\n      \"turns\": [\n        {\n          \"id\": \"nm-1\",\n          \"role\": \"user\",\n          \"text\": \"What makes a product name easy to remember?\"\n        },\n        {\n          \"id\": \"nm-2\",\n          \"role\": \"assistant\",\n          \"thought\": 3,\n          \"reasoning\": \"Keep to the practical rules: short, easy to spell after hearing it once, and not already crowded.\",\n          \"text\": \"Two syllables, spellable after hearing it once, and no existing product in the same space with a similar sound. Say the name out loud in a sentence and see if you have to explain it.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"postgres\",\n      \"title\": \"Postgres index bloat\",\n      \"preview\": \"When to REINDEX versus VACUUM\",\n      \"updated\": \"3d\",\n      \"group\": \"week\",\n      \"turns\": [\n        {\n          \"id\": \"pg-1\",\n          \"role\": \"user\",\n          \"text\": \"How do I tell if an index is bloated?\"\n        },\n        {\n          \"id\": \"pg-2\",\n          \"role\": \"assistant\",\n          \"thought\": 6,\n          \"reasoning\": \"Point to pgstattuple for a real measurement, and give the rough threshold where REINDEX pays off.\",\n          \"text\": \"Install pgstattuple and check the leaf fragmentation and free space of the index. Anything above about 30% free space on a hot index is worth a REINDEX CONCURRENTLY. VACUUM alone will not shrink it.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"cover-letter\",\n      \"title\": \"Cover letter rewrite\",\n      \"preview\": \"Cut to half a page\",\n      \"updated\": \"5d\",\n      \"group\": \"week\",\n      \"turns\": [\n        {\n          \"id\": \"cl-1\",\n          \"role\": \"user\",\n          \"text\": \"My cover letter is a full page. What should I cut?\"\n        },\n        {\n          \"id\": \"cl-2\",\n          \"role\": \"assistant\",\n          \"thought\": 4,\n          \"reasoning\": \"Most cover letters repeat the resume. Cut everything that is already there and keep the one story that is not.\",\n          \"text\": \"Cut anything the resume already says. Keep one short story that shows how you work, one sentence on why this company, and a plain closing line. That is half a page.\"\n        }\n      ]\n    },\n    {\n      \"id\": \"rust\",\n      \"title\": \"Rust lifetimes explained\",\n      \"preview\": \"Borrow checker with two references\",\n      \"updated\": \"6d\",\n      \"group\": \"week\",\n      \"turns\": [\n        {\n          \"id\": \"rs-1\",\n          \"role\": \"user\",\n          \"text\": \"Why does the compiler want a lifetime on this function returning a reference?\"\n        },\n        {\n          \"id\": \"rs-2\",\n          \"role\": \"assistant\",\n          \"thought\": 7,\n          \"reasoning\": \"The function takes two references and returns one; the compiler cannot know which input the output borrows from. Explain elision rules briefly.\",\n          \"text\": \"Because the function takes two references and returns one, the compiler cannot tell which input the result borrows from. A lifetime annotation is you telling it. With a single reference input it would be inferred automatically.\"\n        }\n      ]\n    }\n  ]\n}\n"
        },
        {
          "path": "content/components/chat/chat-03/app/chat/page.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/app/chat/page.tsx",
          "content": "'use client';\n\nimport {\n  IconCheck,\n  IconChevronDown,\n  IconLayoutSidebar,\n  IconShare,\n} from '@tabler/icons-react';\nimport { useRef, useState } from 'react';\nimport { Button } from \"../../shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"../../shared/components/ui/dropdown-menu\";\nimport { Kbd } from \"../../shared/components/ui/kbd\";\nimport {\n  SidebarInset,\n  SidebarProvider,\n  SidebarTrigger,\n} from \"../../shared/components/ui/sidebar\";\nimport { cn } from \"../../shared/lib/utils\";\nimport { ChatSidebar } from \"../../components/chat-sidebar\";\nimport { CommandMenu } from \"../../components/command-menu\";\nimport { type Attachment, Composer } from \"../../components/composer\";\nimport {\n  Conversation,\n  type Thread,\n  type Turn,\n} from \"../../components/conversation\";\nimport { ClaudeLogo, OpenAILogo } from \"../../components/logos\";\nimport data from \"./data.json\" with { type: 'json' };\n\nconst { models, replies } = data;\nconst seed = data.threads as Thread[];\n\nconst logos = { openai: OpenAILogo, anthropic: ClaudeLogo };\nconst Logo = ({ vendor, ...props }: { vendor: string; className?: string }) => {\n  const Icon = logos[vendor as keyof typeof logos];\n  return <Icon {...props} />;\n};\n\nconst thinkDelay = 900;\nconst tokenDelay = 40;\n\nexport default function Chat03() {\n  const [threads, setThreads] = useState(seed);\n  const [activeId, setActiveId] = useState<string | null>('heat-pump');\n  const [draft, setDraft] = useState('');\n  const [attachment, setAttachment] = useState<Attachment | null>(null);\n  const [model, setModel] = useState(models[0].name);\n  const [search, setSearch] = useState(false);\n  const [replyIndex, setReplyIndex] = useState(0);\n  const timer = useRef<number | null>(null);\n\n  const thread = threads.find((t) => t.id === activeId) ?? null;\n  const streaming = thread?.turns.some((t) => t.streaming) ?? false;\n  const altModel = models.find((m) => m.name !== model)?.name ?? model;\n\n  const patch = (threadId: string, turnId: string, fn: (t: Turn) => Turn) =>\n    setThreads((prev) =>\n      prev.map((t) =>\n        t.id === threadId\n          ? { ...t, turns: t.turns.map((m) => (m.id === turnId ? fn(m) : m)) }\n          : t\n      )\n    );\n\n  const stream = (threadId: string, turnId: string) => {\n    const reply = replies[replyIndex % replies.length];\n    setReplyIndex(replyIndex + 1);\n    patch(threadId, turnId, (t) => ({\n      ...t,\n      text: '',\n      reasoning: reply.reasoning,\n      thought: undefined,\n      error: undefined,\n      streaming: true,\n    }));\n    let shown = 0;\n    const tick = () => {\n      shown = Math.min(\n        reply.text.length,\n        shown + 2 + Math.floor(Math.random() * 5)\n      );\n      const done = shown >= reply.text.length;\n      patch(threadId, turnId, (t) => ({\n        ...t,\n        text: reply.text.slice(0, shown),\n        thought: done ? 4 + Math.round(reply.reasoning.length / 40) : t.thought,\n        streaming: !done,\n      }));\n      timer.current = done\n        ? null\n        : window.setTimeout(tick, tokenDelay * (0.5 + Math.random()));\n    };\n    tick();\n  };\n\n  const stop = () => {\n    if (timer.current) {\n      window.clearTimeout(timer.current);\n      timer.current = null;\n    }\n    const live = thread?.turns.find((t) => t.streaming);\n    if (thread && live) {\n      patch(thread.id, live.id, (t) => ({\n        ...t,\n        streaming: false,\n        error: true,\n      }));\n    }\n  };\n\n  const send = () => {\n    const text = draft.trim();\n    const replyId = crypto.randomUUID();\n    const turns: Turn[] = [\n      { id: crypto.randomUUID(), role: 'user', text, day: 'today' },\n      {\n        id: replyId,\n        role: 'assistant',\n        text: '',\n        streaming: true,\n        day: 'today',\n      },\n    ];\n    const id = thread?.id ?? crypto.randomUUID();\n    setThreads((prev) =>\n      thread\n        ? [\n            {\n              ...thread,\n              updated: 'now',\n              group: 'today',\n              turns: [\n                ...thread.turns.map((t) => ({\n                  ...t,\n                  day: t.day ?? thread.group,\n                })),\n                ...turns,\n              ],\n            },\n            ...prev.filter((t) => t.id !== id),\n          ]\n        : [\n            {\n              id,\n              title: text.split(' ').slice(0, 5).join(' '),\n              preview: text,\n              updated: 'now',\n              group: 'today',\n              turns,\n            },\n            ...prev,\n          ]\n    );\n    setActiveId(id);\n    setDraft('');\n    setAttachment(null);\n    window.setTimeout(() => stream(id, replyId), thinkDelay);\n  };\n\n  const retry = (turnId: string, next?: string) => {\n    if (!thread) {\n      return;\n    }\n    if (next) {\n      setModel(next);\n    }\n    patch(thread.id, turnId, (t) => ({\n      ...t,\n      text: '',\n      thought: undefined,\n      error: undefined,\n      streaming: true,\n    }));\n    window.setTimeout(() => stream(thread.id, turnId), thinkDelay);\n  };\n\n  const edit = (turn: Turn) => {\n    if (!thread) {\n      return;\n    }\n    const at = thread.turns.findIndex((t) => t.id === turn.id);\n    setThreads((prev) =>\n      prev.map((t) =>\n        t.id === thread.id ? { ...t, turns: t.turns.slice(0, at) } : t\n      )\n    );\n    setDraft(turn.text);\n  };\n\n  return (\n    <SidebarProvider\n      className=\"h-dvh min-h-0\"\n      style={{ '--sidebar-width': '260px' } as React.CSSProperties}\n    >\n      <ChatSidebar\n        activeId={activeId}\n        onChange={(next) => {\n          setThreads(next);\n          if (activeId && !next.some((t) => t.id === activeId)) {\n            setActiveId(null);\n          }\n        }}\n        onNew={() => setActiveId(null)}\n        onSearch={() => setSearch(true)}\n        onSelect={setActiveId}\n        threads={threads}\n        user={data.user}\n      />\n      <SidebarInset className=\"min-h-0 bg-background\">\n        <header className=\"flex h-13 shrink-0 items-center gap-2.5 border-b px-4\">\n          <SidebarTrigger className=\"size-7.5 rounded-lg text-muted-foreground\">\n            <IconLayoutSidebar className=\"size-4\" stroke={1.8} />\n          </SidebarTrigger>\n          <DropdownMenu>\n            <DropdownMenuTrigger\n              render={\n                <Button\n                  className=\"h-8 gap-1.5 rounded-lg px-2.5 text-[13px]\"\n                  variant=\"secondary\"\n                />\n              }\n            >\n              <Logo\n                className=\"size-3.5\"\n                vendor={\n                  models.find((m) => m.name === model)?.vendor ?? 'openai'\n                }\n              />\n              {model}\n              <IconChevronDown\n                className=\"size-3 text-muted-foreground\"\n                stroke={2}\n              />\n            </DropdownMenuTrigger>\n            <DropdownMenuContent\n              align=\"start\"\n              className=\"w-75 rounded-xl p-1.5\"\n              sideOffset={6}\n            >\n              <DropdownMenuGroup className=\"flex flex-col gap-0.5\">\n                {models.map((m) => (\n                  <DropdownMenuItem\n                    className={cn(\n                      'gap-2.5 rounded-md px-2.5 py-2',\n                      m.name === model && 'bg-muted'\n                    )}\n                    key={m.name}\n                    onClick={() => setModel(m.name)}\n                  >\n                    <span className=\"flex grow flex-col gap-0.5\">\n                      <span className=\"flex items-center gap-1.5 font-medium text-[13px]/4\">\n                        <Logo className=\"size-3 shrink-0\" vendor={m.vendor} />\n                        {m.name}\n                      </span>\n                      <span className=\"text-[11px]/3.5 text-muted-foreground\">\n                        {m.hint}\n                      </span>\n                    </span>\n                    {m.name === model && (\n                      <IconCheck className=\"size-3.5\" stroke={2.2} />\n                    )}\n                    <Kbd className=\"h-4.5 border border-border bg-background px-1.5 font-mono text-[10px]\">\n                      {m.context}\n                    </Kbd>\n                  </DropdownMenuItem>\n                ))}\n              </DropdownMenuGroup>\n            </DropdownMenuContent>\n          </DropdownMenu>\n          <div className=\"grow\" />\n          {thread && (\n            <Button\n              aria-label=\"Share thread\"\n              className=\"size-7.5 rounded-lg text-muted-foreground\"\n              size=\"icon-sm\"\n              variant=\"ghost\"\n            >\n              <IconShare className=\"size-4\" stroke={1.8} />\n            </Button>\n          )}\n        </header>\n\n        <Conversation\n          altModel={altModel}\n          onEdit={edit}\n          onOpen={setActiveId}\n          onRetry={retry}\n          recent={threads.filter((t) => !t.pinned).slice(0, 3)}\n          thread={thread}\n        />\n\n        <Composer\n          attachment={attachment}\n          draft={draft}\n          onAttachment={setAttachment}\n          onDraft={setDraft}\n          onSend={send}\n          onStop={stop}\n          streaming={streaming}\n        />\n      </SidebarInset>\n\n      <CommandMenu\n        onNew={(text) => {\n          setActiveId(null);\n          setDraft(text);\n        }}\n        onOpen={setActiveId}\n        onOpenChange={setSearch}\n        open={search}\n        threads={threads}\n      />\n    </SidebarProvider>\n  );\n}\n"
        },
        {
          "path": "content/components/chat/chat-03/components/chat-sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/components/chat-sidebar.tsx",
          "content": "'use client';\n\nimport {\n  IconArchive,\n  IconDots,\n  IconHelpCircle,\n  IconKeyboard,\n  IconLogout,\n  IconPencil,\n  IconPin,\n  IconPlus,\n  IconSearch,\n  IconSelector,\n  IconSettings,\n  IconShare,\n  IconSunMoon,\n  IconTrash,\n} from '@tabler/icons-react';\nimport { useState } from 'react';\nimport { Avatar, AvatarFallback, AvatarImage } from \"../shared/components/ui/avatar\";\nimport { Button } from \"../shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"../shared/components/ui/dropdown-menu\";\nimport { Kbd, KbdGroup } from \"../shared/components/ui/kbd\";\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"../shared/components/ui/sidebar\";\nimport { cn } from \"../shared/lib/utils\";\nimport type { Thread } from \"./conversation\";\n\nconst groups: { key: Thread['group'] | 'pinned'; label: string }[] = [\n  { key: 'pinned', label: 'Pinned' },\n  { key: 'today', label: 'Today' },\n  { key: 'yesterday', label: 'Yesterday' },\n  { key: 'week', label: 'Previous 7 days' },\n];\n\nconst item = 'h-8 gap-2.5 rounded-md px-2.5 text-[13px]';\n\ntype Props = {\n  user: {\n    name: string;\n    email: string;\n    plan: string;\n    renews: string;\n    avatar: string;\n  };\n  threads: Thread[];\n  activeId: string | null;\n  onSelect: (id: string) => void;\n  onNew: () => void;\n  onSearch: () => void;\n  onChange: (threads: Thread[]) => void;\n};\n\nexport function ChatSidebar({\n  user,\n  threads,\n  activeId,\n  onSelect,\n  onNew,\n  onSearch,\n  onChange,\n}: Props) {\n  const [renameId, setRenameId] = useState<string | null>(null);\n  const [name, setName] = useState('');\n\n  const commitRename = () => {\n    if (renameId && name.trim()) {\n      onChange(\n        threads.map((t) =>\n          t.id === renameId ? { ...t, title: name.trim() } : t\n        )\n      );\n    }\n    setRenameId(null);\n  };\n\n  return (\n    <Sidebar className=\"border-sidebar-border\" collapsible=\"offcanvas\">\n      <SidebarHeader className=\"gap-2 px-3 pt-3 pb-1\">\n        <Button className=\"h-8.5 rounded-lg text-[13px]\" onClick={onNew}>\n          <IconPlus className=\"size-3.5\" stroke={2.2} />\n          New chat\n        </Button>\n        <button\n          className=\"flex h-8 items-center gap-1.75 rounded-lg border border-border pr-1.5 pl-2.5 text-[13px] text-muted-foreground transition-colors hover:bg-muted/60 focus-visible:border-primary focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-primary/20\"\n          onClick={onSearch}\n          type=\"button\"\n        >\n          <IconSearch className=\"size-3.5\" stroke={2} />\n          <span className=\"grow text-left\">Search chats</span>\n          <Kbd className=\"h-4.5 border border-border bg-sidebar px-1.25 text-[10px]\">\n            ⌘K\n          </Kbd>\n        </button>\n      </SidebarHeader>\n\n      <SidebarContent className=\"gap-0 px-3 pt-2\">\n        {groups.map(({ key, label }) => {\n          const list = threads.filter((t) =>\n            key === 'pinned' ? t.pinned : !t.pinned && t.group === key\n          );\n          if (list.length === 0) {\n            return null;\n          }\n          return (\n            <SidebarGroup className=\"p-0 pb-2\" key={key}>\n              <SidebarGroupLabel className=\"h-5 px-2 font-semibold text-[10px] text-muted-foreground/80 uppercase tracking-[0.08em]\">\n                {label}\n              </SidebarGroupLabel>\n              <SidebarGroupContent>\n                <SidebarMenu className=\"gap-0.5\">\n                  {list.map((t) => (\n                    <SidebarMenuItem key={t.id}>\n                      {renameId === t.id ? (\n                        <input\n                          aria-label=\"Thread name\"\n                          autoFocus\n                          className=\"h-7 w-full rounded-md border border-primary bg-background px-2 text-[13px] outline-none ring-3 ring-primary/20\"\n                          onBlur={commitRename}\n                          onChange={(e) => setName(e.target.value)}\n                          onFocus={(e) => e.target.select()}\n                          onKeyDown={(e) => {\n                            if (e.key === 'Enter') {\n                              commitRename();\n                            }\n                            if (e.key === 'Escape') {\n                              setRenameId(null);\n                            }\n                          }}\n                          value={name}\n                        />\n                      ) : (\n                        <>\n                          <SidebarMenuButton\n                            className=\"h-7 px-2 text-[13px] data-active:bg-muted\"\n                            isActive={t.id === activeId}\n                            onClick={() => onSelect(t.id)}\n                          >\n                            <span>{t.title}</span>\n                          </SidebarMenuButton>\n                          <SidebarMenuBadge\n                            className={cn(\n                              'top-1! right-1.5 font-mono font-normal text-[11px] text-muted-foreground/80 group-focus-within/menu-item:opacity-0 group-hover/menu-item:opacity-0 group-has-aria-expanded/menu-item:opacity-0',\n                              t.pinned && 'right-1'\n                            )}\n                          >\n                            {t.pinned ? (\n                              <IconPin className=\"size-3\" stroke={1.8} />\n                            ) : (\n                              t.updated\n                            )}\n                          </SidebarMenuBadge>\n                          <DropdownMenu>\n                            <DropdownMenuTrigger\n                              render={\n                                <SidebarMenuAction\n                                  aria-label=\"Thread actions\"\n                                  className=\"top-1! text-muted-foreground\"\n                                  showOnHover\n                                />\n                              }\n                            >\n                              <IconDots className=\"size-3.5\" />\n                            </DropdownMenuTrigger>\n                            <DropdownMenuContent\n                              align=\"start\"\n                              className=\"w-48 rounded-xl p-1.5\"\n                              finalFocus={false}\n                              side=\"bottom\"\n                              sideOffset={6}\n                            >\n                              <DropdownMenuGroup className=\"flex flex-col gap-0.5\">\n                                <DropdownMenuItem\n                                  className={item}\n                                  onClick={() =>\n                                    onChange(\n                                      threads.map((x) =>\n                                        x.id === t.id\n                                          ? { ...x, pinned: !x.pinned }\n                                          : x\n                                      )\n                                    )\n                                  }\n                                >\n                                  <IconPin stroke={1.8} />\n                                  {t.pinned ? 'Unpin' : 'Pin'}\n                                </DropdownMenuItem>\n                                <DropdownMenuItem\n                                  className={item}\n                                  onClick={() => {\n                                    setRenameId(t.id);\n                                    setName(t.title);\n                                  }}\n                                >\n                                  <IconPencil stroke={1.8} />\n                                  Rename\n                                </DropdownMenuItem>\n                                <DropdownMenuItem className={item}>\n                                  <IconShare stroke={1.8} />\n                                  Share\n                                </DropdownMenuItem>\n                                <DropdownMenuItem\n                                  className={item}\n                                  onClick={() =>\n                                    onChange(\n                                      threads.filter((x) => x.id !== t.id)\n                                    )\n                                  }\n                                >\n                                  <IconArchive stroke={1.8} />\n                                  Archive\n                                </DropdownMenuItem>\n                              </DropdownMenuGroup>\n                              <DropdownMenuSeparator className=\"my-1\" />\n                              <DropdownMenuItem\n                                className={item}\n                                onClick={() =>\n                                  onChange(threads.filter((x) => x.id !== t.id))\n                                }\n                                variant=\"destructive\"\n                              >\n                                <IconTrash stroke={1.8} />\n                                Delete\n                              </DropdownMenuItem>\n                            </DropdownMenuContent>\n                          </DropdownMenu>\n                        </>\n                      )}\n                    </SidebarMenuItem>\n                  ))}\n                </SidebarMenu>\n              </SidebarGroupContent>\n            </SidebarGroup>\n          );\n        })}\n      </SidebarContent>\n\n      <SidebarFooter className=\"border-sidebar-border border-t p-0\">\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <button\n                className=\"flex h-14 w-full items-center gap-2.5 px-3 text-left outline-none transition-colors hover:bg-muted/60 focus-visible:bg-muted/60 aria-expanded:bg-muted/60\"\n                type=\"button\"\n              />\n            }\n          >\n            <Avatar className=\"size-7.5 rounded-lg\">\n              <AvatarImage\n                alt={user.name}\n                className=\"rounded-lg\"\n                src={user.avatar}\n              />\n              <AvatarFallback className=\"rounded-lg\">\n                {user.name[0]}\n              </AvatarFallback>\n            </Avatar>\n            <span className=\"flex min-w-0 grow flex-col gap-px\">\n              <span className=\"truncate font-medium text-[13px]\">\n                {user.name}\n              </span>\n              <span className=\"truncate text-[11px] text-muted-foreground\">\n                {user.plan} · {user.email}\n              </span>\n            </span>\n            <IconSelector\n              className=\"size-3.5 shrink-0 text-muted-foreground/80\"\n              stroke={2}\n            />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"center\"\n            className=\"w-[calc(var(--anchor-width)-1rem)] rounded-xl p-1.5\"\n            side=\"top\"\n            sideOffset={8}\n          >\n            <div className=\"flex items-center gap-2.5 px-2 pt-1.5 pb-2.5\">\n              <Avatar className=\"size-8 rounded-lg\">\n                <AvatarImage\n                  alt={user.name}\n                  className=\"rounded-lg\"\n                  src={user.avatar}\n                />\n                <AvatarFallback className=\"rounded-lg\">\n                  {user.name[0]}\n                </AvatarFallback>\n              </Avatar>\n              <div className=\"flex min-w-0 flex-col gap-px\">\n                <span className=\"font-medium text-[13px]\">{user.name}</span>\n                <span className=\"text-[11px] text-muted-foreground\">\n                  {user.email}\n                </span>\n              </div>\n            </div>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup className=\"flex flex-col gap-0.5 py-0.5\">\n              <DropdownMenuItem className={item}>\n                <IconSettings stroke={1.8} />\n                Settings\n                <KbdGroup className=\"ml-auto\">\n                  <Kbd>⌘</Kbd>\n                  <Kbd>,</Kbd>\n                </KbdGroup>\n              </DropdownMenuItem>\n              <DropdownMenuItem className={item}>\n                <IconSunMoon stroke={1.8} />\n                Appearance\n                <DropdownMenuShortcut className=\"text-xs tracking-normal\">\n                  System\n                </DropdownMenuShortcut>\n              </DropdownMenuItem>\n              <DropdownMenuItem className={item}>\n                <IconKeyboard stroke={1.8} />\n                Keyboard shortcuts\n                <KbdGroup className=\"ml-auto\">\n                  <Kbd>⌘</Kbd>\n                  <Kbd>/</Kbd>\n                </KbdGroup>\n              </DropdownMenuItem>\n              <DropdownMenuItem className={item}>\n                <IconHelpCircle stroke={1.8} />\n                Help & feedback\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n            <DropdownMenuSeparator />\n            <div className=\"my-0.5 flex items-center justify-between rounded-lg bg-muted/60 px-2.5 py-2\">\n              <div className=\"flex flex-col gap-px\">\n                <span className=\"font-medium text-xs\">{user.plan} plan</span>\n                <span className=\"text-[11px] text-muted-foreground\">\n                  Renews {user.renews}\n                </span>\n              </div>\n              <Button className=\"h-auto p-0 text-xs\" size=\"sm\" variant=\"link\">\n                Manage\n              </Button>\n            </div>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className={item}>\n              <IconLogout stroke={1.8} />\n              Log out\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarFooter>\n    </Sidebar>\n  );\n}\n"
        },
        {
          "path": "content/components/chat/chat-03/components/command-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/components/command-menu.tsx",
          "content": "'use client';\n\nimport { IconFileText, IconMessage, IconPlus } from '@tabler/icons-react';\nimport { useEffect, useState } from 'react';\nimport {\n  Command,\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandShortcut,\n} from \"../shared/components/ui/command\";\nimport { Kbd } from \"../shared/components/ui/kbd\";\nimport type { Thread } from \"./conversation\";\n\ntype Props = {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  threads: Thread[];\n  onOpen: (id: string) => void;\n  onNew: (draft: string) => void;\n};\n\nconst heading =\n  '**:[[cmdk-group-heading]]:px-2.5 **:[[cmdk-group-heading]]:font-semibold **:[[cmdk-group-heading]]:text-[10px] **:[[cmdk-group-heading]]:uppercase **:[[cmdk-group-heading]]:tracking-[0.08em] **:[[cmdk-group-heading]]:text-muted-foreground/80';\nconst row = 'h-10 gap-3 rounded-lg px-2.5 text-[13px]';\n\nfunction useCommandKey(onToggle: () => void) {\n  useEffect(() => {\n    const down = (e: KeyboardEvent) => {\n      if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {\n        e.preventDefault();\n        onToggle();\n      }\n    };\n    document.addEventListener('keydown', down);\n    return () => document.removeEventListener('keydown', down);\n  }, [onToggle]);\n}\n\nexport function CommandMenu({\n  open,\n  onOpenChange,\n  threads,\n  onOpen,\n  onNew,\n}: Props) {\n  const [query, setQuery] = useState('');\n  const q = query.trim().toLowerCase();\n  useCommandKey(() => onOpenChange(!open));\n\n  const messages = q\n    ? threads.flatMap((t) =>\n        t.turns\n          .filter(\n            (m) => m.role === 'assistant' && m.text.toLowerCase().includes(q)\n          )\n          .slice(0, 1)\n          .map((m) => {\n            const at = m.text.toLowerCase().indexOf(q);\n            const start = Math.max(0, at - 40);\n            return {\n              id: m.id,\n              threadId: t.id,\n              thread: t.title,\n              snippet: `${start > 0 ? '…' : ''}${m.text.slice(start, start + 90)}…`,\n            };\n          })\n      )\n    : [];\n\n  const pick = (fn: () => void) => {\n    fn();\n    onOpenChange(false);\n    setQuery('');\n  };\n\n  return (\n    <CommandDialog\n      className=\"max-w-140 rounded-[14px]!\"\n      description=\"Search chats and messages\"\n      onOpenChange={(v) => {\n        onOpenChange(v);\n        if (!v) {\n          setQuery('');\n        }\n      }}\n      open={open}\n      title=\"Search\"\n    >\n      <Command\n        className=\"p-0\"\n        filter={(value, search) =>\n          value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0\n        }\n      >\n        <CommandInput\n          onValueChange={setQuery}\n          placeholder=\"Search chats…\"\n          value={query}\n        />\n        <CommandList className=\"max-h-80 p-1\">\n          <CommandEmpty className=\"py-8 text-[13px] text-muted-foreground\">\n            No results for “{query}”\n          </CommandEmpty>\n          <CommandGroup className={heading} heading=\"Chats\">\n            {threads.map((t) => (\n              <CommandItem\n                className={row}\n                key={t.id}\n                onSelect={() => pick(() => onOpen(t.id))}\n                value={`${t.title} ${t.preview}`}\n              >\n                <IconMessage className=\"text-muted-foreground\" stroke={1.8} />\n                <span className=\"flex min-w-0 grow items-baseline gap-1.5\">\n                  <span className=\"shrink-0 font-medium\">{t.title}</span>\n                  <span className=\"truncate text-muted-foreground text-xs\">\n                    {t.preview}\n                  </span>\n                </span>\n                <CommandShortcut className=\"font-mono text-[11px] tracking-normal\">\n                  {t.updated}\n                </CommandShortcut>\n              </CommandItem>\n            ))}\n          </CommandGroup>\n          {messages.length > 0 && (\n            <CommandGroup className={heading} heading=\"Messages\">\n              {messages.map((m) => (\n                <CommandItem\n                  className={row}\n                  key={m.id}\n                  onSelect={() => pick(() => onOpen(m.threadId))}\n                  value={m.snippet}\n                >\n                  <IconFileText\n                    className=\"text-muted-foreground\"\n                    stroke={1.8}\n                  />\n                  <span className=\"grow truncate text-muted-foreground\">\n                    {m.snippet}\n                  </span>\n                  <CommandShortcut className=\"shrink-0 whitespace-nowrap text-[11px] tracking-normal\">\n                    {m.thread}\n                  </CommandShortcut>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          )}\n          <CommandGroup className={heading} forceMount heading=\"Actions\">\n            <CommandItem\n              className={row}\n              forceMount\n              onSelect={() => pick(() => onNew(query.trim()))}\n              value={`new chat ${query}`}\n            >\n              <IconPlus className=\"text-muted-foreground\" stroke={2} />\n              <span className=\"grow\">\n                {q ? `New chat about “${query.trim()}”` : 'New chat'}\n              </span>\n              <CommandShortcut className=\"font-mono text-[11px] tracking-normal\">\n                ⌘N\n              </CommandShortcut>\n            </CommandItem>\n          </CommandGroup>\n        </CommandList>\n        <div className=\"flex h-9 items-center gap-3.5 border-t bg-muted/40 px-3.5 text-[11px] text-muted-foreground\">\n          <span className=\"flex items-center gap-1.25\">\n            <Kbd className=\"h-4.5 bg-background px-1.25 font-mono text-[10px] ring-1 ring-border\">\n              ↑↓\n            </Kbd>\n            Navigate\n          </span>\n          <span className=\"flex items-center gap-1.25\">\n            <Kbd className=\"h-4.5 bg-background px-1.25 font-mono text-[10px] ring-1 ring-border\">\n              ↵\n            </Kbd>\n            Open\n          </span>\n        </div>\n      </Command>\n    </CommandDialog>\n  );\n}\n"
        },
        {
          "path": "content/components/chat/chat-03/components/composer.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/components/composer.tsx",
          "content": "'use client';\n\nimport {\n  IconArrowUp,\n  IconMicrophone,\n  IconPaperclip,\n  IconPhoto,\n  IconPlayerStopFilled,\n  IconPlus,\n  IconWorld,\n  IconX,\n} from '@tabler/icons-react';\nimport { useState } from 'react';\nimport { Button } from \"../shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"../shared/components/ui/dropdown-menu\";\nimport { Textarea } from \"../shared/components/ui/textarea\";\nimport { cn } from \"../shared/lib/utils\";\n\nexport type Attachment = { name: string; meta: string; kind: string };\n\nconst easeOut = 'ease-[cubic-bezier(0.23,1,0.32,1)]';\nconst press = `${easeOut} transition-[scale,background-color] duration-150 active:scale-[0.96] motion-reduce:active:scale-100`;\nconst swap = `${easeOut} fade-in zoom-in-95 motion-reduce:zoom-in-100 animate-in duration-150`;\nconst round = 'size-7.5 rounded-full';\n\ntype Props = {\n  draft: string;\n  onDraft: (v: string) => void;\n  attachment: Attachment | null;\n  onAttachment: (a: Attachment | null) => void;\n  streaming: boolean;\n  onSend: () => void;\n  onStop: () => void;\n};\n\nexport function Composer({\n  draft,\n  onDraft,\n  attachment,\n  onAttachment,\n  streaming,\n  onSend,\n  onStop,\n}: Props) {\n  const [search, setSearch] = useState(false);\n  const [listening, setListening] = useState(false);\n  const canSend = draft.trim().length > 0 && !streaming;\n\n  return (\n    <form\n      className=\"mx-auto flex w-full max-w-160 flex-col px-4 pt-3 pb-5\"\n      onSubmit={(e) => {\n        e.preventDefault();\n        if (canSend) {\n          onSend();\n        }\n      }}\n    >\n      <div\n        className={cn(\n          easeOut,\n          'flex flex-col gap-2.5 rounded-3xl border border-border bg-background p-3 shadow-[0_1px_2px_oklch(0_0_0/0.04),0_8px_24px_oklch(0_0_0/0.05)] transition-[box-shadow,border-color] duration-150 focus-within:border-primary focus-within:ring-4 focus-within:ring-primary/12'\n        )}\n      >\n        {attachment && (\n          <div className={cn(swap, 'flex')}>\n            <div className=\"flex h-12 items-center gap-2.5 rounded-xl border border-border bg-muted/50 pr-3 pl-2\">\n              <span className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-destructive/10 font-mono font-semibold text-[9px] text-destructive tracking-[0.04em]\">\n                {attachment.kind}\n              </span>\n              <span className=\"flex flex-col gap-1\">\n                <span className=\"font-medium text-[13px]/4\">\n                  {attachment.name}\n                </span>\n                <span className=\"text-[11px]/3.5 text-muted-foreground\">\n                  {attachment.meta}\n                </span>\n              </span>\n              <button\n                aria-label=\"Remove attachment\"\n                className=\"rounded-md p-0.5 text-muted-foreground transition-colors hover:text-foreground\"\n                onClick={() => onAttachment(null)}\n                type=\"button\"\n              >\n                <IconX className=\"size-3.5\" stroke={2} />\n              </button>\n            </div>\n          </div>\n        )}\n        <Textarea\n          aria-label=\"Message\"\n          className=\"field-sizing-content max-h-48 min-h-0 resize-none rounded-none border-0 bg-transparent px-1.5 py-0.5 text-sm/5 shadow-none focus-visible:ring-0 md:text-sm/5 dark:bg-transparent\"\n          onChange={(e) => onDraft(e.target.value)}\n          onKeyDown={(e) => {\n            if (e.key === 'Enter' && !e.shiftKey) {\n              e.preventDefault();\n              e.currentTarget.form?.requestSubmit();\n            }\n          }}\n          placeholder={listening ? 'Listening…' : 'Ask anything'}\n          rows={1}\n          value={draft}\n        />\n        <div className=\"flex items-center gap-2\">\n          <DropdownMenu>\n            <DropdownMenuTrigger\n              render={\n                <Button\n                  aria-label=\"Add attachment\"\n                  className={cn(press, round)}\n                  size=\"icon-sm\"\n                  variant=\"outline\"\n                />\n              }\n            >\n              <IconPlus stroke={2} />\n            </DropdownMenuTrigger>\n            <DropdownMenuContent\n              align=\"start\"\n              className=\"w-52 rounded-xl p-1.5\"\n              side=\"top\"\n              sideOffset={10}\n            >\n              <DropdownMenuGroup className=\"flex flex-col gap-0.5\">\n                <DropdownMenuItem\n                  className=\"h-8 gap-2.5 rounded-md px-2.5 text-[13px]\"\n                  onClick={() =>\n                    onAttachment({\n                      name: 'installer-quote.pdf',\n                      meta: '2 pages · 412 KB',\n                      kind: 'PDF',\n                    })\n                  }\n                >\n                  <IconPaperclip stroke={1.8} />\n                  Add files\n                </DropdownMenuItem>\n                <DropdownMenuItem\n                  className=\"h-8 gap-2.5 rounded-md px-2.5 text-[13px]\"\n                  onClick={() =>\n                    onAttachment({\n                      name: 'boiler-room.jpg',\n                      meta: '3024 × 4032 · 2.1 MB',\n                      kind: 'JPG',\n                    })\n                  }\n                >\n                  <IconPhoto stroke={1.8} />\n                  Add photos\n                </DropdownMenuItem>\n              </DropdownMenuGroup>\n            </DropdownMenuContent>\n          </DropdownMenu>\n          <Button\n            aria-pressed={search}\n            className={cn(\n              press,\n              'h-7.5 rounded-full px-2.75 text-xs',\n              search &&\n                'border-primary/30 bg-primary/8 text-primary hover:bg-primary/12'\n            )}\n            onClick={() => setSearch(!search)}\n            size=\"sm\"\n            variant=\"outline\"\n          >\n            <IconWorld stroke={2} />\n            Search\n          </Button>\n          <div className=\"grow\" />\n          <Button\n            aria-label={listening ? 'Stop listening' : 'Voice input'}\n            aria-pressed={listening}\n            className={cn(\n              press,\n              round,\n              listening &&\n                'bg-destructive/15 text-destructive hover:bg-destructive/25'\n            )}\n            onClick={() => setListening(!listening)}\n            size=\"icon-sm\"\n            variant=\"ghost\"\n          >\n            {listening ? (\n              <IconPlayerStopFilled className=\"animate-pulse motion-reduce:animate-none\" />\n            ) : (\n              <IconMicrophone stroke={2} />\n            )}\n          </Button>\n          {streaming ? (\n            <Button\n              aria-label=\"Stop generating\"\n              className={cn(swap, press, round)}\n              onClick={onStop}\n              size=\"icon-sm\"\n            >\n              <IconPlayerStopFilled className=\"size-3\" />\n            </Button>\n          ) : (\n            <Button\n              aria-label=\"Send\"\n              className={cn(\n                swap,\n                press,\n                round,\n                !canSend &&\n                  'bg-muted text-muted-foreground/70 hover:bg-muted disabled:opacity-100'\n              )}\n              disabled={!canSend}\n              size=\"icon-sm\"\n              type=\"submit\"\n            >\n              <IconArrowUp stroke={2.2} />\n            </Button>\n          )}\n        </div>\n      </div>\n    </form>\n  );\n}\n"
        },
        {
          "path": "content/components/chat/chat-03/components/conversation.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/components/conversation.tsx",
          "content": "'use client';\n\nimport {\n  IconAlertCircle,\n  IconChevronDown,\n  IconChevronRight,\n  IconCopy,\n  IconPencil,\n  IconRefresh,\n  IconThumbDown,\n  IconThumbUp,\n} from '@tabler/icons-react';\nimport React, { Fragment } from 'react';\nimport { Button } from \"../shared/components/ui/button\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"../shared/components/ui/collapsible\";\nimport {\n  MessageScroller,\n  MessageScrollerButton,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerProvider,\n  MessageScrollerViewport,\n} from \"../shared/components/ui/message-scroller\";\nimport { cn } from \"../shared/lib/utils\";\n\nexport type Turn = {\n  id: string;\n  role: 'user' | 'assistant';\n  text: string;\n  reasoning?: string;\n  thought?: number;\n  streaming?: boolean;\n  error?: boolean;\n  day?: Thread['group'];\n};\n\nexport type Thread = {\n  id: string;\n  title: string;\n  preview: string;\n  updated: string;\n  group: 'today' | 'yesterday' | 'week';\n  pinned?: boolean;\n  turns: Turn[];\n};\n\nconst easeOut = 'ease-[cubic-bezier(0.23,1,0.32,1)]';\nconst press = `${easeOut} transition-[scale,background-color] duration-150 active:scale-[0.96] motion-reduce:active:scale-100`;\nconst enter = `${easeOut} motion-reduce:slide-in-from-bottom-0 fade-in slide-in-from-bottom-1 animate-in duration-200`;\n\ntype Props = {\n  thread: Thread | null;\n  recent: Thread[];\n  altModel: string;\n  onOpen: (id: string) => void;\n  onEdit: (turn: Turn) => void;\n  onRetry: (turnId: string, model?: string) => void;\n};\n\nexport function Conversation({\n  thread,\n  recent,\n  altModel,\n  onOpen,\n  onEdit,\n  onRetry,\n}: Props) {\n  if (!thread) {\n    return (\n      <div className=\"flex grow items-center justify-center px-8\">\n        <div className={cn(enter, 'flex w-full max-w-160 flex-col gap-10')}>\n          <div className=\"flex flex-col gap-1.5\">\n            <h1 className=\"font-semibold text-[34px]/10 tracking-[-0.025em]\">\n              Where were we?\n            </h1>\n            <p className=\"text-[15px]/5.5 text-muted-foreground\">\n              Nothing here yet. Ask anything, or pick up a thread.\n            </p>\n          </div>\n          <div className=\"flex flex-col\">\n            <p className=\"pb-1 font-semibold text-[10px] text-muted-foreground/80 uppercase tracking-[0.08em]\">\n              Continue\n            </p>\n            {recent.map((t) => (\n              <button\n                className=\"-mx-2 flex h-13 items-center gap-3.5 rounded-lg not-last:border-muted not-last:border-b px-2 text-left transition-colors hover:bg-muted/60\"\n                key={t.id}\n                onClick={() => onOpen(t.id)}\n                type=\"button\"\n              >\n                <span className=\"flex grow flex-col gap-0.5\">\n                  <span className=\"font-medium text-sm/4.5\">{t.title}</span>\n                  <span className=\"text-muted-foreground text-xs\">\n                    {t.preview}\n                  </span>\n                </span>\n                <span className=\"font-mono text-[11px] text-muted-foreground/80\">\n                  {t.updated}\n                </span>\n              </button>\n            ))}\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  const action = (\n    label: string,\n    icon: React.ReactNode,\n    onClick?: () => void\n  ) => (\n    <Button\n      aria-label={label}\n      className={cn(press, 'size-6.5 rounded-lg text-muted-foreground')}\n      onClick={onClick}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n    >\n      {icon}\n    </Button>\n  );\n\n  return (\n    <MessageScrollerProvider autoScroll>\n      <MessageScroller className=\"grow\">\n        <MessageScrollerViewport>\n          <MessageScrollerContent className=\"mx-auto w-full max-w-160 gap-4.5 px-4 pt-6 pb-4\">\n            {thread.turns.map((turn, i) => {\n              const day = turn.day ?? thread.group;\n              const prev = thread.turns[i - 1];\n              const divider = !prev || (prev.day ?? thread.group) !== day;\n              return (\n                <Fragment key={turn.id}>\n                  {divider && (\n                    <div className=\"flex items-center gap-3\">\n                      <div className=\"h-px grow bg-muted\" />\n                      <span className=\"font-medium text-[11px] text-muted-foreground/80\">\n                        {\n                          {\n                            today: 'Today',\n                            yesterday: 'Yesterday',\n                            week: 'This week',\n                          }[day]\n                        }\n                      </span>\n                      <div className=\"h-px grow bg-muted\" />\n                    </div>\n                  )}\n                  {turn.role === 'user' ? (\n                    <MessageScrollerItem\n                      className=\"group/turn flex flex-row-reverse items-center gap-2\"\n                      key={turn.id}\n                      messageId={turn.id}\n                    >\n                      <div className=\"max-w-105 rounded-[20px] bg-muted px-4 py-2.5 text-[15px]/5.5\">\n                        {turn.text}\n                      </div>\n                      <div className=\"flex shrink-0 items-center gap-0.5 opacity-0 transition-opacity group-focus-within/turn:opacity-100 group-hover/turn:opacity-100\">\n                        {action('Edit', <IconPencil stroke={1.8} />, () =>\n                          onEdit(turn)\n                        )}\n                        {action('Copy', <IconCopy stroke={1.8} />, () =>\n                          navigator.clipboard.writeText(turn.text)\n                        )}\n                      </div>\n                    </MessageScrollerItem>\n                  ) : (\n                    <MessageScrollerItem\n                      className={cn(\n                        'flex flex-col gap-2.5',\n                        turn.streaming && enter\n                      )}\n                      key={turn.id}\n                      messageId={turn.id}\n                    >\n                      {turn.error ? (\n                        <>\n                          <div className=\"flex items-start gap-3 rounded-xl bg-destructive/5 px-4 py-3.5 ring-1 ring-destructive/15\">\n                            <IconAlertCircle\n                              className=\"mt-px size-4 shrink-0 text-destructive\"\n                              stroke={2}\n                            />\n                            <div className=\"flex flex-col gap-1\">\n                              <p className=\"font-semibold text-destructive text-sm/5\">\n                                The response was interrupted\n                              </p>\n                              <p className=\"text-[13px]/5 text-destructive/80\">\n                                The model stopped before finishing. Your message\n                                was not lost. Retry with the same model or\n                                switch to a different one.\n                              </p>\n                            </div>\n                          </div>\n                          <div className=\"flex items-center gap-2 pt-0.5\">\n                            <Button\n                              className={cn(press, 'text-[13px]')}\n                              onClick={() => onRetry(turn.id)}\n                            >\n                              <IconRefresh className=\"size-3.5\" stroke={2} />\n                              Retry\n                            </Button>\n                            <Button\n                              className={cn(press, 'text-[13px]')}\n                              onClick={() => onRetry(turn.id, altModel)}\n                              variant=\"outline\"\n                            >\n                              Retry with {altModel}\n                            </Button>\n                          </div>\n                        </>\n                      ) : (\n                        <>\n                          <Collapsible className=\"flex flex-col gap-2\">\n                            <CollapsibleTrigger\n                              className={cn(\n                                'group/thought flex w-fit items-center gap-2 rounded-md font-medium text-[13px] text-muted-foreground outline-none focus-visible:ring-3 focus-visible:ring-ring/50',\n                                turn.streaming &&\n                                  !turn.text &&\n                                  'shimmer text-primary'\n                              )}\n                            >\n                              <IconChevronRight\n                                className=\"size-3.5 transition-transform group-data-[panel-open]/thought:rotate-90\"\n                                stroke={2}\n                              />\n                              {turn.thought\n                                ? `Thought for ${turn.thought}s`\n                                : 'Thinking…'}\n                            </CollapsibleTrigger>\n                            <CollapsibleContent className=\"border-muted border-l-2 pl-3.5 text-[13px]/5 text-muted-foreground\">\n                              {turn.reasoning}\n                            </CollapsibleContent>\n                          </Collapsible>\n                          {turn.text && (\n                            <p className=\"text-[15px]/6\">{turn.text}</p>\n                          )}\n                          {!turn.streaming && (\n                            <div className=\"-ml-1.5 flex items-center gap-0.5\">\n                              {action('Copy', <IconCopy stroke={1.8} />, () =>\n                                navigator.clipboard.writeText(turn.text)\n                              )}\n                              {action(\n                                'Good response',\n                                <IconThumbUp stroke={1.8} />\n                              )}\n                              {action(\n                                'Bad response',\n                                <IconThumbDown stroke={1.8} />\n                              )}\n                              {action(\n                                'Regenerate',\n                                <IconRefresh stroke={1.8} />,\n                                () => onRetry(turn.id)\n                              )}\n                            </div>\n                          )}\n                        </>\n                      )}\n                    </MessageScrollerItem>\n                  )}\n                </Fragment>\n              );\n            })}\n          </MessageScrollerContent>\n        </MessageScrollerViewport>\n        <MessageScrollerButton\n          className={cn(\n            easeOut,\n            'bottom-2 h-8 gap-1.5 rounded-full border-border bg-background px-3.5 font-medium text-[13px] shadow-xs transition-[translate,scale,opacity,background-color]'\n          )}\n          size=\"sm\"\n          variant=\"outline\"\n        >\n          <IconChevronDown className=\"size-3.5\" stroke={2} />\n          Jump to latest\n        </MessageScrollerButton>\n      </MessageScroller>\n    </MessageScrollerProvider>\n  );\n}\n"
        },
        {
          "path": "content/components/chat/chat-03/components/logos.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/components/logos.tsx",
          "content": "import type { ComponentProps } from 'react';\n\nexport function OpenAILogo(props: ComponentProps<'svg'>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      viewBox=\"0 0 24 24\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365 2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z\" />\n    </svg>\n  );\n}\n\nexport function ClaudeLogo(props: ComponentProps<'svg'>) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      fill=\"currentColor\"\n      viewBox=\"0 0 24 24\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      {...props}\n    >\n      <path d=\"m4.71 15.96 4.71-2.64.08-.23-.08-.13h-.23l-.79-.05-2.69-.07-2.33-.1-2.26-.12-.57-.12L0 11.8l.06-.38.48-.32.68.06 1.51.1 2.27.16 1.65.1 2.44.25h.39l.05-.16-.13-.1-.1-.1-2.35-1.59-2.54-1.68-1.33-.97-.72-.49-.36-.46-.16-1.01.66-.72.88.06.23.06.89.69 1.91 1.48 2.49 1.83.37.3.15-.1.02-.07-.17-.27-1.36-2.46-1.45-2.5-.65-1.04-.17-.62a2.97 2.97 0 0 1-.1-.73L5.05.76l.42-.14.99.14.42.36.62 1.41 1 2.22 1.55 3.02.45.9.24.83.09.25h.16v-.15l.13-1.7.23-2.1.23-2.7.08-.76.37-.9.75-.5.58.28.48.69-.07.44-.29 1.85-.56 2.9-.36 1.94h.21l.25-.24 1-1.33 1.68-2.1.74-.84.87-.92.56-.44h1.05l.78 1.16-.35 1.19-1.09 1.38-.9 1.17-1.3 1.74-.8 1.4.07.11.19-.02 2.94-.63 1.59-.29 1.9-.32.85.4.1.4-.34.83-2.03.5-2.38.48-3.54.84-.05.03.05.06 1.6.15.68.04h1.67l3.12.23.81.54.49.66-.08.5-1.25.64-1.7-.4-3.95-.94-1.35-.34h-.19v.11l1.13 1.1 2.07 1.87 2.59 2.4.13.6-.33.47-.35-.05-2.28-1.72-.88-.77-1.99-1.68h-.13v.18l.46.67 2.42 3.64.13 1.12-.18.36-.63.22-.69-.13-1.42-1.99-1.47-2.25-1.18-2.01-.14.08-.7 7.51-.33.38-.75.29-.63-.48-.34-.78.34-1.53.4-2 .33-1.6.3-1.97.18-.66-.01-.04-.15.02-1.5 2.06-2.28 3.09-1.81 1.93-.43.17-.75-.39.07-.69.42-.62 2.49-3.17 1.5-1.97.97-1.14-.01-.16h-.06L2.4 18.5l-1.17.15-.5-.47.06-.77.24-.25 1.97-1.36z\" />\n    </svg>\n  );\n}\n"
        },
        {
          "path": "licenses/chat-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/chat-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/collapsible.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/collapsible.tsx",
          "content": "'use client';\n\nimport { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible';\n\nfunction Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {\n  return (\n    <CollapsiblePrimitive.Trigger data-slot=\"collapsible-trigger\" {...props} />\n  );\n}\n\nfunction CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {\n  return (\n    <CollapsiblePrimitive.Panel data-slot=\"collapsible-content\" {...props} />\n  );\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
        },
        {
          "path": "components/ui/command.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/command.tsx",
          "content": "'use client';\n\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { CheckIcon, SearchIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"./dialog\";\nimport { InputGroup, InputGroupAddon } from \"./input-group\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      className={cn(\n        'flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground',\n        className\n      )}\n      data-slot=\"command\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandDialog({\n  title = 'Command Palette',\n  description = 'Search for a command to run...',\n  children,\n  className,\n  showCloseButton = false,\n  ...props\n}: Omit<React.ComponentProps<typeof Dialog>, 'children'> & {\n  title?: string;\n  description?: string;\n  className?: string;\n  showCloseButton?: boolean;\n  children: React.ReactNode;\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\n          'top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0',\n          className\n        )}\n        showCloseButton={showCloseButton}\n      >\n        {children}\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div className=\"p-1 pb-0\" data-slot=\"command-input-wrapper\">\n      <InputGroup className=\"h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!\">\n        <CommandPrimitive.Input\n          className={cn(\n            'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n            className\n          )}\n          data-slot=\"command-input\"\n          {...props}\n        />\n        <InputGroupAddon>\n          <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n        </InputGroupAddon>\n      </InputGroup>\n    </div>\n  );\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      className={cn(\n        'no-scrollbar max-h-72 scroll-py-1 overflow-y-auto overflow-x-hidden outline-none',\n        className\n      )}\n      data-slot=\"command-list\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandEmpty({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      className={cn('py-6 text-center text-sm', className)}\n      data-slot=\"command-empty\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      className={cn(\n        'overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group-heading]]:text-xs',\n        className\n      )}\n      data-slot=\"command-group\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      className={cn('-mx-1 h-px bg-border', className)}\n      data-slot=\"command-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      className={cn(\n        \"group/command-item relative flex cursor-default select-none items-center gap-2 in-data-[slot=dialog-content]:rounded-lg! rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-selected:bg-muted data-selected:text-foreground data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-selected:*:[svg]:text-foreground\",\n        className\n      )}\n      data-slot=\"command-item\"\n      {...props}\n    >\n      {children}\n      <CheckIcon className=\"ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100\" />\n    </CommandPrimitive.Item>\n  );\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-data-selected/command-item:text-foreground',\n        className\n      )}\n      data-slot=\"command-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n};\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/kbd.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/kbd.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {\n  return (\n    <kbd\n      className={cn(\n        \"pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm bg-muted in-data-[slot=tooltip-content]:bg-background/20 px-1 font-medium font-sans in-data-[slot=tooltip-content]:text-background text-muted-foreground text-xs dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3\",\n        className\n      )}\n      data-slot=\"kbd\"\n      {...props}\n    />\n  );\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <kbd\n      className={cn('inline-flex items-center gap-1', className)}\n      data-slot=\"kbd-group\"\n      {...props}\n    />\n  );\n}\n\nexport { Kbd, KbdGroup };\n"
        },
        {
          "path": "components/ui/message-scroller.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/message-scroller.tsx",
          "content": "'use client';\n\nimport {\n  MessageScroller as MessageScrollerPrimitive,\n  useMessageScroller,\n  useMessageScrollerScrollable,\n  useMessageScrollerVisibility,\n} from '@shadcn/react/message-scroller';\nimport { ArrowDownIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction MessageScrollerProvider(\n  props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>\n) {\n  return <MessageScrollerPrimitive.Provider {...props} />;\n}\n\nfunction MessageScroller({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) {\n  return (\n    <MessageScrollerPrimitive.Root\n      className={cn(\n        'group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden',\n        className\n      )}\n      data-slot=\"message-scroller\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerViewport({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) {\n  return (\n    <MessageScrollerPrimitive.Viewport\n      className={cn(\n        'scroll-fade-b scrollbar-thin scrollbar-gutter-stable data-autoscrolling:scrollbar-thumb-transparent data-autoscrolling:scrollbar-track-transparent size-full min-h-0 min-w-0 overflow-y-auto overscroll-contain contain-content data-pending-scroll:invisible',\n        className\n      )}\n      data-slot=\"message-scroller-viewport\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerContent({\n  className,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) {\n  return (\n    <MessageScrollerPrimitive.Content\n      className={cn('flex h-max min-h-full flex-col gap-6', className)}\n      data-slot=\"message-scroller-content\"\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerItem({\n  className,\n  scrollAnchor = false,\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) {\n  return (\n    <MessageScrollerPrimitive.Item\n      className={cn(\n        'min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]',\n        className\n      )}\n      data-slot=\"message-scroller-item\"\n      scrollAnchor={scrollAnchor}\n      {...props}\n    />\n  );\n}\n\nfunction MessageScrollerButton({\n  direction = 'end',\n  className,\n  children,\n  render,\n  variant = 'secondary',\n  size = 'icon-sm',\n  ...props\n}: React.ComponentProps<typeof MessageScrollerPrimitive.Button> &\n  Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {\n  return (\n    <MessageScrollerPrimitive.Button\n      className={cn(\n        '-translate-x-1/2 data-[direction=start]:data-[active=false]:-translate-y-full absolute inset-s-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[direction=end]:data-[active=false]:translate-y-full data-[active=false]:pointer-events-none data-[direction=start]:top-4 data-[direction=end]:bottom-4 data-[active=true]:translate-y-0 data-[active=false]:scale-95 data-[active=true]:scale-100 data-[active=false]:opacity-0 data-[active=true]:opacity-100 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180',\n        className\n      )}\n      data-direction={direction}\n      data-size={size}\n      data-slot=\"message-scroller-button\"\n      data-variant={variant}\n      direction={direction}\n      render={render ?? <Button size={size} variant={variant} />}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <ArrowDownIcon />\n          <span className=\"sr-only\">\n            {direction === 'end' ? 'Scroll to end' : 'Scroll to start'}\n          </span>\n        </>\n      )}\n    </MessageScrollerPrimitive.Button>\n  );\n}\n\nexport {\n  MessageScrollerProvider,\n  MessageScroller,\n  MessageScrollerViewport,\n  MessageScrollerContent,\n  MessageScrollerItem,\n  MessageScrollerButton,\n  useMessageScroller,\n  useMessageScrollerScrollable,\n  useMessageScrollerVisibility,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/sheet.tsx",
          "content": "'use client';\n\nimport { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"sheet-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        className={cn(\n          'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sheet-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                className=\"absolute top-3 right-3\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 p-4', className)}\n      data-slot=\"sheet-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      data-slot=\"sheet-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      className={cn('font-medium text-base text-foreground', className)}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/sidebar.tsx",
          "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport { cn } from \"../../lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className\n        )}\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className\n        )}\n        data-slot=\"sidebar\"\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>\n        <SheetContent\n          className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n          data-mobile=\"true\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          dir={dir}\n          side={side}\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-side={side}\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-variant={variant}\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n        data-slot=\"sidebar-gap\"\n      />\n      <div\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sidebar-container\"\n        {...props}\n      >\n        <div\n          className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      className={cn(className)}\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      className={cn(\n        'group-data-[side=left]:-right-4 ltr:-translate-x-1/2 rtl:-translate-x-1/2 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      className={cn(\n        'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',\n        className\n      )}\n      data-slot=\"sidebar-inset\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      className={cn('h-8 w-full bg-background shadow-none', className)}\n      data-sidebar=\"input\"\n      data-slot=\"sidebar-input\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"header\"\n      data-slot=\"sidebar-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"footer\"\n      data-slot=\"sidebar-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      data-sidebar=\"separator\"\n      data-slot=\"sidebar-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      data-sidebar=\"content\"\n      data-slot=\"sidebar-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      data-sidebar=\"group\"\n      data-slot=\"sidebar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'group-data-[collapsible=icon]:-mt-8 flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label',\n    },\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action',\n    },\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('w-full text-sm', className)}\n      data-sidebar=\"group-content\"\n      data-slot=\"sidebar-group-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn('flex w-full min-w-0 flex-col gap-0', className)}\n      data-sidebar=\"menu\"\n      data-slot=\"sidebar-menu\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-item relative', className)}\n      data-sidebar=\"menu-item\"\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n      },\n      props\n    ),\n    render: tooltip ? <TooltipTrigger render={render} /> : render,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive,\n    },\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        side=\"right\"\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          showOnHover &&\n            'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action',\n    },\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',\n        className\n      )}\n      data-sidebar=\"menu-badge\"\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      data-sidebar=\"menu-skeleton\"\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      data-sidebar=\"menu-sub\"\n      data-slot=\"sidebar-menu-sub\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-sub-item relative', className)}\n      data-sidebar=\"menu-sub-item\"\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          '-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive,\n    },\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/skeleton.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('animate-pulse rounded-md bg-muted', className)}\n      data-slot=\"skeleton\"\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/hooks/use-mobile.ts",
          "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/chat-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "chat",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/chat-03/app/chat/page.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/chat/chat-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "chat",
          "ai",
          "conversation",
          "messages",
          "聊天",
          "对话",
          "消息",
          "助手"
        ],
        "contentGuidelines": [
          "Connect message streaming and persistence to your application.",
          "Test empty history, long responses, and failed requests."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/chat-03/app/chat/page.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/chat-03"
      }
    },
    {
      "name": "command-menu-01",
      "type": "registry:block",
      "title": "Command Menu with Groups",
      "description": "Command Menu with Groups. Keyboard-friendly command palettes for navigation, search, and quick actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cmdk@1.1.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/command-menu/command-menu-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/command-menu-01.tsx",
          "content": "'use client';\n\nimport {\n  IconBell,\n  IconBolt,\n  IconCalendar,\n  IconChartBar,\n  IconChartPie,\n  IconClock,\n  IconFileText,\n  IconHelp,\n  IconKeyboard,\n  IconLayoutDashboard,\n  IconLayoutKanban,\n  IconLogout,\n  IconMessage,\n  IconPalette,\n  IconSettings,\n  IconSquareCheck,\n  IconTarget,\n  IconTrendingUp,\n  IconUsers,\n} from '@tabler/icons-react';\nimport { useEffect, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Command,\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"./shared/components/ui/command\";\nimport { Kbd } from \"./shared/components/ui/kbd\";\n\nconst workspaceItems = [\n  { icon: IconLayoutDashboard, label: 'Dashboard' },\n  { icon: IconLayoutKanban, label: 'Projects' },\n  { icon: IconSquareCheck, label: 'Tasks' },\n  { icon: IconCalendar, label: 'Calendar' },\n  { icon: IconUsers, label: 'Team members' },\n  { icon: IconMessage, label: 'Messages' },\n  { icon: IconFileText, label: 'Documents' },\n  { icon: IconBell, label: 'Notifications' },\n  { icon: IconClock, label: 'Time tracking' },\n  { icon: IconTarget, label: 'Goals' },\n];\n\nconst analyticsItems = [\n  { icon: IconChartBar, label: 'Overview' },\n  { icon: IconTrendingUp, label: 'Performance' },\n  { icon: IconChartPie, label: 'Reports' },\n  { icon: IconBolt, label: 'Insights' },\n];\n\nconst settingsItems = [\n  { icon: IconSettings, label: 'Preferences' },\n  { icon: IconPalette, label: 'Appearance' },\n  { icon: IconKeyboard, label: 'Keyboard shortcuts' },\n  { icon: IconHelp, label: 'Help & support' },\n  { icon: IconLogout, label: 'Sign out' },\n];\n\nexport function CommandMenu01() {\n  const [open, setOpen] = useState(true);\n\n  useEffect(() => {\n    const down = (e: KeyboardEvent) => {\n      if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {\n        e.preventDefault();\n        setOpen((prev) => !prev);\n      }\n    };\n\n    document.addEventListener('keydown', down);\n    return () => document.removeEventListener('keydown', down);\n  }, []);\n\n  return (\n    <>\n      <Button onClick={() => setOpen(true)} variant=\"outline\">\n        Open Command Menu\n      </Button>\n\n      <CommandDialog\n        className=\"-translate-y-1/2 top-1/2\"\n        onOpenChange={setOpen}\n        open={open}\n        showCloseButton={false}\n      >\n        <Command>\n          <CommandInput\n            className=\"h-12\"\n            placeholder=\"Type a command or search...\"\n          />\n          <CommandList className=\"h-[320px] max-h-[320px]\">\n            <CommandEmpty>No results found.</CommandEmpty>\n            <CommandGroup heading=\"Workspace\">\n              {workspaceItems.map((item) => (\n                <CommandItem key={item.label}>\n                  <item.icon className=\"mr-2 h-5 w-5\" />\n                  <span>{item.label}</span>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n            <CommandGroup heading=\"Analytics\">\n              {analyticsItems.map((item) => (\n                <CommandItem key={item.label}>\n                  <item.icon className=\"mr-2 h-5 w-5\" />\n                  <span>{item.label}</span>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n            <CommandGroup heading=\"Settings\">\n              {settingsItems.map((item) => (\n                <CommandItem key={item.label}>\n                  <item.icon className=\"mr-2 h-5 w-5\" />\n                  <span>{item.label}</span>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </CommandList>\n          <div className=\"flex h-12 items-center justify-end border-t px-3\">\n            <button\n              className=\"flex items-center gap-1 text-muted-foreground text-sm hover:text-foreground\"\n              onClick={() => setOpen(false)}\n              type=\"button\"\n            >\n              <span>Close</span>\n              <Kbd className=\"ml-1\">Esc</Kbd>\n            </button>\n          </div>\n        </Command>\n      </CommandDialog>\n    </>\n  );\n}\n"
        },
        {
          "path": "licenses/command-menu-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/command-menu-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/command.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/shared/components/ui/command.tsx",
          "content": "'use client';\n\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { CheckIcon, SearchIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"./dialog\";\nimport { InputGroup, InputGroupAddon } from \"./input-group\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      className={cn(\n        'flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground',\n        className\n      )}\n      data-slot=\"command\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandDialog({\n  title = 'Command Palette',\n  description = 'Search for a command to run...',\n  children,\n  className,\n  showCloseButton = false,\n  ...props\n}: Omit<React.ComponentProps<typeof Dialog>, 'children'> & {\n  title?: string;\n  description?: string;\n  className?: string;\n  showCloseButton?: boolean;\n  children: React.ReactNode;\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\n          'top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0',\n          className\n        )}\n        showCloseButton={showCloseButton}\n      >\n        {children}\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div className=\"p-1 pb-0\" data-slot=\"command-input-wrapper\">\n      <InputGroup className=\"h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!\">\n        <CommandPrimitive.Input\n          className={cn(\n            'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n            className\n          )}\n          data-slot=\"command-input\"\n          {...props}\n        />\n        <InputGroupAddon>\n          <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n        </InputGroupAddon>\n      </InputGroup>\n    </div>\n  );\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      className={cn(\n        'no-scrollbar max-h-72 scroll-py-1 overflow-y-auto overflow-x-hidden outline-none',\n        className\n      )}\n      data-slot=\"command-list\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandEmpty({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      className={cn('py-6 text-center text-sm', className)}\n      data-slot=\"command-empty\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      className={cn(\n        'overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group-heading]]:text-xs',\n        className\n      )}\n      data-slot=\"command-group\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      className={cn('-mx-1 h-px bg-border', className)}\n      data-slot=\"command-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      className={cn(\n        \"group/command-item relative flex cursor-default select-none items-center gap-2 in-data-[slot=dialog-content]:rounded-lg! rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-selected:bg-muted data-selected:text-foreground data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-selected:*:[svg]:text-foreground\",\n        className\n      )}\n      data-slot=\"command-item\"\n      {...props}\n    >\n      {children}\n      <CheckIcon className=\"ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100\" />\n    </CommandPrimitive.Item>\n  );\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-data-selected/command-item:text-foreground',\n        className\n      )}\n      data-slot=\"command-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n};\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/kbd.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/shared/components/ui/kbd.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {\n  return (\n    <kbd\n      className={cn(\n        \"pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm bg-muted in-data-[slot=tooltip-content]:bg-background/20 px-1 font-medium font-sans in-data-[slot=tooltip-content]:text-background text-muted-foreground text-xs dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3\",\n        className\n      )}\n      data-slot=\"kbd\"\n      {...props}\n    />\n  );\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <kbd\n      className={cn('inline-flex items-center gap-1', className)}\n      data-slot=\"kbd-group\"\n      {...props}\n    />\n  );\n}\n\nexport { Kbd, KbdGroup };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "command-menu",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export CommandMenu01 from @components/uiblocks/command-menu-01/command-menu-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/command-menu/command-menu-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "command",
          "search",
          "palette",
          "keyboard",
          "命令",
          "搜索",
          "快捷键"
        ],
        "contentGuidelines": [
          "Give each command a clear label and an actual destination or action.",
          "Verify shortcuts do not conflict with browser or form behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/command-menu-01/command-menu-01.tsx",
        "exportName": "CommandMenu01",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/command-menu-01"
      }
    },
    {
      "name": "command-menu-02",
      "type": "registry:block",
      "title": "Command Menu with Keyboard Shortcuts",
      "description": "Command Menu with Keyboard Shortcuts. Keyboard-friendly command palettes for navigation, search, and quick actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cmdk@1.1.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/command-menu/command-menu-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/command-menu-02.tsx",
          "content": "'use client';\n\nimport {\n  IconArrowRight,\n  IconAt,\n  IconCopy,\n  IconDeviceDesktop,\n  IconDownload,\n  IconFile,\n  IconFileSearch,\n  IconKeyboard,\n  IconLink,\n  IconLogout,\n  IconMessage,\n  IconPencil,\n  IconPlus,\n  IconSend,\n  IconSettings,\n  IconTemplate,\n  IconUser,\n  IconUsers,\n} from '@tabler/icons-react';\nimport { useEffect, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandShortcut,\n} from \"./shared/components/ui/command\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"./shared/components/ui/dialog\";\nimport { Kbd, KbdGroup } from \"./shared/components/ui/kbd\";\n\nexport function CommandMenu02() {\n  const [open, setOpen] = useState(true);\n  const [inputValue, setInputValue] = useState('');\n\n  useEffect(() => {\n    const down = (e: KeyboardEvent) => {\n      if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {\n        e.preventDefault();\n        setOpen((prev) => !prev);\n      }\n    };\n\n    document.addEventListener('keydown', down);\n    return () => document.removeEventListener('keydown', down);\n  }, []);\n\n  return (\n    <>\n      <Button onClick={() => setOpen(true)} variant=\"outline\">\n        Open Command Menu\n      </Button>\n\n      <Dialog onOpenChange={setOpen} open={open}>\n        <DialogHeader className=\"sr-only\">\n          <DialogTitle>Command Menu</DialogTitle>\n          <DialogDescription>\n            Use the command menu to navigate through the app.\n          </DialogDescription>\n        </DialogHeader>\n        <DialogContent\n          className=\"gap-0 overflow-hidden rounded-xl border-border/50 p-0 shadow-lg sm:max-w-lg\"\n          showCloseButton={false}\n        >\n          <Command className=\"p-0 **:data-[slot=input-group]:h-10! **:data-[slot=command-input-wrapper]:grow **:data-[slot=input-group]:rounded-none! **:data-[slot=input-group]:border-0 **:data-[slot=input-group]:bg-transparent **:data-[slot=command-input-wrapper]:p-0 **:data-[slot=input-group-addon]:pl-0!\">\n            <div className=\"flex h-12 items-center gap-2 border-border/50 border-b px-4\">\n              <CommandInput\n                className=\"text-[15px]\"\n                onValueChange={setInputValue}\n                placeholder=\"What do you need?\"\n                value={inputValue}\n              />\n              <button\n                className=\"flex shrink-0 items-center\"\n                onClick={() => setOpen(false)}\n                type=\"button\"\n              >\n                <Kbd>Esc</Kbd>\n              </button>\n            </div>\n\n            <CommandList className=\"max-h-[400px] py-2\">\n              <CommandEmpty>No results found.</CommandEmpty>\n\n              <CommandGroup>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconSettings aria-hidden />\n                  Account Settings...\n                  <CommandShortcut>\n                    <KbdGroup>\n                      <Kbd>⌘</Kbd>\n                      <Kbd>,</Kbd>\n                    </KbdGroup>\n                  </CommandShortcut>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconUser aria-hidden />\n                  Switch Workspace...\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconLogout aria-hidden />\n                  Log Out\n                  <CommandShortcut>\n                    <KbdGroup>\n                      <Kbd>⌘</Kbd>\n                      <Kbd>Q</Kbd>\n                    </KbdGroup>\n                  </CommandShortcut>\n                </CommandItem>\n              </CommandGroup>\n\n              <CommandGroup heading=\"Documents\">\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconFile aria-hidden />\n                  Search Documents...\n                  <CommandShortcut>\n                    <KbdGroup>\n                      <Kbd>⌘</Kbd>\n                      <Kbd>F</Kbd>\n                    </KbdGroup>\n                  </CommandShortcut>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconPlus aria-hidden />\n                  Create New Document...\n                  <CommandShortcut>\n                    <KbdGroup>\n                      <Kbd>⌘</Kbd>\n                      <Kbd>N</Kbd>\n                    </KbdGroup>\n                  </CommandShortcut>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconFile aria-hidden />\n                  Upload Document...\n                  <CommandShortcut>\n                    <KbdGroup>\n                      <Kbd>⌘</Kbd>\n                      <Kbd>U</Kbd>\n                    </KbdGroup>\n                  </CommandShortcut>\n                </CommandItem>\n              </CommandGroup>\n\n              <CommandGroup heading=\"Signing\">\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconSend aria-hidden />\n                  Request Signature...\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconPencil aria-hidden />\n                  Sign a Document...\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconUsers aria-hidden />\n                  Bulk Send for Signature...\n                </CommandItem>\n              </CommandGroup>\n\n              <CommandGroup heading=\"Templates\">\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconTemplate aria-hidden />\n                  Search Templates...\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconPlus aria-hidden />\n                  Create New Template...\n                </CommandItem>\n              </CommandGroup>\n\n              <CommandGroup heading=\"General\">\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconDeviceDesktop aria-hidden />\n                  Change Theme...\n                  <CommandShortcut>\n                    <KbdGroup>\n                      <Kbd>⌘</Kbd>\n                      <Kbd>T</Kbd>\n                    </KbdGroup>\n                  </CommandShortcut>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconCopy aria-hidden />\n                  Copy Current URL\n                  <CommandShortcut>\n                    <KbdGroup>\n                      <Kbd>⌘</Kbd>\n                      <Kbd>⇧</Kbd>\n                      <Kbd>C</Kbd>\n                    </KbdGroup>\n                  </CommandShortcut>\n                </CommandItem>\n              </CommandGroup>\n\n              <CommandGroup heading=\"Navigation\">\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;<strong className=\"font-semibold\">Inbox</strong>\n                  </span>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;\n                    <strong className=\"font-semibold\">Action Required</strong>\n                  </span>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;\n                    <strong className=\"font-semibold\">\n                      Waiting for Others\n                    </strong>\n                  </span>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;\n                    <strong className=\"font-semibold\">Completed</strong>\n                  </span>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;<strong className=\"font-semibold\">Drafts</strong>\n                  </span>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;\n                    <strong className=\"font-semibold\">Templates</strong>\n                  </span>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;\n                    <strong className=\"font-semibold\">Archive</strong>\n                  </span>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;<strong className=\"font-semibold\">Trash</strong>\n                  </span>\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconArrowRight aria-hidden />\n                  <span>\n                    Go to&nbsp;\n                    <strong className=\"font-semibold\">Settings</strong>\n                  </span>\n                </CommandItem>\n              </CommandGroup>\n\n              <CommandGroup heading=\"Quick Actions\">\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconLink aria-hidden />\n                  Copy Signing Link\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconDownload aria-hidden />\n                  Download Document\n                </CommandItem>\n              </CommandGroup>\n\n              <CommandGroup heading=\"Help\">\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconFileSearch aria-hidden />\n                  Search Help Center...\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconMessage aria-hidden />\n                  Send Feedback...\n                </CommandItem>\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconAt aria-hidden />\n                  Contact Support\n                </CommandItem>\n              </CommandGroup>\n\n              <CommandGroup heading=\"Keyboard Shortcuts\">\n                <CommandItem\n                  className=\"mx-2 rounded-lg py-2.5\"\n                  onSelect={() => setOpen(false)}\n                >\n                  <IconKeyboard aria-hidden />\n                  View Keyboard Shortcuts\n                  <CommandShortcut>\n                    <KbdGroup>\n                      <Kbd>⌘</Kbd>\n                      <Kbd>/</Kbd>\n                    </KbdGroup>\n                  </CommandShortcut>\n                </CommandItem>\n              </CommandGroup>\n            </CommandList>\n          </Command>\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n}\n"
        },
        {
          "path": "licenses/command-menu-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/command-menu-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/command.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/shared/components/ui/command.tsx",
          "content": "'use client';\n\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { CheckIcon, SearchIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"./dialog\";\nimport { InputGroup, InputGroupAddon } from \"./input-group\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      className={cn(\n        'flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground',\n        className\n      )}\n      data-slot=\"command\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandDialog({\n  title = 'Command Palette',\n  description = 'Search for a command to run...',\n  children,\n  className,\n  showCloseButton = false,\n  ...props\n}: Omit<React.ComponentProps<typeof Dialog>, 'children'> & {\n  title?: string;\n  description?: string;\n  className?: string;\n  showCloseButton?: boolean;\n  children: React.ReactNode;\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\n          'top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0',\n          className\n        )}\n        showCloseButton={showCloseButton}\n      >\n        {children}\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div className=\"p-1 pb-0\" data-slot=\"command-input-wrapper\">\n      <InputGroup className=\"h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!\">\n        <CommandPrimitive.Input\n          className={cn(\n            'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n            className\n          )}\n          data-slot=\"command-input\"\n          {...props}\n        />\n        <InputGroupAddon>\n          <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n        </InputGroupAddon>\n      </InputGroup>\n    </div>\n  );\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      className={cn(\n        'no-scrollbar max-h-72 scroll-py-1 overflow-y-auto overflow-x-hidden outline-none',\n        className\n      )}\n      data-slot=\"command-list\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandEmpty({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      className={cn('py-6 text-center text-sm', className)}\n      data-slot=\"command-empty\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      className={cn(\n        'overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group-heading]]:text-xs',\n        className\n      )}\n      data-slot=\"command-group\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      className={cn('-mx-1 h-px bg-border', className)}\n      data-slot=\"command-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      className={cn(\n        \"group/command-item relative flex cursor-default select-none items-center gap-2 in-data-[slot=dialog-content]:rounded-lg! rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-selected:bg-muted data-selected:text-foreground data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-selected:*:[svg]:text-foreground\",\n        className\n      )}\n      data-slot=\"command-item\"\n      {...props}\n    >\n      {children}\n      <CheckIcon className=\"ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100\" />\n    </CommandPrimitive.Item>\n  );\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-data-selected/command-item:text-foreground',\n        className\n      )}\n      data-slot=\"command-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n};\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/kbd.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/shared/components/ui/kbd.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {\n  return (\n    <kbd\n      className={cn(\n        \"pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm bg-muted in-data-[slot=tooltip-content]:bg-background/20 px-1 font-medium font-sans in-data-[slot=tooltip-content]:text-background text-muted-foreground text-xs dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3\",\n        className\n      )}\n      data-slot=\"kbd\"\n      {...props}\n    />\n  );\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <kbd\n      className={cn('inline-flex items-center gap-1', className)}\n      data-slot=\"kbd-group\"\n      {...props}\n    />\n  );\n}\n\nexport { Kbd, KbdGroup };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "command-menu",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export CommandMenu02 from @components/uiblocks/command-menu-02/command-menu-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/command-menu/command-menu-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "command",
          "search",
          "palette",
          "keyboard",
          "命令",
          "搜索",
          "快捷键"
        ],
        "contentGuidelines": [
          "Give each command a clear label and an actual destination or action.",
          "Verify shortcuts do not conflict with browser or form behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/command-menu-02/command-menu-02.tsx",
        "exportName": "CommandMenu02",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/command-menu-02"
      }
    },
    {
      "name": "command-menu-03",
      "type": "registry:block",
      "title": "Command Menu with Documentation Search",
      "description": "Command Menu with Documentation Search. Keyboard-friendly command palettes for navigation, search, and quick actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cmdk@1.1.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/command-menu/command-menu-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/command-menu-03.tsx",
          "content": "\"use client\";\n\nimport { IconArrowRight, IconCornerDownLeft } from \"@tabler/icons-react\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandShortcut,\n} from \"./shared/components/ui/command\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"./shared/components/ui/dialog\";\nimport { Kbd } from \"./shared/components/ui/kbd\";\n\nconst navItems = [\n  { href: \"/\", label: \"Home\", keywords: [\"home\", \"main\", \"index\"] },\n  {\n    href: \"/dashboard\",\n    label: \"Dashboard\",\n    keywords: [\"dashboard\", \"overview\"],\n  },\n  {\n    href: \"/settings\",\n    label: \"Settings\",\n    keywords: [\"settings\", \"preferences\"],\n  },\n];\n\nconst pageGroups = [\n  {\n    name: \"Getting Started\",\n    pages: [\n      {\n        name: \"Introduction\",\n        href: \"/docs/introduction\",\n        keywords: [\"intro\", \"start\"],\n      },\n      {\n        name: \"Installation\",\n        href: \"/docs/installation\",\n        keywords: [\"install\", \"setup\"],\n      },\n      {\n        name: \"Quick Start\",\n        href: \"/docs/quick-start\",\n        keywords: [\"quick\", \"begin\"],\n      },\n    ],\n  },\n  {\n    name: \"Utilities\",\n    pages: [\n      {\n        name: \"Typography\",\n        href: \"/docs/utilities/typography\",\n        keywords: [\"text\", \"font\"],\n      },\n      {\n        name: \"Colors\",\n        href: \"/docs/utilities/colors\",\n        keywords: [\"color\", \"theme\"],\n      },\n      {\n        name: \"Spacing\",\n        href: \"/docs/utilities/spacing\",\n        keywords: [\"margin\", \"padding\"],\n      },\n    ],\n  },\n];\n\nconst colorGroups = [\n  {\n    name: \"Neutral\",\n    colors: [\n      {\n        name: \"Neutral 50\",\n        className: \"neutral-50\",\n        value: \"oklch(0.985 0 0)\",\n      },\n      {\n        name: \"Neutral 100\",\n        className: \"neutral-100\",\n        value: \"oklch(0.97 0 0)\",\n      },\n      {\n        name: \"Neutral 200\",\n        className: \"neutral-200\",\n        value: \"oklch(0.922 0 0)\",\n      },\n      {\n        name: \"Neutral 500\",\n        className: \"neutral-500\",\n        value: \"oklch(0.556 0 0)\",\n      },\n      {\n        name: \"Neutral 900\",\n        className: \"neutral-900\",\n        value: \"oklch(0.205 0 0)\",\n      },\n    ],\n  },\n  {\n    name: \"Blue\",\n    colors: [\n      {\n        name: \"Blue 50\",\n        className: \"blue-50\",\n        value: \"oklch(0.97 0.014 254.604)\",\n      },\n      {\n        name: \"Blue 500\",\n        className: \"blue-500\",\n        value: \"oklch(0.623 0.214 259.815)\",\n      },\n      {\n        name: \"Blue 600\",\n        className: \"blue-600\",\n        value: \"oklch(0.546 0.245 262.881)\",\n      },\n    ],\n  },\n];\n\nexport type CommandMenu03Props = {\n  /** Connect this to your host router, such as TanStack Router's navigate. */\n  onNavigate?: (href: string) => void;\n};\n\nexport function CommandMenu03({ onNavigate }: CommandMenu03Props = {}) {\n  const [demoDestination, setDemoDestination] = useState<string | null>(null);\n  const navigate = useCallback(\n    (href: string) => {\n      if (onNavigate) onNavigate(href);\n      else setDemoDestination(href);\n    },\n    [onNavigate]\n  );\n  const [open, setOpen] = useState(true);\n\n  const copyToClipboard = useCallback((text: string) => {\n    navigator.clipboard.writeText(text);\n  }, []);\n\n  const runCommand = useCallback((command: () => unknown) => {\n    setOpen(false);\n    command();\n  }, []);\n\n  useEffect(() => {\n    const down = (e: KeyboardEvent) => {\n      if ((e.key === \"k\" && (e.metaKey || e.ctrlKey)) || e.key === \"/\") {\n        if (\n          (e.target instanceof HTMLElement && e.target.isContentEditable) ||\n          e.target instanceof HTMLInputElement ||\n          e.target instanceof HTMLTextAreaElement ||\n          e.target instanceof HTMLSelectElement\n        ) {\n          return;\n        }\n        e.preventDefault();\n        setOpen((prev) => !prev);\n      }\n    };\n\n    document.addEventListener(\"keydown\", down);\n    return () => document.removeEventListener(\"keydown\", down);\n  }, []);\n\n  return (\n    <>\n      <Button onClick={() => setOpen(true)} variant=\"outline\">\n        Open Command Menu\n      </Button>\n      {demoDestination && (\n        <p className=\"text-center text-muted-foreground text-xs\" role=\"status\">\n          Demo destination: {demoDestination}. Connect onNavigate to your\n          application router.\n        </p>\n      )}\n\n      <Dialog onOpenChange={setOpen} open={open}>\n        <DialogContent className=\"rounded-xl border-none bg-clip-padding p-2 pb-11 shadow-2xl ring-4 ring-neutral-200/80 **:data-[slot=dialog-close]:top-3! **:data-[slot=dialog-close]:right-3! sm:max-w-md dark:bg-neutral-900 dark:ring-neutral-800\">\n          <DialogHeader className=\"sr-only\">\n            <DialogTitle>Search documentation...</DialogTitle>\n            <DialogDescription>\n              Search for a command to run...\n            </DialogDescription>\n          </DialogHeader>\n\n          <Command className=\"rounded-none! bg-transparent p-0 **:data-[slot=input-group]:h-9! **:data-[slot=input-group]:rounded-md! **:data-[slot=input-group]:border-input **:data-[slot=input-group]:bg-input/50 **:data-[slot=command-input-wrapper]:p-0 **:data-[slot=command-input]:pr-9 **:data-[slot=command-input]:pl-2! **:data-[slot=input-group-addon]:pl-3!\">\n            <CommandInput placeholder=\"Search documentation...\" />\n            <CommandList className=\"no-scrollbar min-h-80 scroll-pt-2 scroll-pb-1.5\">\n              <CommandEmpty className=\"py-12 text-center text-muted-foreground text-sm\">\n                No results found.\n              </CommandEmpty>\n\n              {navItems.length > 0 && (\n                <CommandGroup\n                  className=\"p-0! **:[[cmdk-group-heading]]:scroll-mt-16 **:[[cmdk-group-heading]]:p-3! **:[[cmdk-group-heading]]:pb-1!\"\n                  heading=\"Pages\"\n                >\n                  {navItems.map((item) => (\n                    <CommandItem\n                      className=\"h-9 rounded-md border border-transparent px-3! font-medium hover:border-input hover:bg-input/50\"\n                      key={item.href}\n                      keywords={item.keywords}\n                      onSelect={() => {\n                        runCommand(() => navigate(item.href));\n                      }}\n                      value={`Navigation ${item.label}`}\n                    >\n                      <IconArrowRight aria-hidden=\"true\" className=\"size-4\" />\n                      {item.label}\n                    </CommandItem>\n                  ))}\n                </CommandGroup>\n              )}\n\n              {pageGroups.map((group) => (\n                <CommandGroup\n                  className=\"p-0! **:[[cmdk-group-heading]]:scroll-mt-16 **:[[cmdk-group-heading]]:p-3! **:[[cmdk-group-heading]]:pb-1!\"\n                  heading={group.name}\n                  key={group.name}\n                >\n                  {group.pages.map((page) => {\n                    const isComponent = page.href.includes(\"/components/\");\n                    return (\n                      <CommandItem\n                        className=\"h-9 rounded-md border border-transparent px-3! font-medium hover:border-input hover:bg-input/50\"\n                        key={page.href}\n                        keywords={page.keywords}\n                        onSelect={() => {\n                          runCommand(() => navigate(page.href));\n                        }}\n                        value={`${group.name} ${page.name}`}\n                      >\n                        {isComponent ? (\n                          <div className=\"aspect-square size-4 rounded-full border border-muted-foreground border-dashed\" />\n                        ) : (\n                          <IconArrowRight\n                            aria-hidden=\"true\"\n                            className=\"size-4\"\n                          />\n                        )}\n                        {page.name}\n                      </CommandItem>\n                    );\n                  })}\n                </CommandGroup>\n              ))}\n\n              {colorGroups.map((colorGroup) => (\n                <CommandGroup\n                  className=\"p-0! **:[[cmdk-group-heading]]:p-3!\"\n                  heading={colorGroup.name}\n                  key={colorGroup.name}\n                >\n                  {colorGroup.colors.map((color) => (\n                    <CommandItem\n                      className=\"h-9 rounded-md border border-transparent px-3! font-medium hover:border-input hover:bg-input/50\"\n                      key={color.className}\n                      keywords={[\"color\", color.name, color.className]}\n                      onSelect={() => {\n                        runCommand(() => copyToClipboard(color.value));\n                      }}\n                      value={color.className}\n                    >\n                      <div\n                        className=\"aspect-square size-4 rounded-sm border\"\n                        style={{ backgroundColor: color.value }}\n                      />\n                      {color.className}\n                      <CommandShortcut className=\"font-mono font-normal text-xs tabular-nums tracking-normal\">\n                        {color.value}\n                      </CommandShortcut>\n                    </CommandItem>\n                  ))}\n                </CommandGroup>\n              ))}\n            </CommandList>\n          </Command>\n\n          <div className=\"absolute inset-x-0 bottom-0 z-20 flex h-10 items-center gap-2 rounded-b-xl border-t border-t-neutral-100 bg-neutral-50 px-4 font-medium text-muted-foreground text-xs dark:border-t-neutral-700 dark:bg-neutral-800\">\n            <Kbd>\n              <IconCornerDownLeft aria-hidden=\"true\" className=\"size-3\" />\n            </Kbd>\n            Select\n          </div>\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n}\n"
        },
        {
          "path": "licenses/command-menu-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/command-menu-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.2.0):\n- Replaced Next.js navigation with an optional onNavigate callback. The standalone demo displays the selected destination without visiting fictional routes.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/command.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/shared/components/ui/command.tsx",
          "content": "'use client';\n\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { CheckIcon, SearchIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"./dialog\";\nimport { InputGroup, InputGroupAddon } from \"./input-group\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      className={cn(\n        'flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground',\n        className\n      )}\n      data-slot=\"command\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandDialog({\n  title = 'Command Palette',\n  description = 'Search for a command to run...',\n  children,\n  className,\n  showCloseButton = false,\n  ...props\n}: Omit<React.ComponentProps<typeof Dialog>, 'children'> & {\n  title?: string;\n  description?: string;\n  className?: string;\n  showCloseButton?: boolean;\n  children: React.ReactNode;\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\n          'top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0',\n          className\n        )}\n        showCloseButton={showCloseButton}\n      >\n        {children}\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div className=\"p-1 pb-0\" data-slot=\"command-input-wrapper\">\n      <InputGroup className=\"h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!\">\n        <CommandPrimitive.Input\n          className={cn(\n            'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n            className\n          )}\n          data-slot=\"command-input\"\n          {...props}\n        />\n        <InputGroupAddon>\n          <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n        </InputGroupAddon>\n      </InputGroup>\n    </div>\n  );\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      className={cn(\n        'no-scrollbar max-h-72 scroll-py-1 overflow-y-auto overflow-x-hidden outline-none',\n        className\n      )}\n      data-slot=\"command-list\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandEmpty({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      className={cn('py-6 text-center text-sm', className)}\n      data-slot=\"command-empty\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      className={cn(\n        'overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group-heading]]:text-xs',\n        className\n      )}\n      data-slot=\"command-group\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      className={cn('-mx-1 h-px bg-border', className)}\n      data-slot=\"command-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      className={cn(\n        \"group/command-item relative flex cursor-default select-none items-center gap-2 in-data-[slot=dialog-content]:rounded-lg! rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-selected:bg-muted data-selected:text-foreground data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-selected:*:[svg]:text-foreground\",\n        className\n      )}\n      data-slot=\"command-item\"\n      {...props}\n    >\n      {children}\n      <CheckIcon className=\"ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100\" />\n    </CommandPrimitive.Item>\n  );\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-data-selected/command-item:text-foreground',\n        className\n      )}\n      data-slot=\"command-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n};\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/kbd.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/shared/components/ui/kbd.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Kbd({ className, ...props }: React.ComponentProps<'kbd'>) {\n  return (\n    <kbd\n      className={cn(\n        \"pointer-events-none inline-flex h-5 w-fit min-w-5 select-none items-center justify-center gap-1 rounded-sm bg-muted in-data-[slot=tooltip-content]:bg-background/20 px-1 font-medium font-sans in-data-[slot=tooltip-content]:text-background text-muted-foreground text-xs dark:in-data-[slot=tooltip-content]:bg-background/10 [&_svg:not([class*='size-'])]:size-3\",\n        className\n      )}\n      data-slot=\"kbd\"\n      {...props}\n    />\n  );\n}\n\nfunction KbdGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <kbd\n      className={cn('inline-flex items-center gap-1', className)}\n      data-slot=\"kbd-group\"\n      {...props}\n    />\n  );\n}\n\nexport { Kbd, KbdGroup };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "command-menu",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export CommandMenu03 from @components/uiblocks/command-menu-03/command-menu-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/command-menu/command-menu-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.2.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f",
          "modifications": [
            "Replaced Next.js navigation with an optional onNavigate callback. The standalone demo displays the selected destination without visiting fictional routes."
          ]
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "command",
          "search",
          "palette",
          "keyboard",
          "命令",
          "搜索",
          "快捷键"
        ],
        "contentGuidelines": [
          "Give each command a clear label and an actual destination or action.",
          "Verify shortcuts do not conflict with browser or form behavior.",
          "Pass onNavigate(href) to connect the command palette to your host router. Without it, navigation selections only display their demo destination."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/command-menu-03/command-menu-03.tsx",
        "exportName": "CommandMenu03",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/command-menu-03"
      }
    },
    {
      "name": "command-menu-04",
      "type": "registry:block",
      "title": "Command Menu with Global Search",
      "description": "Command Menu with Global Search. Keyboard-friendly command palettes for navigation, search, and quick actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cmdk@1.1.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/command-menu/command-menu-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-04/command-menu-04.tsx",
          "content": "'use client';\n\nimport {\n  IconFileText,\n  IconFileTypeDocx,\n  IconFileTypePdf,\n  IconFileTypeXls,\n  IconFileZip,\n  IconFolder,\n  IconMessageCircle,\n  IconPhoto,\n  IconX,\n} from '@tabler/icons-react';\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { type ComponentType, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Command,\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandList,\n} from \"./shared/components/ui/command\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport type SearchResultKind = 'chat' | 'image' | 'document' | 'project';\n\nexport type SearchResult = {\n  id: string;\n  kind: SearchResultKind;\n  title: string;\n  /** Shown under the title while searching. Chats use a text excerpt; files use their type. */\n  snippet: string;\n  date: string;\n  /** Images only: CSS background for the thumbnail. */\n  thumbnail?: string;\n};\n\ntype Filter = 'all' | SearchResultKind;\n\nconst filters: { value: Filter; label: string }[] = [\n  { value: 'all', label: 'All' },\n  { value: 'chat', label: 'Chats' },\n  { value: 'image', label: 'Images' },\n  { value: 'document', label: 'Documents' },\n  { value: 'project', label: 'Projects' },\n];\n\nconst fileIcons: Record<string, ComponentType<{ className?: string }>> = {\n  pdf: IconFileTypePdf,\n  docx: IconFileTypeDocx,\n  xlsx: IconFileTypeXls,\n  zip: IconFileZip,\n};\n\nconst kindIcons: Record<\n  SearchResultKind,\n  ComponentType<{ className?: string }>\n> = {\n  chat: IconMessageCircle,\n  image: IconPhoto,\n  document: IconFileText,\n  project: IconFolder,\n};\n\nconst lastOpenedChats: SearchResult[] = [\n  {\n    id: 'c1',\n    kind: 'chat',\n    title: 'Q3 launch checklist',\n    snippet: 'Pin the launch date once legal signs off on the new terms.',\n    date: 'Today',\n  },\n  {\n    id: 'c2',\n    kind: 'chat',\n    title: 'Draft onboarding email sequence',\n    snippet:\n      'Three emails over five days, starting the morning after launch, each with one clear action.',\n    date: 'Today',\n  },\n];\n\nconst recentChats: SearchResult[] = [\n  {\n    id: 'c3',\n    kind: 'chat',\n    title: 'Compare pricing page layouts',\n    snippet:\n      'Three-column pricing works when the middle tier is the one most people should pick. Ship it with the launch.',\n    date: 'Yesterday',\n  },\n  {\n    id: 'c4',\n    kind: 'chat',\n    title: 'Summarize customer interviews',\n    snippet:\n      'Eight of twelve customers mentioned invoice export first. Nobody brought up the dashboard redesign.',\n    date: 'Yesterday',\n  },\n  {\n    id: 'c5',\n    kind: 'chat',\n    title: 'Fix flaky checkout test',\n    snippet:\n      'The test races the payment webhook. Wait for the order status instead of a fixed delay.',\n    date: 'Sep 4',\n  },\n  {\n    id: 'c6',\n    kind: 'chat',\n    title: 'Write release notes for v2.4',\n    snippet:\n      'Lead with the two launch features people asked for most, then list fixes in one short block.',\n    date: 'Sep 2',\n  },\n  {\n    id: 'c7',\n    kind: 'chat',\n    title: 'Plan database index cleanup',\n    snippet:\n      'Drop the four unused indexes on orders first; they cost the most on writes.',\n    date: 'Aug 30',\n  },\n  {\n    id: 'c8',\n    kind: 'chat',\n    title: 'Brainstorm team offsite agenda',\n    snippet:\n      'Half a day on the roadmap, half a day on the launch retro, evening free.',\n    date: 'Aug 28',\n  },\n  {\n    id: 'c9',\n    kind: 'chat',\n    title: 'Review API rate limit options',\n    snippet:\n      'Token bucket per API key, 600 requests a minute. Raise the ceiling for launch week.',\n    date: 'Aug 26',\n  },\n];\n\nconst images: SearchResult[] = [\n  {\n    id: 'i1',\n    kind: 'image',\n    title: 'launch-hero-banner.png',\n    snippet: 'Image',\n    date: 'Sep 3',\n    thumbnail: 'linear-gradient(135deg, #f97316, #db2777)',\n  },\n  {\n    id: 'i2',\n    kind: 'image',\n    title: 'launch-pricing-table.png',\n    snippet: 'Image',\n    date: 'Aug 20',\n    thumbnail: 'linear-gradient(135deg, #0ea5e9, #6366f1)',\n  },\n  {\n    id: 'i3',\n    kind: 'image',\n    title: 'launch-onboarding-flow.png',\n    snippet: 'Image',\n    date: 'Aug 27',\n    thumbnail: 'linear-gradient(135deg, #22c55e, #0d9488)',\n  },\n  {\n    id: 'i4',\n    kind: 'image',\n    title: 'dashboard-dark-mode.png',\n    snippet: 'Image',\n    date: 'Aug 15',\n    thumbnail: 'linear-gradient(135deg, #1e293b, #475569)',\n  },\n  {\n    id: 'i5',\n    kind: 'image',\n    title: 'launch-social-card.jpg',\n    snippet: 'Image',\n    date: 'Aug 12',\n    thumbnail: 'linear-gradient(135deg, #a855f7, #f43f5e)',\n  },\n  {\n    id: 'i6',\n    kind: 'image',\n    title: 'checkout-error-states.png',\n    snippet: 'Image',\n    date: 'Jul 30',\n    thumbnail: 'linear-gradient(135deg, #eab308, #f97316)',\n  },\n];\n\nconst documents: SearchResult[] = [\n  {\n    id: 'd1',\n    kind: 'document',\n    title: 'Q3_launch_plan.pdf',\n    snippet: 'PDF',\n    date: 'Sep 1',\n  },\n  {\n    id: 'd2',\n    kind: 'document',\n    title: 'launch-pricing-proposal.docx',\n    snippet: 'Document',\n    date: 'Aug 22',\n  },\n  {\n    id: 'd3',\n    kind: 'document',\n    title: 'pre-launch-interview-notes.md',\n    snippet: 'Document',\n    date: 'Aug 19',\n  },\n  {\n    id: 'd4',\n    kind: 'document',\n    title: 'launch-assets-final.zip',\n    snippet: 'File',\n    date: 'Aug 11',\n  },\n  {\n    id: 'd5',\n    kind: 'document',\n    title: 'pricing-experiments.xlsx',\n    snippet: 'Spreadsheet',\n    date: 'Aug 14',\n  },\n];\n\nconst projects: SearchResult[] = [\n  {\n    id: 'p1',\n    kind: 'project',\n    title: 'Q3 product launch',\n    snippet: 'Project',\n    date: 'Sep 5',\n  },\n  {\n    id: 'p2',\n    kind: 'project',\n    title: 'Launch pricing and packaging',\n    snippet: 'Project',\n    date: 'Aug 30',\n  },\n  {\n    id: 'p3',\n    kind: 'project',\n    title: 'Post-launch onboarding redesign',\n    snippet: 'Project',\n    date: 'Aug 21',\n  },\n  {\n    id: 'p4',\n    kind: 'project',\n    title: 'Checkout reliability',\n    snippet: 'Project',\n    date: 'Jul 28',\n  },\n];\n\nconst searchResults: SearchResult[] = [\n  ...lastOpenedChats,\n  ...recentChats,\n  ...images,\n  ...documents,\n  ...projects,\n];\n\nexport function CommandMenu04() {\n  const [open, setOpen] = useState(true);\n\n  return (\n    <>\n      <Button onClick={() => setOpen(true)} variant=\"outline\">\n        Search\n      </Button>\n\n      <GlobalSearch\n        lastOpened={lastOpenedChats}\n        onOpenChange={setOpen}\n        onSelect={() => setOpen(false)}\n        open={open}\n        recent={recentChats}\n        results={searchResults}\n      />\n    </>\n  );\n}\n\nexport function GlobalSearch({\n  open,\n  onOpenChange,\n  onSelect,\n  lastOpened,\n  recent,\n  results,\n}: {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  onSelect: (result: SearchResult) => void;\n  lastOpened: SearchResult[];\n  recent: SearchResult[];\n  results: SearchResult[];\n}) {\n  const [query, setQuery] = useState('');\n  const [filter, setFilter] = useState<Filter>('all');\n  const needle = query.trim().toLowerCase();\n\n  const matches = results.filter(\n    (result) =>\n      (filter === 'all' || result.kind === filter) &&\n      `${result.title} ${result.snippet}`.toLowerCase().includes(needle)\n  );\n\n  const reset = () => {\n    setQuery('');\n    setFilter('all');\n  };\n\n  return (\n    <CommandDialog\n      className=\"-translate-y-1/2 top-1/2 rounded-2xl! sm:max-w-[720px]\"\n      description=\"Search chats, images, documents, and projects\"\n      onOpenChange={(next) => {\n        if (!next) {\n          reset();\n        }\n        onOpenChange(next);\n      }}\n      open={open}\n      title=\"Global search\"\n    >\n      <Command shouldFilter={false}>\n        <div className=\"flex h-16 shrink-0 items-center py-5 pr-4 pl-6\">\n          <CommandPrimitive.Input\n            className=\"min-w-0 flex-1 bg-transparent text-base tracking-[-0.02em] outline-hidden placeholder:text-muted-foreground\"\n            onValueChange={setQuery}\n            placeholder=\"Search...\"\n            value={query}\n          />\n          <div className=\"flex shrink-0 items-center gap-1\">\n            {query && (\n              <>\n                <button\n                  className=\"px-2 text-muted-foreground text-sm hover:text-foreground\"\n                  onClick={reset}\n                  type=\"button\"\n                >\n                  Clear\n                </button>\n                <span aria-hidden className=\"mx-1 h-5 w-px bg-border\" />\n              </>\n            )}\n            <button\n              aria-label=\"Close search\"\n              className=\"flex size-9 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground\"\n              onClick={() => onOpenChange(false)}\n              type=\"button\"\n            >\n              <IconX className=\"size-5\" />\n            </button>\n          </div>\n        </div>\n\n        <CommandList className=\"h-[396px] max-h-[396px] pb-3\">\n          {needle ? (\n            <>\n              <div\n                aria-label=\"Result type filters\"\n                className=\"sticky top-0 z-10 flex gap-1 bg-popover px-6 pb-3\"\n                role=\"tablist\"\n              >\n                {filters.map((option) => {\n                  const selected = option.value === filter;\n                  return (\n                    <button\n                      aria-selected={selected}\n                      className={cn(\n                        'rounded-full px-3.5 py-1.5 text-sm transition-colors',\n                        selected\n                          ? 'bg-muted text-foreground'\n                          : 'text-muted-foreground hover:text-foreground'\n                      )}\n                      key={option.value}\n                      onClick={() => setFilter(option.value)}\n                      role=\"tab\"\n                      type=\"button\"\n                    >\n                      {option.label}\n                    </button>\n                  );\n                })}\n              </div>\n              <CommandEmpty>No results for “{query}”.</CommandEmpty>\n              {filter === 'image' ? (\n                <div className=\"grid grid-cols-4 gap-3 px-6\">\n                  {matches.map((result) => (\n                    <CommandItem\n                      className=\"block aspect-square rounded-xl! bg-transparent! p-0 [&_svg]:hidden\"\n                      key={result.id}\n                      onSelect={() => onSelect(result)}\n                      value={result.id}\n                    >\n                      <span\n                        aria-label={result.title}\n                        className=\"block size-full rounded-xl ring-1 ring-foreground/10 ring-offset-2 ring-offset-popover group-data-selected/command-item:ring-foreground/40\"\n                        role=\"img\"\n                        style={{ background: result.thumbnail }}\n                      />\n                    </CommandItem>\n                  ))}\n                </div>\n              ) : (\n                matches.map((result) => (\n                  <ResultRow\n                    key={result.id}\n                    needle={needle}\n                    onSelect={onSelect}\n                    result={result}\n                  />\n                ))\n              )}\n            </>\n          ) : (\n            <>\n              <CommandGroup\n                className=\"p-0 **:[[cmdk-group-heading]]:px-6 **:[[cmdk-group-heading]]:py-2 **:[[cmdk-group-heading]]:font-normal! **:[[cmdk-group-heading]]:text-sm!\"\n                heading=\"Last opened\"\n              >\n                {lastOpened.map((result) => (\n                  <ResultRow\n                    key={result.id}\n                    onSelect={onSelect}\n                    result={result}\n                  />\n                ))}\n              </CommandGroup>\n              <CommandGroup\n                className=\"mt-5 p-0 **:[[cmdk-group-heading]]:px-6 **:[[cmdk-group-heading]]:py-2 **:[[cmdk-group-heading]]:font-normal! **:[[cmdk-group-heading]]:text-sm!\"\n                heading=\"Recent chats\"\n              >\n                {recent.map((result) => (\n                  <ResultRow\n                    key={result.id}\n                    onSelect={onSelect}\n                    result={result}\n                  />\n                ))}\n              </CommandGroup>\n            </>\n          )}\n        </CommandList>\n      </Command>\n    </CommandDialog>\n  );\n}\n\nfunction ResultRow({\n  result,\n  needle,\n  onSelect,\n}: {\n  result: SearchResult;\n  needle?: string;\n  onSelect: (result: SearchResult) => void;\n}) {\n  const extension = result.title.split('.').pop() ?? '';\n  const Icon =\n    result.kind === 'document'\n      ? (fileIcons[extension] ?? IconFileText)\n      : kindIcons[result.kind];\n\n  return (\n    <CommandItem\n      className=\"mx-3 min-h-12 gap-3 rounded-xl! px-3 py-2 text-sm\"\n      onSelect={() => onSelect(result)}\n      value={result.id}\n    >\n      <span className=\"flex size-8 shrink-0 items-center justify-center\">\n        <Icon\n          className={cn('size-5!', extension === 'pdf' && 'text-red-500')}\n        />\n      </span>\n      <span className=\"min-w-0 flex-1\">\n        <span className=\"block truncate leading-5\">\n          <Highlight needle={needle} text={result.title} />\n        </span>\n        {needle && (\n          <span className=\"block truncate pt-0.5 text-muted-foreground leading-5\">\n            <Highlight needle={needle} text={result.snippet} />\n          </span>\n        )}\n      </span>\n      {needle && (\n        <span className=\"shrink-0 pl-4 text-muted-foreground text-sm\">\n          {result.date}\n        </span>\n      )}\n    </CommandItem>\n  );\n}\n\nfunction Highlight({ text, needle }: { text: string; needle?: string }) {\n  const start = needle ? text.toLowerCase().indexOf(needle) : -1;\n  if (!needle || start === -1) {\n    return text;\n  }\n  const end = start + needle.length;\n  return (\n    <>\n      {text.slice(0, start)}\n      <mark className=\"bg-transparent font-semibold text-foreground\">\n        {text.slice(start, end)}\n      </mark>\n      {text.slice(end)}\n    </>\n  );\n}\n"
        },
        {
          "path": "licenses/command-menu-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/command-menu-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-04/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/command.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-04/shared/components/ui/command.tsx",
          "content": "'use client';\n\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { CheckIcon, SearchIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"./dialog\";\nimport { InputGroup, InputGroupAddon } from \"./input-group\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      className={cn(\n        'flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground',\n        className\n      )}\n      data-slot=\"command\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandDialog({\n  title = 'Command Palette',\n  description = 'Search for a command to run...',\n  children,\n  className,\n  showCloseButton = false,\n  ...props\n}: Omit<React.ComponentProps<typeof Dialog>, 'children'> & {\n  title?: string;\n  description?: string;\n  className?: string;\n  showCloseButton?: boolean;\n  children: React.ReactNode;\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\n          'top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0',\n          className\n        )}\n        showCloseButton={showCloseButton}\n      >\n        {children}\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div className=\"p-1 pb-0\" data-slot=\"command-input-wrapper\">\n      <InputGroup className=\"h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!\">\n        <CommandPrimitive.Input\n          className={cn(\n            'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n            className\n          )}\n          data-slot=\"command-input\"\n          {...props}\n        />\n        <InputGroupAddon>\n          <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n        </InputGroupAddon>\n      </InputGroup>\n    </div>\n  );\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      className={cn(\n        'no-scrollbar max-h-72 scroll-py-1 overflow-y-auto overflow-x-hidden outline-none',\n        className\n      )}\n      data-slot=\"command-list\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandEmpty({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      className={cn('py-6 text-center text-sm', className)}\n      data-slot=\"command-empty\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      className={cn(\n        'overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group-heading]]:text-xs',\n        className\n      )}\n      data-slot=\"command-group\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      className={cn('-mx-1 h-px bg-border', className)}\n      data-slot=\"command-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      className={cn(\n        \"group/command-item relative flex cursor-default select-none items-center gap-2 in-data-[slot=dialog-content]:rounded-lg! rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-selected:bg-muted data-selected:text-foreground data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-selected:*:[svg]:text-foreground\",\n        className\n      )}\n      data-slot=\"command-item\"\n      {...props}\n    >\n      {children}\n      <CheckIcon className=\"ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100\" />\n    </CommandPrimitive.Item>\n  );\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-data-selected/command-item:text-foreground',\n        className\n      )}\n      data-slot=\"command-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n};\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-04/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-04/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-04/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-04/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/command-menu-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "command-menu",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export CommandMenu04 from @components/uiblocks/command-menu-04/command-menu-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/command-menu/command-menu-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "command",
          "search",
          "palette",
          "keyboard",
          "命令",
          "搜索",
          "快捷键"
        ],
        "contentGuidelines": [
          "Give each command a clear label and an actual destination or action.",
          "Verify shortcuts do not conflict with browser or form behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/command-menu-04/command-menu-04.tsx",
        "exportName": "CommandMenu04",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/command-menu-04"
      }
    },
    {
      "name": "dashboard-01",
      "type": "registry:block",
      "title": "Dashboard with Revenue and Customers",
      "description": "Dashboard with Revenue and Customers. Complete application dashboards combining navigation, metrics, charts, and customer data.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "date-fns@4.4.0",
        "lucide-react@0.577.0",
        "recharts@3.8.0"
      ],
      "files": [
        {
          "path": "content/components/dashboard/dashboard-01/app/page.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/app/page.tsx",
          "content": "'use client';\n\nimport { useState } from 'react';\nimport { SidebarInset, SidebarProvider } from \"../shared/components/ui/sidebar\";\nimport { cn } from \"../shared/lib/utils\";\nimport { CustomersTable } from \"../customers-table\";\nimport { DashboardCharts } from \"../dashboard-charts\";\nimport { DashboardHeader } from \"../dashboard-header\";\nimport { DashboardSidebar } from \"../dashboard-sidebar\";\n\nexport default function Page() {\n  const [period, setPeriod] = useState<'day' | 'week' | 'month' | 'year'>(\n    'week'\n  );\n  const [range, setRange] = useState({ from: '2025-11-10', to: '2025-11-16' });\n\n  const bindTheme = (node: HTMLElement | null) => {\n    if (!node) {\n      return;\n    }\n    const onKey = (event: KeyboardEvent) => {\n      const target = event.target as HTMLElement;\n      if (\n        event.key === 'D' &&\n        event.shiftKey &&\n        !target.isContentEditable &&\n        !['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)\n      ) {\n        document.documentElement.classList.toggle('dark');\n      }\n    };\n    window.addEventListener('keydown', onKey);\n    return () => window.removeEventListener('keydown', onKey);\n  };\n\n  return (\n    <SidebarProvider\n      className=\"isolate min-h-dvh bg-background font-sans text-foreground antialiased [--chart-1:#3b82f6] [--chart-2:#d4d4d8] [--font-sans:system-ui,sans-serif] [--sidebar-width-icon:3.5rem]! [--sidebar-width:15.5rem]! **:data-[slot=sidebar-group-label]:transition-none **:data-[slot=sidebar-menu-button]:transition-none **:data-[slot=sidebar-container]:ease-[cubic-bezier(0.23,1,0.32,1)] **:data-[slot=sidebar-gap]:ease-[cubic-bezier(0.23,1,0.32,1)] dark:[--chart-2:#3f3f46] dark:[--sidebar-accent:#1a1a1a] dark:[--sidebar:#0d0d0d]\"\n      defaultOpen={false}\n    >\n      <a\n        className=\"sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:rounded-lg focus:bg-background focus:p-3\"\n        href=\"#overview\"\n      >\n        Skip to dashboard\n      </a>\n      <DashboardSidebar />\n      <SidebarInset className=\"min-w-0\" id=\"overview\" ref={bindTheme}>\n        <DashboardHeader\n          onPeriodChange={setPeriod}\n          onRangeChange={setRange}\n          period={period}\n          range={range}\n        />\n        <div className=\"@container/metrics border-b\">\n          <dl className=\"grid @3xl/metrics:grid-cols-4 grid-cols-2\">\n            {metrics.map((metric) => (\n              <div\n                className=\"flex min-w-0 flex-col justify-center gap-2 border-b @3xl/metrics:border-b-0 @3xl/metrics:not-first:border-l @3xl/metrics:px-7 px-4 py-6 even:border-l [&:nth-last-child(-n+2)]:border-b-0\"\n                key={metric.label}\n              >\n                <dt className=\"truncate text-muted-foreground text-sm sm:text-[0.9375rem]\">\n                  {metric.label}\n                </dt>\n                <dd className=\"flex flex-wrap items-baseline gap-2.5\">\n                  <span className=\"font-semibold text-[1.75rem] tabular-nums tracking-tight\">\n                    {metric.value}\n                  </span>\n                  <span\n                    className={cn(\n                      'font-medium text-[0.8125rem] tabular-nums',\n                      metric.changeClass\n                    )}\n                  >\n                    {metric.change}\n                  </span>\n                </dd>\n              </div>\n            ))}\n          </dl>\n        </div>\n        <DashboardCharts from={range.from} period={period} to={range.to} />\n        <CustomersTable />\n      </SidebarInset>\n    </SidebarProvider>\n  );\n}\n\nconst metrics = [\n  {\n    label: 'New customers',\n    value: '1,284',\n    change: '+8.2%',\n    changeClass: 'text-green-600 dark:text-green-400',\n  },\n  {\n    label: 'Success rate',\n    value: '97.4%',\n    change: '+0.6%',\n    changeClass: 'text-green-600 dark:text-green-400',\n  },\n  {\n    label: 'Failure rate',\n    value: '2.6%',\n    change: '-0.6%',\n    changeClass: 'text-red-600 dark:text-red-400',\n  },\n  {\n    label: 'Refunds',\n    value: '38',\n    change: '0.0%',\n    changeClass: 'text-muted-foreground',\n  },\n];\n"
        },
        {
          "path": "content/components/dashboard/dashboard-01/customers-table.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/customers-table.tsx",
          "content": "'use client';\n\nimport {\n  DownloadIcon,\n  EllipsisIcon,\n  EyeIcon,\n  FunnelIcon,\n  MailIcon,\n  SearchIcon,\n  XIcon,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"./shared/components/ui/dialog\";\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput,\n  InputGroupText,\n} from \"./shared/components/ui/input-group\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"./shared/components/ui/table\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst easeOut = 'ease-[cubic-bezier(0.23,1,0.32,1)]';\nconst press = `${easeOut} transition-[scale,background-color] duration-150 active:scale-[0.96] motion-reduce:active:scale-100`;\nconst swap = `${easeOut} fade-in zoom-in-95 motion-reduce:zoom-in-100 animate-in duration-150`;\n\nexport function CustomersTable() {\n  const [query, setQuery] = useState('');\n  const [selected, setSelected] = useState<Record<Facet, readonly string[]>>({\n    Plan: [],\n    Status: [],\n  });\n  const [page, setPage] = useState(1);\n  const [customer, setCustomer] = useState<Customer | null>(null);\n\n  const bindSearch = (node: HTMLInputElement | null) => {\n    if (!node) {\n      return;\n    }\n\n    const focusSearch = (event: KeyboardEvent) => {\n      if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {\n        event.preventDefault();\n        node.focus();\n      }\n    };\n    window.addEventListener('keydown', focusSearch);\n    return () => window.removeEventListener('keydown', focusSearch);\n  };\n\n  const term = query.trim().toLocaleLowerCase();\n  const filtered = customers.filter(\n    (item) =>\n      (!term ||\n        item.name.toLocaleLowerCase().includes(term) ||\n        item.email.toLocaleLowerCase().includes(term)) &&\n      (!selected.Plan.length || selected.Plan.includes(item.plan)) &&\n      (!selected.Status.length || selected.Status.includes(item.status))\n  );\n\n  const pageCount = Math.max(1, Math.ceil(filtered.length / 9));\n  const visibleCustomers = filtered.slice((page - 1) * 9, page * 9);\n  const filterCount = selected.Plan.length + selected.Status.length;\n  const toggle = (facet: Facet, option: string) => {\n    setSelected((current) => ({\n      ...current,\n      [facet]: current[facet].includes(option)\n        ? current[facet].filter((item) => item !== option)\n        : [...current[facet], option],\n    }));\n    setPage(1);\n  };\n  const clear = (facet?: Facet) => {\n    setSelected((current) =>\n      facet ? { ...current, [facet]: [] } : { Plan: [], Status: [] }\n    );\n    setPage(1);\n  };\n\n  const exportCustomers = () => {\n    const csv = [\n      ['Customer', 'Email', 'Signed up', 'Plan', 'Status'],\n      ...filtered.map((item) => [\n        item.name,\n        item.email,\n        item.signedUp,\n        item.plan,\n        item.status,\n      ]),\n    ]\n      .map((row) =>\n        row.map((value) => `\"${value.replaceAll('\"', '\"\"')}\"`).join(',')\n      )\n      .join('\\n');\n    const url = URL.createObjectURL(\n      new Blob([csv], { type: 'text/csv;charset=utf-8' })\n    );\n    const link = document.createElement('a');\n    link.href = url;\n    link.download = 'customers.csv';\n    link.click();\n    URL.revokeObjectURL(url);\n  };\n\n  return (\n    <section className=\"@container/customers w-full\" id=\"customers\">\n      <div className=\"flex @3xl/customers:flex-row flex-col @3xl/customers:items-center @3xl/customers:justify-between gap-4 p-4 sm:p-7 sm:py-6\">\n        <div>\n          <h2 className=\"font-semibold text-[18px] leading-6\">New customers</h2>\n          <p className=\"mt-1 text-[13px] text-muted-foreground\">\n            Recent signups across all plans\n          </p>\n        </div>\n        <div className=\"flex min-w-0 flex-1 flex-wrap items-center @3xl/customers:justify-end gap-2\">\n          {filterCount > 0 && (\n            <fieldset\n              aria-label=\"Active customer filters\"\n              className=\"flex flex-wrap items-center gap-2\"\n            >\n              {facets.map(\n                (facet) =>\n                  selected[facet].length > 0 && (\n                    <div\n                      className={cn(\n                        swap,\n                        'flex h-7 items-center rounded-md bg-muted pl-2 text-[0.8125rem]'\n                      )}\n                      key={facet}\n                    >\n                      <DropdownMenu>\n                        <DropdownMenuTrigger\n                          render={\n                            <button\n                              className=\"flex h-full max-w-56 items-center gap-1 rounded-l-md outline-none focus-visible:ring-3 focus-visible:ring-ring/50\"\n                              type=\"button\"\n                            />\n                          }\n                        >\n                          <span className=\"text-muted-foreground\">\n                            {facet}:\n                          </span>\n                          <span className=\"truncate font-medium\">\n                            {selected[facet].join(', ')}\n                          </span>\n                        </DropdownMenuTrigger>\n                        <DropdownMenuContent className=\"w-44 rounded-[10px] p-1.5\">\n                          <DropdownMenuGroup>\n                            {options[facet].map((option) => (\n                              <DropdownMenuCheckboxItem\n                                checked={selected[facet].includes(option)}\n                                className=\"min-h-9 px-2.5\"\n                                closeOnClick={false}\n                                key={option}\n                                onCheckedChange={() => toggle(facet, option)}\n                              >\n                                {option}\n                              </DropdownMenuCheckboxItem>\n                            ))}\n                          </DropdownMenuGroup>\n                        </DropdownMenuContent>\n                      </DropdownMenu>\n                      <button\n                        aria-label={`Remove ${facet.toLowerCase()} filter`}\n                        className=\"flex h-full w-6 items-center justify-center rounded-r-md text-muted-foreground outline-none hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50\"\n                        onClick={() => clear(facet)}\n                        type=\"button\"\n                      >\n                        <XIcon aria-hidden=\"true\" className=\"size-3.5\" />\n                      </button>\n                    </div>\n                  )\n              )}\n              <Button\n                onClick={() => clear()}\n                size=\"sm\"\n                type=\"button\"\n                variant=\"ghost\"\n              >\n                Clear all\n              </Button>\n            </fieldset>\n          )}\n          <label className=\"sr-only\" htmlFor=\"customer-search\">\n            Search customers\n          </label>\n          <InputGroup className=\"h-9 @3xl/customers:w-60 w-full border-border bg-background dark:border-input\">\n            <InputGroupAddon>\n              <InputGroupText>\n                <SearchIcon aria-hidden=\"true\" />\n              </InputGroupText>\n            </InputGroupAddon>\n            <InputGroupInput\n              className=\"text-[16px] sm:text-sm\"\n              id=\"customer-search\"\n              name=\"customer-search\"\n              onChange={(event) => {\n                setQuery(event.target.value);\n                setPage(1);\n              }}\n              placeholder=\"Search customers\"\n              ref={bindSearch}\n              type=\"search\"\n              value={query}\n            />\n            <InputGroupAddon align=\"inline-end\">\n              <kbd className=\"hidden rounded border bg-muted px-1.5 py-0.5 font-medium text-[10px] sm:block\">\n                ⌘K\n              </kbd>\n            </InputGroupAddon>\n          </InputGroup>\n          <div className=\"flex gap-2\">\n            <DropdownMenu>\n              <DropdownMenuTrigger\n                render={\n                  <Button\n                    className={cn(press, 'h-9 flex-1 sm:flex-none')}\n                    size=\"lg\"\n                    variant=\"outline\"\n                  />\n                }\n              >\n                <FunnelIcon aria-hidden=\"true\" />\n                Filter{filterCount ? ` (${filterCount})` : ''}\n              </DropdownMenuTrigger>\n              <DropdownMenuContent align=\"end\" className=\"w-48\">\n                {facets.map((facet, index) => (\n                  <DropdownMenuGroup key={facet}>\n                    {index > 0 && <DropdownMenuSeparator />}\n                    <DropdownMenuLabel>{facet}</DropdownMenuLabel>\n                    {options[facet].map((option) => (\n                      <DropdownMenuCheckboxItem\n                        checked={selected[facet].includes(option)}\n                        key={option}\n                        onCheckedChange={() => toggle(facet, option)}\n                      >\n                        {option}\n                      </DropdownMenuCheckboxItem>\n                    ))}\n                  </DropdownMenuGroup>\n                ))}\n                {filterCount > 0 && (\n                  <>\n                    <DropdownMenuSeparator />\n                    <DropdownMenuGroup>\n                      <DropdownMenuItem onClick={() => clear()}>\n                        Clear filters\n                      </DropdownMenuItem>\n                    </DropdownMenuGroup>\n                  </>\n                )}\n              </DropdownMenuContent>\n            </DropdownMenu>\n            <Button\n              className={cn(press, 'h-9 flex-1 sm:flex-none')}\n              onClick={exportCustomers}\n              size=\"lg\"\n              variant=\"outline\"\n            >\n              <DownloadIcon aria-hidden=\"true\" />\n              Export\n            </Button>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"border-t\">\n        <Table className=\"min-w-245 table-fixed [&_td:first-child]:pl-4 sm:[&_td:first-child]:pl-7 [&_td:last-child]:pr-4 sm:[&_td:last-child]:pr-7 [&_th:first-child]:pl-4 sm:[&_th:first-child]:pl-7 [&_th:last-child]:pr-4 sm:[&_th:last-child]:pr-7\">\n          <colgroup>\n            <col className=\"w-[27%]\" />\n            <col className=\"w-[23%]\" />\n            <col className=\"w-[18%]\" />\n            <col className=\"w-[12%]\" />\n            <col className=\"w-[calc(20%-68px)]\" />\n            <col className=\"w-17\" />\n          </colgroup>\n          <TableHeader>\n            <TableRow className=\"h-11 hover:bg-transparent\">\n              <TableHead className=\"px-2 text-muted-foreground text-xs\">\n                Customer\n              </TableHead>\n              <TableHead className=\"px-2 text-muted-foreground text-xs\">\n                Email\n              </TableHead>\n              <TableHead className=\"px-2 text-muted-foreground text-xs\">\n                Signed up\n              </TableHead>\n              <TableHead className=\"px-2 text-muted-foreground text-xs\">\n                Plan\n              </TableHead>\n              <TableHead className=\"px-2 text-muted-foreground text-xs\">\n                Status\n              </TableHead>\n              <TableHead>\n                <span className=\"sr-only\">Actions</span>\n              </TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {visibleCustomers.map((item) => (\n              <TableRow className=\"h-14\" key={item.email}>\n                <TableCell className=\"px-2 py-0 font-medium text-sm\">\n                  {item.name}\n                </TableCell>\n                <TableCell className=\"px-2 py-0 text-muted-foreground text-sm\">\n                  {item.email}\n                </TableCell>\n                <TableCell className=\"px-2 py-0 text-muted-foreground text-sm\">\n                  {item.signedUp}\n                </TableCell>\n                <TableCell className=\"px-2 py-0 text-sm\">{item.plan}</TableCell>\n                <TableCell className=\"px-2 py-0\">\n                  <StatusDot status={item.status} />\n                </TableCell>\n                <TableCell className=\"px-0 py-0 text-right\">\n                  <DropdownMenu>\n                    <DropdownMenuTrigger\n                      render={\n                        <Button\n                          aria-label={`Actions for ${item.name}`}\n                          size=\"icon-sm\"\n                          variant=\"ghost\"\n                        />\n                      }\n                    >\n                      <EllipsisIcon aria-hidden=\"true\" />\n                    </DropdownMenuTrigger>\n                    <DropdownMenuContent\n                      align=\"end\"\n                      className=\"w-60 rounded-[10px] p-1.5\"\n                      sideOffset={6}\n                    >\n                      <DropdownMenuGroup>\n                        <DropdownMenuLabel className=\"flex flex-col gap-1 px-2.5 py-2\">\n                          <div className=\"truncate font-medium text-foreground text-sm\">\n                            {item.name}\n                          </div>\n                          <div className=\"truncate font-normal text-xs\">\n                            {item.email}\n                          </div>\n                        </DropdownMenuLabel>\n                      </DropdownMenuGroup>\n                      <DropdownMenuSeparator />\n                      <DropdownMenuGroup>\n                        <DropdownMenuItem\n                          className=\"min-h-9 gap-2.5 px-2.5\"\n                          onClick={() => setCustomer(item)}\n                        >\n                          <EyeIcon\n                            aria-hidden=\"true\"\n                            className=\"text-muted-foreground\"\n                          />\n                          View customer\n                        </DropdownMenuItem>\n                        <DropdownMenuItem\n                          className=\"min-h-9 gap-2.5 px-2.5\"\n                          render={\n                            <a\n                              aria-label={`Send email to ${item.name}`}\n                              href={`mailto:${item.email}`}\n                            >\n                              <MailIcon\n                                aria-hidden=\"true\"\n                                className=\"text-muted-foreground\"\n                              />\n                              Send email\n                            </a>\n                          }\n                        />\n                      </DropdownMenuGroup>\n                    </DropdownMenuContent>\n                  </DropdownMenu>\n                </TableCell>\n              </TableRow>\n            ))}\n            {!visibleCustomers.length && (\n              <TableRow className=\"h-28 hover:bg-transparent\">\n                <TableCell\n                  className=\"text-center text-muted-foreground text-sm\"\n                  colSpan={6}\n                >\n                  No customers match your search and filters.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </div>\n\n      <div className=\"flex flex-col gap-3 border-t p-4 sm:flex-row sm:items-center sm:justify-between sm:px-7\">\n        <p aria-live=\"polite\" className=\"text-[13px] text-muted-foreground\">\n          {filtered.length\n            ? `${(page - 1) * 9 + 1}–${Math.min(page * 9, filtered.length)} of ${filtered.length} customers`\n            : '0 customers'}\n        </p>\n        <nav aria-label=\"Customer pages\" className=\"flex items-center gap-1\">\n          <Button\n            disabled={page === 1}\n            onClick={() => setPage((current) => Math.max(1, current - 1))}\n            size=\"sm\"\n            variant=\"outline\"\n          >\n            Previous\n          </Button>\n          {Array.from({ length: pageCount }, (_, index) => index + 1).map(\n            (number) => (\n              <Button\n                aria-current={page === number ? 'page' : undefined}\n                key={number}\n                onClick={() => setPage(number)}\n                size=\"icon-sm\"\n                variant={page === number ? 'secondary' : 'ghost'}\n              >\n                {number}\n              </Button>\n            )\n          )}\n          <Button\n            disabled={page === pageCount}\n            onClick={() =>\n              setPage((current) => Math.min(pageCount, current + 1))\n            }\n            size=\"sm\"\n            variant=\"outline\"\n          >\n            Next\n          </Button>\n        </nav>\n      </div>\n\n      <Dialog\n        onOpenChange={(open) => {\n          if (!open) {\n            setCustomer(null);\n          }\n        }}\n        open={customer !== null}\n      >\n        <DialogContent className=\"sm:max-w-md\">\n          {customer && (\n            <>\n              <DialogHeader>\n                <DialogTitle>{customer.name}</DialogTitle>\n                <DialogDescription>\n                  Customer details and signup information.\n                </DialogDescription>\n              </DialogHeader>\n              <dl className=\"grid grid-cols-[88px_1fr] gap-x-4 gap-y-3 text-sm\">\n                <dt className=\"text-muted-foreground\">Email</dt>\n                <dd className=\"min-w-0 break-all\">{customer.email}</dd>\n                <dt className=\"text-muted-foreground\">Signed up</dt>\n                <dd>{customer.signedUp}</dd>\n                <dt className=\"text-muted-foreground\">Plan</dt>\n                <dd>{customer.plan}</dd>\n                <dt className=\"text-muted-foreground\">Status</dt>\n                <dd>\n                  <StatusDot status={customer.status} />\n                </dd>\n              </dl>\n              <DialogFooter>\n                <Button\n                  render={\n                    <a\n                      aria-label={`Contact ${customer.name}`}\n                      href={`mailto:${customer.email}`}\n                    >\n                      <MailIcon aria-hidden=\"true\" />\n                      Contact customer\n                    </a>\n                  }\n                />\n              </DialogFooter>\n            </>\n          )}\n        </DialogContent>\n      </Dialog>\n    </section>\n  );\n}\n\nfunction StatusDot({ status }: { readonly status: Status }) {\n  return (\n    <span className=\"inline-flex items-center gap-2 text-sm\">\n      <span\n        aria-hidden=\"true\"\n        className={cn('size-1.5 rounded-full', statusDots[status])}\n      />\n      {status}\n    </span>\n  );\n}\n\ntype Plan = 'Starter' | 'Pro' | 'Team';\ntype Status = 'Active' | 'Pending' | 'Under review' | 'Rejected' | 'Inactive';\n\ntype Customer = {\n  readonly name: string;\n  readonly email: string;\n  readonly signedUp: string;\n  readonly plan: Plan;\n  readonly status: Status;\n};\n\nconst customers: readonly Customer[] = [\n  {\n    name: 'Priya Raman',\n    email: 'priya.raman@lumenlabs.io',\n    signedUp: 'Today, 9:12 AM',\n    plan: 'Pro',\n    status: 'Active',\n  },\n  {\n    name: 'Daniel Okafor',\n    email: 'd.okafor@fastmail.com',\n    signedUp: 'Today, 8:40 AM',\n    plan: 'Starter',\n    status: 'Pending',\n  },\n  {\n    name: 'Mei Lin Chen',\n    email: 'meilin@outlook.com',\n    signedUp: 'Yesterday, 6:05 PM',\n    plan: 'Team',\n    status: 'Active',\n  },\n  {\n    name: 'Tomás Herrera',\n    email: 'tomas.h@gmail.com',\n    signedUp: 'Yesterday, 2:31 PM',\n    plan: 'Pro',\n    status: 'Under review',\n  },\n  {\n    name: 'Aisha Bello',\n    email: 'aisha.bello@proton.me',\n    signedUp: '11 Nov, 4:18 PM',\n    plan: 'Starter',\n    status: 'Rejected',\n  },\n  {\n    name: 'Lucas Moreau',\n    email: 'l.moreau@yahoo.fr',\n    signedUp: '11 Nov, 11:02 AM',\n    plan: 'Team',\n    status: 'Active',\n  },\n  {\n    name: 'Hannah Weiss',\n    email: 'hannah.w@gmail.com',\n    signedUp: '10 Nov, 7:44 PM',\n    plan: 'Pro',\n    status: 'Pending',\n  },\n  {\n    name: 'Kenji Sato',\n    email: 'kenji.sato@icloud.com',\n    signedUp: '10 Nov, 3:15 PM',\n    plan: 'Starter',\n    status: 'Inactive',\n  },\n  {\n    name: 'Olivia Brooks',\n    email: 'olivia@brooks.studio',\n    signedUp: '9 Nov, 10:27 AM',\n    plan: 'Team',\n    status: 'Active',\n  },\n  {\n    name: 'Sofia Alvarez',\n    email: 'sofia@northstar.co',\n    signedUp: '9 Nov, 8:16 AM',\n    plan: 'Pro',\n    status: 'Active',\n  },\n  {\n    name: 'Marcus Johnson',\n    email: 'marcus.j@hey.com',\n    signedUp: '8 Nov, 5:42 PM',\n    plan: 'Team',\n    status: 'Under review',\n  },\n  {\n    name: 'Elena Petrova',\n    email: 'elena@kinetic.design',\n    signedUp: '8 Nov, 1:20 PM',\n    plan: 'Starter',\n    status: 'Pending',\n  },\n  {\n    name: 'Noah Williams',\n    email: 'noah@fieldnotes.app',\n    signedUp: '7 Nov, 4:55 PM',\n    plan: 'Pro',\n    status: 'Active',\n  },\n  {\n    name: 'Fatima Zahra',\n    email: 'fatima.z@proton.me',\n    signedUp: '7 Nov, 11:09 AM',\n    plan: 'Team',\n    status: 'Active',\n  },\n  {\n    name: 'Ethan Park',\n    email: 'ethan.park@icloud.com',\n    signedUp: '6 Nov, 6:33 PM',\n    plan: 'Starter',\n    status: 'Inactive',\n  },\n  {\n    name: 'Amara Nwosu',\n    email: 'amara@woven.studio',\n    signedUp: '6 Nov, 2:08 PM',\n    plan: 'Pro',\n    status: 'Rejected',\n  },\n  {\n    name: 'Gabriel Silva',\n    email: 'gabriel@orbit.dev',\n    signedUp: '5 Nov, 9:47 AM',\n    plan: 'Team',\n    status: 'Active',\n  },\n  {\n    name: 'Leila Haddad',\n    email: 'leila.h@fastmail.com',\n    signedUp: '4 Nov, 3:26 PM',\n    plan: 'Starter',\n    status: 'Pending',\n  },\n];\n\ntype Facet = 'Plan' | 'Status';\nconst facets: readonly Facet[] = ['Plan', 'Status'];\nconst options: Record<Facet, readonly string[]> = {\n  Plan: ['Starter', 'Pro', 'Team'],\n  Status: ['Active', 'Pending', 'Under review', 'Rejected', 'Inactive'],\n};\n\nconst statusDots: Record<Status, string> = {\n  Active: 'bg-green-500',\n  Pending: 'bg-amber-500',\n  'Under review': 'bg-blue-500',\n  Rejected: 'bg-red-500',\n  Inactive: 'bg-muted-foreground/40',\n};\n"
        },
        {
          "path": "content/components/dashboard/dashboard-01/dashboard-charts.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/dashboard-charts.tsx",
          "content": "'use client';\n\nimport { format, parseISO } from 'date-fns';\nimport {\n  Area,\n  CartesianGrid,\n  ComposedChart,\n  Line,\n  XAxis,\n  YAxis,\n} from 'recharts';\nimport {\n  type ChartConfig,\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n} from \"./shared/components/ui/chart\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst swap =\n  'ease-[cubic-bezier(0.23,1,0.32,1)] fade-in zoom-in-95 motion-reduce:zoom-in-100 animate-in duration-150';\n\nexport function DashboardCharts({\n  period,\n  from,\n  to,\n}: {\n  period: 'day' | 'week' | 'month' | 'year';\n  from: string;\n  to: string;\n}) {\n  const hourly = from === to;\n  const start = days.findIndex((day) => day.date >= from);\n  const current = days.filter((day) => day.date >= from && day.date <= to);\n  const prior = days.slice(start - current.length, start);\n  const data = hourly\n    ? hours(current[0], prior[0])\n    : current.map((day, index) => ({\n        date: day.date,\n        revenue: day.revenue,\n        priorRevenue: prior[index]?.revenue ?? 0,\n        charges: day.charges,\n        priorCharges: prior[index]?.charges ?? 0,\n      }));\n  const tickStep = Math.max(1, Math.ceil(data.length / 7));\n  const ticks = data\n    .filter((_, index) => index % tickStep === 0)\n    .map((row) => row.date);\n  const tickFormat = hourly ? 'ha' : data.length > 90 ? 'MMM' : 'd MMM';\n  const labelFormat = hourly ? 'EEE, d MMM, ha' : 'EEE, d MMM yyyy';\n\n  return (\n    <div className=\"@container/charts border-b\">\n      <div className=\"grid @4xl/charts:grid-cols-2\">\n        {panels.map((panel) => {\n          const total = data.reduce((sum, row) => sum + row[panel.current], 0);\n          const previous = data.reduce(\n            (sum, row) => sum + row[panel.previous],\n            0\n          );\n          const change = previous ? ((total - previous) / previous) * 100 : 0;\n          const config = {\n            [panel.current]: {\n              label: `This ${period}`,\n              color: 'var(--chart-1)',\n            },\n            [panel.previous]: {\n              label: `Last ${period}`,\n              color: 'var(--chart-2)',\n            },\n          } satisfies ChartConfig;\n\n          return (\n            <section\n              aria-labelledby={`${panel.id}-title`}\n              className=\"@container/panel flex min-h-100 min-w-0 flex-col gap-7 border-t @4xl/charts:border-t-0 @4xl/charts:not-first:border-l p-4 py-7 first:border-t-0 sm:px-7\"\n              id={panel.id}\n              key={panel.id}\n            >\n              <div className=\"relative flex flex-col gap-2\">\n                <h2\n                  className=\"text-[0.9375rem] text-muted-foreground\"\n                  id={`${panel.id}-title`}\n                >\n                  {panel.title}\n                </h2>\n                <div className=\"flex flex-wrap items-baseline gap-2.5\">\n                  <p className=\"font-semibold text-[1.75rem] tabular-nums tracking-tight\">\n                    {panel.currency ? '$' : ''}\n                    {total.toLocaleString('en-US')}\n                  </p>\n                  <p\n                    className={cn(\n                      'font-medium text-[0.8125rem] tabular-nums',\n                      change < 0\n                        ? 'text-red-600 dark:text-red-400'\n                        : 'text-green-600 dark:text-green-400'\n                    )}\n                  >\n                    {change > 0 ? '+' : ''}\n                    {change.toFixed(1)}%\n                  </p>\n                  <p className=\"text-[0.8125rem] text-muted-foreground\">\n                    vs. last {period}\n                  </p>\n                </div>\n                <div className=\"@lg/panel:absolute @lg/panel:top-1 @lg/panel:right-0 flex items-center gap-4 text-[0.8125rem]\">\n                  <div className=\"flex items-center gap-2\">\n                    <span\n                      aria-hidden=\"true\"\n                      className=\"h-0.75 w-2.5 rounded-full bg-chart-1\"\n                    />\n                    This {period}\n                  </div>\n                  <div className=\"flex items-center gap-2\">\n                    <span\n                      aria-hidden=\"true\"\n                      className=\"h-0.75 w-2.5 rounded-full bg-chart-2\"\n                    />\n                    Last {period}\n                  </div>\n                </div>\n              </div>\n              {data.length ? (\n                <ChartContainer\n                  className={cn(swap, 'aspect-auto h-63 w-full')}\n                  config={config}\n                  key={`${from}-${to}`}\n                >\n                  <ComposedChart\n                    accessibilityLayer\n                    data={data}\n                    margin={{ top: 8, right: 8, bottom: 0, left: 0 }}\n                  >\n                    <CartesianGrid\n                      stroke=\"var(--border)\"\n                      strokeDasharray=\"3 4\"\n                      vertical={false}\n                    />\n                    <XAxis\n                      axisLine={false}\n                      dataKey=\"date\"\n                      height={40}\n                      minTickGap={24}\n                      tickFormatter={(date: string) =>\n                        format(parseISO(date), tickFormat)\n                      }\n                      tickLine={false}\n                      tickMargin={16}\n                      ticks={ticks}\n                    />\n                    <YAxis\n                      axisLine={false}\n                      domain={[0, 'auto']}\n                      tickCount={4}\n                      tickFormatter={(value: number) =>\n                        `${panel.currency ? '$' : ''}${value >= 1000 ? `${value / 1000}k` : value}`\n                      }\n                      tickLine={false}\n                      tickMargin={8}\n                      width={48}\n                    />\n                    <ChartTooltip\n                      content={\n                        <ChartTooltipContent\n                          className=\"w-47.5 rounded-[10px] bg-popover px-3 py-2.5\"\n                          formatter={(value, name) => (\n                            <div className=\"flex w-full items-center gap-2\">\n                              <span\n                                aria-hidden=\"true\"\n                                className={cn(\n                                  'size-2 shrink-0 rounded-xs',\n                                  name === panel.current\n                                    ? 'bg-chart-1'\n                                    : 'bg-chart-2'\n                                )}\n                              />\n                              <span className=\"flex-1 text-muted-foreground\">\n                                {name === panel.current\n                                  ? `This ${period}`\n                                  : `Last ${period}`}\n                              </span>\n                              <span className=\"font-medium text-foreground tabular-nums\">\n                                {panel.currency ? '$' : ''}\n                                {Number(value).toLocaleString('en-US')}\n                              </span>\n                            </div>\n                          )}\n                          labelClassName=\"font-normal text-muted-foreground\"\n                          labelFormatter={(label) =>\n                            typeof label === 'string'\n                              ? format(parseISO(label), labelFormat)\n                              : label\n                          }\n                        />\n                      }\n                      cursor={{\n                        stroke: 'var(--chart-1)',\n                        strokeDasharray: '3 3',\n                      }}\n                    />\n                    <Area\n                      activeDot={false}\n                      dataKey={panel.current}\n                      fill=\"var(--chart-1)\"\n                      fillOpacity={0.1}\n                      isAnimationActive={false}\n                      stroke=\"none\"\n                      tooltipType=\"none\"\n                      type=\"linear\"\n                    />\n                    <Line\n                      activeDot={{\n                        r: 5,\n                        fill: 'var(--chart-1)',\n                        stroke: 'var(--background)',\n                        strokeWidth: 2,\n                      }}\n                      dataKey={panel.current}\n                      dot={data.length === 1}\n                      isAnimationActive={false}\n                      stroke=\"var(--chart-1)\"\n                      strokeWidth={2.5}\n                      type=\"linear\"\n                    />\n                    <Line\n                      activeDot={{\n                        r: 4,\n                        fill: 'var(--chart-2)',\n                        stroke: 'var(--background)',\n                        strokeWidth: 2,\n                      }}\n                      dataKey={panel.previous}\n                      dot={data.length === 1}\n                      isAnimationActive={false}\n                      stroke=\"var(--chart-2)\"\n                      strokeWidth={2}\n                      type=\"linear\"\n                    />\n                  </ComposedChart>\n                </ChartContainer>\n              ) : (\n                <p className=\"flex flex-1 items-center justify-center text-muted-foreground text-sm\">\n                  No data for this date range.\n                </p>\n              )}\n            </section>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n\nconst panels = [\n  {\n    id: 'revenue',\n    title: 'Revenue',\n    current: 'revenue',\n    previous: 'priorRevenue',\n    currency: true,\n  },\n  {\n    id: 'charges',\n    title: 'Charges',\n    current: 'charges',\n    previous: 'priorCharges',\n    currency: false,\n  },\n] as const;\n\ntype Day = { date: string; revenue: number; charges: number };\n\n// Seeded LCG so every render draws the same two years of demo data.\nlet seed = 20_251_116;\nconst random = () => {\n  seed = (Math.imul(seed, 1_664_525) + 1_013_904_223) >>> 0;\n  return seed / 2 ** 32;\n};\n\nconst days: Day[] = Array.from({ length: 731 }, (_, index) => {\n  const date = new Date(Date.UTC(2023, 10, 17 + index));\n  const growth = 1 + index / 731;\n  const weekend = date.getUTCDay() % 6 === 0 ? 0.62 : 1;\n  const season =\n    1 + 0.12 * Math.sin(((date.getUTCMonth() - 3) / 12) * Math.PI * 2);\n  const spike = random() < 0.04 ? 1.6 : 1;\n  const revenue = Math.round(\n    3400 * growth * weekend * season * spike * (0.82 + random() * 0.36)\n  );\n  return {\n    date: date.toISOString().slice(0, 10),\n    revenue,\n    charges: Math.round((revenue / 13.5) * (0.9 + random() * 0.2)),\n  };\n});\n\nconst activity = [\n  0.54, 0.46, 0.38, 0.35, 0.41, 0.58, 0.74, 0.97, 1.12, 1.38, 1.21, 1.53, 1.29,\n  1.41, 1.67, 1.48, 1.31, 1.58, 1.36, 1.19, 1.04, 0.86, 0.72, 0.63,\n];\n\n// Split a day's totals across 24 hours. Noise is keyed on the date so the\n// same day always draws the same curve, and the hourly shares sum to the day.\nfunction hours(day: Day | undefined, previous: Day | undefined) {\n  if (!(day && previous)) {\n    return [];\n  }\n  const weights = [day, previous].map((row) => {\n    const key = Number(row.date.replaceAll('-', ''));\n    const noisy = activity.map(\n      (weight, hour) =>\n        weight * (0.8 + 0.4 * ((Math.sin(key + hour * 7.31) + 1) / 2))\n    );\n    const total = noisy.reduce((sum, weight) => sum + weight, 0);\n    return noisy.map((weight) => weight / total);\n  });\n  let share = 0;\n  let priorShare = 0;\n  return activity.map((_, hour) => {\n    const from = share;\n    const priorFrom = priorShare;\n    share += weights[0][hour];\n    priorShare += weights[1][hour];\n    return {\n      date: `${day.date}T${String(hour).padStart(2, '0')}:00:00`,\n      revenue: Math.round(day.revenue * share) - Math.round(day.revenue * from),\n      priorRevenue:\n        Math.round(previous.revenue * priorShare) -\n        Math.round(previous.revenue * priorFrom),\n      charges: Math.round(day.charges * share) - Math.round(day.charges * from),\n      priorCharges:\n        Math.round(previous.charges * priorShare) -\n        Math.round(previous.charges * priorFrom),\n    };\n  });\n}\n"
        },
        {
          "path": "content/components/dashboard/dashboard-01/dashboard-header.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/dashboard-header.tsx",
          "content": "'use client';\n\nimport { format, parseISO } from 'date-fns';\nimport {\n  Bell,\n  CalendarDays,\n  ChartNoAxesCombined,\n  ChevronDown,\n  LockKeyhole,\n  LogOut,\n  Moon,\n  PanelLeftClose,\n  PanelLeftOpen,\n  Sun,\n  UserRound,\n} from 'lucide-react';\nimport { type ComponentProps, useState } from 'react';\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"./shared/components/ui/dialog\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport { Field, FieldGroup, FieldLabel } from \"./shared/components/ui/field\";\nimport { Input } from \"./shared/components/ui/input\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverDescription,\n  PopoverTitle,\n  PopoverTrigger,\n} from \"./shared/components/ui/popover\";\nimport { Separator } from \"./shared/components/ui/separator\";\nimport { useSidebar } from \"./shared/components/ui/sidebar\";\nimport { ToggleGroup, ToggleGroupItem } from \"./shared/components/ui/toggle-group\";\nimport { cn } from \"./shared/lib/utils\";\nimport type { DashboardCharts } from \"./dashboard-charts\";\n\ntype ChartOptions = ComponentProps<typeof DashboardCharts>;\ntype DateRange = Pick<ChartOptions, 'from' | 'to'>;\n\nconst press =\n  'ease-[cubic-bezier(0.23,1,0.32,1)] transition-[scale,background-color] duration-150 active:scale-[0.96] motion-reduce:active:scale-100';\n\nexport function DashboardHeader({\n  period,\n  range,\n  onPeriodChange,\n  onRangeChange,\n}: {\n  period: ChartOptions['period'];\n  range: DateRange;\n  onPeriodChange: (period: ChartOptions['period']) => void;\n  onRangeChange: (range: DateRange) => void;\n}) {\n  const { isMobile, openMobile, state, toggleSidebar } = useSidebar();\n  const sidebarOpen = isMobile ? openMobile : state === 'expanded';\n  const [customRange, setCustomRange] = useState<DateRange | null>(null);\n  const [unread, setUnread] = useState(true);\n  const [notificationsOpen, setNotificationsOpen] = useState(false);\n  const from = parseISO(range.from);\n  const to = parseISO(range.to);\n  const rangeLabel =\n    range.from === range.to\n      ? format(to, 'd MMM')\n      : `${format(from, from.getMonth() === to.getMonth() && from.getFullYear() === to.getFullYear() ? 'd' : 'd MMM')} – ${format(to, 'd MMM')}`;\n\n  return (\n    <>\n      <header className=\"flex min-h-15 flex-wrap items-center justify-between gap-3 border-b px-4 py-3 lg:px-7 lg:py-2\">\n        <div className=\"flex items-center gap-3\">\n          <Button\n            aria-expanded={sidebarOpen}\n            aria-label={sidebarOpen ? 'Collapse sidebar' : 'Expand sidebar'}\n            onClick={toggleSidebar}\n            size=\"icon-sm\"\n            type=\"button\"\n            variant=\"ghost\"\n          >\n            {sidebarOpen ? (\n              <PanelLeftClose aria-hidden=\"true\" />\n            ) : (\n              <PanelLeftOpen aria-hidden=\"true\" />\n            )}\n          </Button>\n          <h1 className=\"font-semibold text-lg tracking-tight\">Overview</h1>\n          <p className=\"text-[0.8125rem] text-muted-foreground max-sm:hidden\">\n            Updated 2 min ago\n          </p>\n        </div>\n        <div className=\"flex flex-wrap items-center gap-2 sm:gap-3\">\n          <ToggleGroup\n            aria-label=\"Chart period\"\n            className=\"rounded-[9px] border bg-muted/50 p-0.75\"\n            onValueChange={(values) => {\n              const next = periods.find((item) => item.value === values[0]);\n              if (!next) {\n                return;\n              }\n              onPeriodChange(next.value);\n              onRangeChange({ from: next.from, to: '2025-11-16' });\n            }}\n            size=\"sm\"\n            spacing={0.5}\n            value={[period]}\n          >\n            {periods.map((item) => (\n              <ToggleGroupItem\n                className=\"h-7 min-w-0 rounded-md px-3 text-[0.8125rem]\"\n                key={item.value}\n                value={item.value}\n              >\n                {item.label}\n              </ToggleGroupItem>\n            ))}\n          </ToggleGroup>\n          <DropdownMenu>\n            <DropdownMenuTrigger\n              render={\n                <Button\n                  aria-label={`Date range: ${rangeLabel}`}\n                  className={press}\n                  size=\"lg\"\n                  variant=\"outline\"\n                />\n              }\n            >\n              <CalendarDays data-icon=\"inline-start\" />\n              {rangeLabel}\n            </DropdownMenuTrigger>\n            <DropdownMenuContent\n              align=\"end\"\n              className=\"w-50 rounded-[10px] p-1.5\"\n            >\n              <DropdownMenuGroup>\n                <DropdownMenuLabel className=\"sr-only\">\n                  Date range\n                </DropdownMenuLabel>\n                <DropdownMenuRadioGroup\n                  onValueChange={(value) => {\n                    const next = ranges.find((item) => item.from === value);\n                    if (next) {\n                      onRangeChange({ from: next.from, to: '2025-11-16' });\n                    }\n                  }}\n                  value={range.to === '2025-11-16' ? range.from : ''}\n                >\n                  {ranges.map((item) => (\n                    <DropdownMenuRadioItem\n                      className=\"min-h-8.5 px-2.5\"\n                      key={item.label}\n                      value={item.from}\n                    >\n                      {item.label}\n                    </DropdownMenuRadioItem>\n                  ))}\n                </DropdownMenuRadioGroup>\n              </DropdownMenuGroup>\n              <DropdownMenuSeparator />\n              <DropdownMenuGroup>\n                <DropdownMenuItem\n                  className=\"min-h-8.5 px-2.5\"\n                  onClick={() => setCustomRange(range)}\n                >\n                  Custom range…\n                </DropdownMenuItem>\n              </DropdownMenuGroup>\n            </DropdownMenuContent>\n          </DropdownMenu>\n          <Separator className=\"h-5! max-sm:hidden\" orientation=\"vertical\" />\n          <Button\n            aria-label=\"Toggle dark mode\"\n            className={cn(press, 'relative')}\n            onClick={() => document.documentElement.classList.toggle('dark')}\n            size=\"icon-lg\"\n            title=\"Toggle dark mode (Shift+D)\"\n            type=\"button\"\n            variant=\"ghost\"\n          >\n            <Sun className=\"scale-100 opacity-100 transition-[opacity,scale] duration-150 ease-out dark:scale-50 dark:opacity-0\" />\n            <Moon className=\"absolute scale-50 opacity-0 transition-[opacity,scale] duration-150 ease-out dark:scale-100 dark:opacity-100\" />\n          </Button>\n          <Popover onOpenChange={setNotificationsOpen} open={notificationsOpen}>\n            <PopoverTrigger\n              render={\n                <Button\n                  aria-label={\n                    unread ? 'Notifications, unread updates' : 'Notifications'\n                  }\n                  className={cn(press, 'relative')}\n                  size=\"icon-lg\"\n                  variant=\"ghost\"\n                />\n              }\n            >\n              <Bell />\n              {unread && (\n                <span className=\"absolute top-2 right-2 size-1.5 rounded-full bg-blue-500 ring-2 ring-background\" />\n              )}\n            </PopoverTrigger>\n            <PopoverContent\n              align=\"end\"\n              className=\"w-90 max-w-[calc(100vw-2rem)] gap-0 overflow-hidden rounded-xl p-0\"\n              sideOffset={8}\n            >\n              <div className=\"flex h-13 items-center justify-between gap-3 border-b px-4\">\n                <PopoverTitle>Notifications</PopoverTitle>\n                <div className=\"text-muted-foreground text-xs tabular-nums\">\n                  {unread ? '2 unread' : 'All read'}\n                </div>\n                <PopoverDescription className=\"sr-only\">\n                  Recent workspace activity.\n                </PopoverDescription>\n              </div>\n              <Button\n                className=\"h-auto w-full items-start justify-start gap-3 whitespace-normal rounded-none p-4 text-left font-normal\"\n                onClick={() => setNotificationsOpen(false)}\n                render={\n                  <a href=\"#revenue\">\n                    <div className=\"flex size-9 shrink-0 items-center justify-center rounded-lg border bg-background\">\n                      <ChartNoAxesCombined\n                        aria-hidden=\"true\"\n                        className=\"size-4 text-muted-foreground\"\n                      />\n                    </div>\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n                      <div className=\"flex items-center justify-between gap-3 text-sm\">\n                        <span className=\"font-medium\">Weekly report</span>\n                        <time className=\"shrink-0 text-muted-foreground text-xs\">\n                          2m ago\n                        </time>\n                      </div>\n                      <p className=\"text-[0.8125rem] text-muted-foreground leading-5\">\n                        10–16 Nov · $42,910 in revenue\n                      </p>\n                      <p className=\"text-muted-foreground text-xs\">\n                        3,482 charges processed\n                      </p>\n                    </div>\n                    <span\n                      aria-hidden=\"true\"\n                      className={\n                        unread\n                          ? 'mt-1.5 size-1.5 shrink-0 rounded-full bg-blue-500'\n                          : 'mt-1.5 size-1.5 shrink-0'\n                      }\n                    />\n                  </a>\n                }\n                variant=\"ghost\"\n              />\n              <Separator />\n              <Button\n                className=\"h-auto w-full items-start justify-start gap-3 whitespace-normal rounded-none p-4 text-left font-normal\"\n                onClick={() => setNotificationsOpen(false)}\n                render={\n                  <a href=\"#customers\">\n                    <Avatar className=\"size-9 rounded-lg after:rounded-lg\">\n                      <AvatarImage\n                        alt=\"\"\n                        className=\"rounded-lg\"\n                        src=\"https://i.pravatar.cc/96?img=47\"\n                      />\n                      <AvatarFallback className=\"rounded-lg\">PR</AvatarFallback>\n                    </Avatar>\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n                      <div className=\"flex items-center justify-between gap-3 text-sm\">\n                        <span className=\"font-medium\">\n                          Priya Raman signed up\n                        </span>\n                        <time className=\"shrink-0 text-muted-foreground text-xs\">\n                          18m ago\n                        </time>\n                      </div>\n                      <p className=\"text-[0.8125rem] text-muted-foreground leading-5\">\n                        Joined Lumen Labs on the Pro plan.\n                      </p>\n                    </div>\n                    <span\n                      aria-hidden=\"true\"\n                      className={\n                        unread\n                          ? 'mt-1.5 size-1.5 shrink-0 rounded-full bg-blue-500'\n                          : 'mt-1.5 size-1.5 shrink-0'\n                      }\n                    />\n                  </a>\n                }\n                variant=\"ghost\"\n              />\n              <div className=\"flex items-center justify-end border-t px-3 py-2\">\n                <Button\n                  disabled={!unread}\n                  onClick={() => setUnread(false)}\n                  size=\"sm\"\n                  type=\"button\"\n                  variant=\"ghost\"\n                >\n                  Mark all as read\n                </Button>\n              </div>\n            </PopoverContent>\n          </Popover>\n          <DropdownMenu>\n            <DropdownMenuTrigger\n              render={\n                <Button\n                  aria-label=\"Account menu\"\n                  className={cn(press, 'gap-2 rounded-full pr-2.5 pl-1')}\n                  size=\"lg\"\n                  variant=\"outline\"\n                />\n              }\n            >\n              <Avatar className=\"size-7\">\n                <AvatarFallback className=\"bg-muted font-semibold text-[0.6875rem] text-foreground\">\n                  EB\n                </AvatarFallback>\n              </Avatar>\n              <ChevronDown data-icon=\"inline-end\" />\n            </DropdownMenuTrigger>\n            <DropdownMenuContent\n              align=\"end\"\n              className=\"w-60 rounded-[10px] p-1.5\"\n            >\n              <DropdownMenuGroup>\n                <DropdownMenuLabel className=\"flex flex-col gap-0.5 px-2.5 py-2\">\n                  <div className=\"font-medium text-foreground text-sm\">\n                    Ephraim Brooks\n                  </div>\n                  <div className=\"font-normal text-[0.8125rem]\">\n                    ephraim@lumenlabs.io\n                  </div>\n                </DropdownMenuLabel>\n              </DropdownMenuGroup>\n              <DropdownMenuSeparator />\n              <DropdownMenuGroup>\n                <DropdownMenuItem\n                  className=\"min-h-8.5 gap-2.5 px-2.5\"\n                  render={\n                    <a aria-label=\"Profile\" href=\"/settings/profile\">\n                      <UserRound />\n                      Profile\n                    </a>\n                  }\n                />\n                <DropdownMenuItem\n                  className=\"min-h-8.5 gap-2.5 px-2.5\"\n                  render={\n                    <a\n                      aria-label=\"Notifications\"\n                      href=\"/settings/notifications\"\n                    >\n                      <Bell />\n                      Notifications\n                    </a>\n                  }\n                />\n                <DropdownMenuItem\n                  className=\"min-h-8.5 gap-2.5 px-2.5\"\n                  render={\n                    <a aria-label=\"Security\" href=\"/settings/security\">\n                      <LockKeyhole />\n                      Security\n                    </a>\n                  }\n                />\n              </DropdownMenuGroup>\n              <DropdownMenuSeparator />\n              <DropdownMenuGroup>\n                <DropdownMenuItem\n                  className=\"min-h-8.5 gap-2.5 px-2.5\"\n                  render={\n                    <a aria-label=\"Log out\" href=\"/login\">\n                      <LogOut />\n                      Log out\n                    </a>\n                  }\n                  variant=\"destructive\"\n                />\n              </DropdownMenuGroup>\n            </DropdownMenuContent>\n          </DropdownMenu>\n        </div>\n      </header>\n      <Dialog\n        onOpenChange={(open) => {\n          if (!open) {\n            setCustomRange(null);\n          }\n        }}\n        open={customRange !== null}\n      >\n        <DialogContent>\n          {customRange && (\n            <form\n              className=\"flex flex-col gap-5\"\n              onSubmit={(event) => {\n                event.preventDefault();\n                if (\n                  customRange.from &&\n                  customRange.to &&\n                  customRange.from <= customRange.to\n                ) {\n                  onRangeChange(customRange);\n                  setCustomRange(null);\n                }\n              }}\n            >\n              <DialogHeader>\n                <DialogTitle>Custom date range</DialogTitle>\n                <DialogDescription>\n                  Choose the dates to include in your charts.\n                </DialogDescription>\n              </DialogHeader>\n              <FieldGroup>\n                <Field>\n                  <FieldLabel htmlFor=\"dashboard-from\">Start date</FieldLabel>\n                  <Input\n                    id=\"dashboard-from\"\n                    max={customRange.to}\n                    name=\"from\"\n                    onChange={(event) =>\n                      setCustomRange({\n                        ...customRange,\n                        from: event.target.value,\n                      })\n                    }\n                    required\n                    type=\"date\"\n                    value={customRange.from}\n                  />\n                </Field>\n                <Field>\n                  <FieldLabel htmlFor=\"dashboard-to\">End date</FieldLabel>\n                  <Input\n                    id=\"dashboard-to\"\n                    min={customRange.from}\n                    name=\"to\"\n                    onChange={(event) =>\n                      setCustomRange({ ...customRange, to: event.target.value })\n                    }\n                    required\n                    type=\"date\"\n                    value={customRange.to}\n                  />\n                </Field>\n              </FieldGroup>\n              <DialogFooter>\n                <Button\n                  onClick={() => setCustomRange(null)}\n                  type=\"button\"\n                  variant=\"outline\"\n                >\n                  Cancel\n                </Button>\n                <Button className={press} type=\"submit\">\n                  Apply range\n                </Button>\n              </DialogFooter>\n            </form>\n          )}\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n}\n\nconst periods = [\n  { label: 'Day', value: 'day', from: '2025-11-16' },\n  { label: 'Week', value: 'week', from: '2025-11-10' },\n  { label: 'Month', value: 'month', from: '2025-10-18' },\n  { label: 'Year', value: 'year', from: '2025-01-01' },\n] as const;\n\nconst ranges = [\n  { label: 'Last 7 days', from: '2025-11-10' },\n  { label: 'Last 14 days', from: '2025-11-03' },\n  { label: 'Last 30 days', from: '2025-10-18' },\n  { label: 'This quarter', from: '2025-10-01' },\n  { label: 'Year to date', from: '2025-01-01' },\n];\n"
        },
        {
          "path": "content/components/dashboard/dashboard-01/dashboard-sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/dashboard-sidebar.tsx",
          "content": "'use client';\n\nimport {\n  ArrowLeftRight,\n  BriefcaseBusiness,\n  ChartNoAxesCombined,\n  Check,\n  ChevronDown,\n  CircleHelp,\n  CirclePlus,\n  CreditCard,\n  LayoutDashboard,\n  Moon,\n  Settings,\n  Users,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"./shared/components/ui/dialog\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport { Field, FieldGroup, FieldLabel } from \"./shared/components/ui/field\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Progress } from \"./shared/components/ui/progress\";\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport function DashboardSidebar() {\n  const { isMobile, setOpenMobile, state } = useSidebar();\n  const [workspaces, setWorkspaces] = useState([\n    'Lumen Labs',\n    'Halcyon Studio',\n    'Pinecrest Co.',\n  ]);\n  const [workspace, setWorkspace] = useState('Lumen Labs');\n  const [creating, setCreating] = useState(false);\n  const [name, setName] = useState('');\n\n  return (\n    <>\n      <Sidebar collapsible=\"icon\">\n        <SidebarHeader className=\"px-3 pt-3.5 pb-5\">\n          <SidebarMenu>\n            <SidebarMenuItem>\n              <DropdownMenu>\n                <DropdownMenuTrigger\n                  render={\n                    <SidebarMenuButton\n                      aria-label={`${workspace}, switch workspace`}\n                      className=\"h-9 gap-2 rounded-lg px-2 font-medium group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0!\"\n                    />\n                  }\n                >\n                  <span className=\"flex size-5 shrink-0 items-center justify-center rounded-[5px] bg-blue-500 font-semibold text-[0.6875rem] text-white\">\n                    {workspace.slice(0, 1)}\n                  </span>\n                  <span className=\"flex-1 truncate group-data-[collapsible=icon]:hidden\">\n                    {workspace}\n                  </span>\n                  <ChevronDown className=\"text-muted-foreground group-data-[collapsible=icon]:hidden\" />\n                </DropdownMenuTrigger>\n                <DropdownMenuContent\n                  className={cn(\n                    'rounded-[10px] p-1.5',\n                    state === 'collapsed' && !isMobile\n                      ? 'w-56'\n                      : 'w-(--anchor-width)'\n                  )}\n                  side={state === 'collapsed' && !isMobile ? 'right' : 'bottom'}\n                  sideOffset={8}\n                >\n                  <DropdownMenuGroup>\n                    <DropdownMenuLabel className=\"px-2.5 py-2\">\n                      Workspaces\n                    </DropdownMenuLabel>\n                    {workspaces.map((item, index) => (\n                      <DropdownMenuItem\n                        className={cn(\n                          'min-h-9 gap-2.5 px-2.5',\n                          item === workspace && 'bg-accent'\n                        )}\n                        key={item}\n                        onClick={() => setWorkspace(item)}\n                      >\n                        <span\n                          className={cn(\n                            'flex size-5 shrink-0 items-center justify-center rounded-[5px] font-semibold text-[0.6875rem]',\n                            index === 0\n                              ? 'bg-blue-500 text-white'\n                              : 'bg-muted text-muted-foreground'\n                          )}\n                        >\n                          {item.slice(0, 1)}\n                        </span>\n                        <span className=\"flex-1 truncate\">{item}</span>\n                        {item === workspace && <Check aria-label=\"Selected\" />}\n                      </DropdownMenuItem>\n                    ))}\n                  </DropdownMenuGroup>\n                  <DropdownMenuSeparator />\n                  <DropdownMenuGroup>\n                    <DropdownMenuItem\n                      className=\"min-h-9 gap-2.5 px-2.5\"\n                      onClick={() => setCreating(true)}\n                    >\n                      <CirclePlus />\n                      Create workspace\n                    </DropdownMenuItem>\n                  </DropdownMenuGroup>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </SidebarMenuItem>\n          </SidebarMenu>\n        </SidebarHeader>\n        <SidebarContent>\n          <SidebarGroup className=\"px-3 py-0\">\n            <SidebarGroupLabel className=\"px-2.5 text-[0.6875rem] tracking-wider\">\n              WORKSPACE\n            </SidebarGroupLabel>\n            <SidebarMenu className=\"gap-0.5\">\n              {navigation.map((item) => (\n                <SidebarMenuItem\n                  className={\n                    item.label === 'Overview'\n                      ? 'before:-left-3 before:absolute before:top-2.5 before:h-4 before:w-0.75 before:rounded-r-sm before:bg-blue-500'\n                      : undefined\n                  }\n                  key={item.label}\n                >\n                  <SidebarMenuButton\n                    className=\"h-9 gap-2.5 rounded-lg px-2.5\"\n                    isActive={item.label === 'Overview'}\n                    onClick={() => setOpenMobile(false)}\n                    render={\n                      <a aria-label={item.label} href={item.href}>\n                        <item.icon\n                          aria-hidden=\"true\"\n                          className={\n                            item.label === 'Overview'\n                              ? undefined\n                              : 'text-muted-foreground'\n                          }\n                        />\n                        <span className=\"flex-1 group-data-[collapsible=icon]:hidden\">\n                          {item.label}\n                        </span>\n                        {item.label === 'Customers' && (\n                          <div className=\"rounded-full bg-muted px-1.5 text-[0.6875rem] text-muted-foreground tabular-nums group-data-[collapsible=icon]:hidden\">\n                            12\n                          </div>\n                        )}\n                      </a>\n                    }\n                    tooltip={item.label}\n                  />\n                </SidebarMenuItem>\n              ))}\n            </SidebarMenu>\n          </SidebarGroup>\n        </SidebarContent>\n        <SidebarFooter className=\"gap-3 px-3 pb-5\">\n          <SidebarMenu className=\"gap-0.5\">\n            <SidebarMenuItem>\n              <DropdownMenu>\n                <DropdownMenuTrigger\n                  render={\n                    <SidebarMenuButton\n                      aria-label=\"Settings\"\n                      className=\"h-9 gap-2.5 rounded-lg px-2.5\"\n                      tooltip=\"Settings\"\n                    />\n                  }\n                >\n                  <Settings className=\"text-muted-foreground\" />\n                  <span className=\"group-data-[collapsible=icon]:hidden\">\n                    Settings\n                  </span>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent\n                  className=\"w-64 rounded-[10px] p-1.5\"\n                  side=\"top\"\n                  sideOffset={8}\n                >\n                  <DropdownMenuGroup>\n                    <DropdownMenuItem\n                      className=\"min-h-9 gap-2.5 px-2.5\"\n                      render={\n                        <a\n                          aria-label=\"Company profile\"\n                          href=\"/settings/company\"\n                        >\n                          <BriefcaseBusiness />\n                          Company profile\n                        </a>\n                      }\n                    />\n                    <DropdownMenuItem\n                      className=\"min-h-9 gap-2.5 px-2.5\"\n                      render={\n                        <a aria-label=\"Team\" href=\"/settings/team\">\n                          <Users />\n                          Team\n                        </a>\n                      }\n                    />\n                    <DropdownMenuItem\n                      className=\"min-h-9 gap-2.5 px-2.5\"\n                      render={\n                        <a aria-label=\"Billing\" href=\"/settings/billing\">\n                          <CreditCard />\n                          Billing\n                        </a>\n                      }\n                    />\n                    <DropdownMenuItem\n                      className=\"min-h-9 gap-2.5 px-2.5\"\n                      closeOnClick={false}\n                      onClick={() =>\n                        document.documentElement.classList.toggle('dark')\n                      }\n                    >\n                      <Moon />\n                      <span className=\"flex-1\">Appearance</span>\n                      <div className=\"text-muted-foreground text-xs\">\n                        <span className=\"dark:hidden\">Light</span>\n                        <span className=\"not-dark:hidden\">Dark</span>\n                      </div>\n                    </DropdownMenuItem>\n                  </DropdownMenuGroup>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </SidebarMenuItem>\n            <SidebarMenuItem>\n              <SidebarMenuButton\n                className=\"h-9 gap-2.5 rounded-lg px-2.5\"\n                render={\n                  <a\n                    aria-label=\"Help and support\"\n                    href=\"mailto:support@lumenlabs.io\"\n                  >\n                    <CircleHelp className=\"text-muted-foreground\" />\n                    <span className=\"group-data-[collapsible=icon]:hidden\">\n                      Help &amp; support\n                    </span>\n                  </a>\n                }\n                tooltip=\"Help and support\"\n              />\n            </SidebarMenuItem>\n          </SidebarMenu>\n          <div className=\"flex flex-col gap-3 rounded-[10px] border border-border bg-background p-3 group-data-[collapsible=icon]:hidden dark:border-input dark:bg-input/30\">\n            <div className=\"flex items-center justify-between text-[0.8125rem]\">\n              <span className=\"font-medium\">Monthly volume</span>\n              <div className=\"text-[0.8125rem] text-muted-foreground tabular-nums\">\n                72%\n              </div>\n            </div>\n            <Progress\n              aria-label=\"Monthly volume used\"\n              className=\"[&_[data-slot=progress-indicator]]:bg-blue-500 [&_[data-slot=progress-track]]:bg-border\"\n              value={72.4}\n            />\n            <p className=\"text-muted-foreground text-xs tabular-nums\">\n              $72,400 of $100,000 used\n            </p>\n          </div>\n        </SidebarFooter>\n      </Sidebar>\n      <Dialog onOpenChange={setCreating} open={creating}>\n        <DialogContent>\n          <form\n            className=\"flex flex-col gap-5\"\n            onSubmit={(event) => {\n              event.preventDefault();\n              const nextName = name.trim();\n              if (!nextName) {\n                return;\n              }\n              if (!workspaces.includes(nextName)) {\n                setWorkspaces([...workspaces, nextName]);\n              }\n              setWorkspace(nextName);\n              setName('');\n              setCreating(false);\n            }}\n          >\n            <DialogHeader>\n              <DialogTitle>Create workspace</DialogTitle>\n              <DialogDescription>\n                Add a workspace to this dashboard preview.\n              </DialogDescription>\n            </DialogHeader>\n            <FieldGroup>\n              <Field>\n                <FieldLabel htmlFor=\"workspace-name\">Workspace name</FieldLabel>\n                <Input\n                  autoComplete=\"organization\"\n                  id=\"workspace-name\"\n                  maxLength={60}\n                  name=\"workspace-name\"\n                  onChange={(event) => setName(event.target.value)}\n                  pattern=\".*\\S.*\"\n                  required\n                  value={name}\n                />\n              </Field>\n            </FieldGroup>\n            <DialogFooter>\n              <Button\n                onClick={() => setCreating(false)}\n                type=\"button\"\n                variant=\"outline\"\n              >\n                Cancel\n              </Button>\n              <Button type=\"submit\">Create workspace</Button>\n            </DialogFooter>\n          </form>\n        </DialogContent>\n      </Dialog>\n    </>\n  );\n}\n\nconst navigation = [\n  { label: 'Overview', href: '#overview', icon: LayoutDashboard },\n  { label: 'Customers', href: '#customers', icon: Users },\n  { label: 'Transactions', href: '/transactions', icon: ArrowLeftRight },\n  { label: 'Payouts', href: '/payouts', icon: CreditCard },\n  { label: 'Reports', href: '#revenue', icon: ChartNoAxesCombined },\n];\n"
        },
        {
          "path": "licenses/dashboard-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dashboard-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/chart.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/chart.tsx",
          "content": "'use client';\n\nimport * as React from 'react';\nimport type { TooltipValueType } from 'recharts';\nimport * as RechartsPrimitive from 'recharts';\n\nimport { cn } from \"../../lib/utils\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: '', dark: '.dark' } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n  string,\n  {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n>;\n\ntype ChartContextProps = {\n  config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error('useChart must be used within a <ChartContainer />');\n  }\n\n  return context;\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  initialDimension = INITIAL_DIMENSION,\n  ...props\n}: React.ComponentProps<'div'> & {\n  config: ChartConfig;\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >['children'];\n  initialDimension?: {\n    width: number;\n    height: number;\n  };\n}) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`;\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        className={cn(\n          \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        data-chart={chartId}\n        data-slot=\"chart\"\n        {...props}\n      >\n        <ChartStyle config={config} id={chartId} />\n        <RechartsPrimitive.ResponsiveContainer\n          initialDimension={initialDimension}\n        >\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme ?? config.color\n  );\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??\n      itemConfig.color;\n    return color ? `  --color-${key}: ${color};` : null;\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n'),\n      }}\n    />\n  );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<'div'> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: 'line' | 'dot' | 'dashed';\n    nameKey?: string;\n    labelKey?: string;\n  } & Omit<\n    RechartsPrimitive.DefaultTooltipContentProps<\n      TooltipValueType,\n      TooltipNameType\n    >,\n    'accessibilityLayer'\n  >) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? 'value'}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === 'string'\n        ? (config[label]?.label ?? label)\n        : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn('font-medium', labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn('font-medium', labelClassName)}>{value}</div>;\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey,\n  ]);\n\n  if (!(active && payload?.length)) {\n    return null;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot';\n\n  return (\n    <div\n      className={cn(\n        'grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl',\n        className\n      )}\n    >\n      {nestLabel ? null : tooltipLabel}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== 'none')\n          .map((item, index) => {\n            const key = `${nameKey ?? item.name ?? item.dataKey ?? 'value'}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n            const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n            return (\n              <div\n                className={cn(\n                  'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground',\n                  indicator === 'dot' && 'items-center'\n                )}\n                key={index}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',\n                            {\n                              'h-2.5 w-2.5': indicator === 'dot',\n                              'w-1': indicator === 'line',\n                              'w-0 border-[1.5px] border-dashed bg-transparent':\n                                indicator === 'dashed',\n                              'my-0.5': nestLabel && indicator === 'dashed',\n                            }\n                          )}\n                          style={\n                            {\n                              '--color-bg': indicatorColor,\n                              '--color-border': indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        'flex flex-1 justify-between leading-none',\n                        nestLabel ? 'items-end' : 'items-center'\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label ?? item.name}\n                        </span>\n                      </div>\n                      {item.value != null && (\n                        <span className=\"font-medium font-mono text-foreground tabular-nums\">\n                          {typeof item.value === 'number'\n                            ? item.value.toLocaleString()\n                            : String(item.value)}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = 'bottom',\n  nameKey,\n}: React.ComponentProps<'div'> & {\n  hideIcon?: boolean;\n  nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-center justify-center gap-4',\n        verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== 'none')\n        .map((item, index) => {\n          const key = `${nameKey ?? item.dataKey ?? 'value'}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n          return (\n            <div\n              className={cn(\n                'flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground'\n              )}\n              key={index}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== 'object' || payload === null) {\n    return;\n  }\n\n  const payloadPayload =\n    'payload' in payload &&\n    typeof payload.payload === 'object' &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === 'string'\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string;\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n};\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/field.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/field.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useMemo } from 'react';\nimport { Label } from \"./label\";\nimport { Separator } from \"./separator\";\nimport { cn } from \"../../lib/utils\";\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n  return (\n    <fieldset\n      className={cn(\n        'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',\n        className\n      )}\n      data-slot=\"field-set\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLegend({\n  className,\n  variant = 'legend',\n  ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n  return (\n    <legend\n      className={cn(\n        'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',\n        className\n      )}\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',\n        className\n      )}\n      data-slot=\"field-group\"\n      {...props}\n    />\n  );\n}\n\nconst fieldVariants = cva(\n  'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',\n  {\n    variants: {\n      orientation: {\n        vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',\n        horizontal:\n          'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        responsive:\n          '@md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n      },\n    },\n    defaultVariants: {\n      orientation: 'vertical',\n    },\n  }\n);\n\nfunction Field({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      className={cn(fieldVariants({ orientation }), className)}\n      data-orientation={orientation}\n      data-slot=\"field\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-content flex flex-1 flex-col gap-0.5 leading-snug',\n        className\n      )}\n      data-slot=\"field-content\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      className={cn(\n        'group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 *:data-[slot=field]:p-2.5 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',\n        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex w-fit items-center gap-2 font-medium text-sm group-data-[disabled=true]/field:opacity-50',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      className={cn(\n        '[[data-variant=legend]+&]:-mt-1.5 text-left font-normal text-muted-foreground text-sm leading-normal group-has-data-horizontal/field:text-balance',\n        'nth-last-2:-mt-1 last:mt-0',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      data-slot=\"field-description\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <div\n      className={cn(\n        '-my-2 group-data-[variant=outline]/field-group:-mb-2 relative h-5 text-sm',\n        className\n      )}\n      data-content={!!children}\n      data-slot=\"field-separator\"\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<'div'> & {\n  errors?: Array<{ message?: string } | undefined>;\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children;\n    }\n\n    if (!errors?.length) {\n      return null;\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ];\n\n    if (uniqueErrors?.length === 1) {\n      return uniqueErrors[0]?.message;\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    );\n  }, [children, errors]);\n\n  if (!content) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn('font-normal text-destructive text-sm', className)}\n      data-slot=\"field-error\"\n      role=\"alert\"\n      {...props}\n    >\n      {content}\n    </div>\n  );\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/popover.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/popover.tsx",
          "content": "'use client';\n\nimport { Popover as PopoverPrimitive } from '@base-ui/react/popover';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Popover({ ...props }: PopoverPrimitive.Root.Props) {\n  return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {\n  return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverContent({\n  className,\n  align = 'center',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  ...props\n}: PopoverPrimitive.Popup.Props &\n  Pick<\n    PopoverPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <PopoverPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-popover-foreground text-sm shadow-md outline-hidden ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-slot=\"popover-content\"\n          {...props}\n        />\n      </PopoverPrimitive.Positioner>\n    </PopoverPrimitive.Portal>\n  );\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 text-sm', className)}\n      data-slot=\"popover-header\"\n      {...props}\n    />\n  );\n}\n\nfunction PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {\n  return (\n    <PopoverPrimitive.Title\n      className={cn('font-medium', className)}\n      data-slot=\"popover-title\"\n      {...props}\n    />\n  );\n}\n\nfunction PopoverDescription({\n  className,\n  ...props\n}: PopoverPrimitive.Description.Props) {\n  return (\n    <PopoverPrimitive.Description\n      className={cn('text-muted-foreground', className)}\n      data-slot=\"popover-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Popover,\n  PopoverContent,\n  PopoverDescription,\n  PopoverHeader,\n  PopoverTitle,\n  PopoverTrigger,\n};\n"
        },
        {
          "path": "components/ui/progress.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/progress.tsx",
          "content": "'use client';\n\nimport { Progress as ProgressPrimitive } from '@base-ui/react/progress';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Progress({\n  className,\n  children,\n  value,\n  ...props\n}: ProgressPrimitive.Root.Props) {\n  return (\n    <ProgressPrimitive.Root\n      className={cn('flex flex-wrap gap-3', className)}\n      data-slot=\"progress\"\n      value={value}\n      {...props}\n    >\n      {children}\n      <ProgressTrack>\n        <ProgressIndicator />\n      </ProgressTrack>\n    </ProgressPrimitive.Root>\n  );\n}\n\nfunction ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {\n  return (\n    <ProgressPrimitive.Track\n      className={cn(\n        'relative flex h-1 w-full items-center overflow-x-hidden rounded-full bg-muted',\n        className\n      )}\n      data-slot=\"progress-track\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressIndicator({\n  className,\n  ...props\n}: ProgressPrimitive.Indicator.Props) {\n  return (\n    <ProgressPrimitive.Indicator\n      className={cn('h-full bg-primary transition-all', className)}\n      data-slot=\"progress-indicator\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {\n  return (\n    <ProgressPrimitive.Label\n      className={cn('font-medium text-sm', className)}\n      data-slot=\"progress-label\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {\n  return (\n    <ProgressPrimitive.Value\n      className={cn(\n        'ml-auto text-muted-foreground text-sm tabular-nums',\n        className\n      )}\n      data-slot=\"progress-value\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Progress,\n  ProgressTrack,\n  ProgressIndicator,\n  ProgressLabel,\n  ProgressValue,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/sheet.tsx",
          "content": "'use client';\n\nimport { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"sheet-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        className={cn(\n          'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sheet-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                className=\"absolute top-3 right-3\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 p-4', className)}\n      data-slot=\"sheet-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      data-slot=\"sheet-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      className={cn('font-medium text-base text-foreground', className)}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/sidebar.tsx",
          "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport { cn } from \"../../lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className\n        )}\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className\n        )}\n        data-slot=\"sidebar\"\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>\n        <SheetContent\n          className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n          data-mobile=\"true\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          dir={dir}\n          side={side}\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-side={side}\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-variant={variant}\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n        data-slot=\"sidebar-gap\"\n      />\n      <div\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sidebar-container\"\n        {...props}\n      >\n        <div\n          className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      className={cn(className)}\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      className={cn(\n        'group-data-[side=left]:-right-4 ltr:-translate-x-1/2 rtl:-translate-x-1/2 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      className={cn(\n        'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',\n        className\n      )}\n      data-slot=\"sidebar-inset\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      className={cn('h-8 w-full bg-background shadow-none', className)}\n      data-sidebar=\"input\"\n      data-slot=\"sidebar-input\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"header\"\n      data-slot=\"sidebar-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"footer\"\n      data-slot=\"sidebar-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      data-sidebar=\"separator\"\n      data-slot=\"sidebar-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      data-sidebar=\"content\"\n      data-slot=\"sidebar-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      data-sidebar=\"group\"\n      data-slot=\"sidebar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'group-data-[collapsible=icon]:-mt-8 flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label',\n    },\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action',\n    },\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('w-full text-sm', className)}\n      data-sidebar=\"group-content\"\n      data-slot=\"sidebar-group-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn('flex w-full min-w-0 flex-col gap-0', className)}\n      data-sidebar=\"menu\"\n      data-slot=\"sidebar-menu\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-item relative', className)}\n      data-sidebar=\"menu-item\"\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n      },\n      props\n    ),\n    render: tooltip ? <TooltipTrigger render={render} /> : render,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive,\n    },\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        side=\"right\"\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          showOnHover &&\n            'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action',\n    },\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',\n        className\n      )}\n      data-sidebar=\"menu-badge\"\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      data-sidebar=\"menu-skeleton\"\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      data-sidebar=\"menu-sub\"\n      data-slot=\"sidebar-menu-sub\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-sub-item relative', className)}\n      data-sidebar=\"menu-sub-item\"\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          '-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive,\n    },\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/skeleton.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('animate-pulse rounded-md bg-muted', className)}\n      data-slot=\"skeleton\"\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
        },
        {
          "path": "components/ui/table.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/table.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div\n      className=\"relative w-full overflow-x-auto\"\n      data-slot=\"table-container\"\n    >\n      <table\n        className={cn('w-full caption-bottom text-sm', className)}\n        data-slot=\"table\"\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return (\n    <thead\n      className={cn('[&_tr]:border-b', className)}\n      data-slot=\"table-header\"\n      {...props}\n    />\n  );\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      className={cn('[&_tr:last-child]:border-0', className)}\n      data-slot=\"table-body\"\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      className={cn(\n        'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',\n        className\n      )}\n      data-slot=\"table-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      className={cn(\n        'border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',\n        className\n      )}\n      data-slot=\"table-row\"\n      {...props}\n    />\n  );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      className={cn(\n        'h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-head\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      className={cn(\n        'whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-cell\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      className={cn('mt-4 text-muted-foreground text-sm', className)}\n      data-slot=\"table-caption\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n};\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "components/ui/toggle-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/toggle-group.tsx",
          "content": "'use client';\n\nimport { Toggle as TogglePrimitive } from '@base-ui/react/toggle';\nimport { ToggleGroup as ToggleGroupPrimitive } from '@base-ui/react/toggle-group';\nimport type { VariantProps } from 'class-variance-authority';\nimport * as React from 'react';\nimport { toggleVariants } from \"./toggle\";\nimport { cn } from \"../../lib/utils\";\n\nconst ToggleGroupContext = React.createContext<\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number;\n    orientation?: 'horizontal' | 'vertical';\n  }\n>({\n  size: 'default',\n  variant: 'default',\n  spacing: 2,\n  orientation: 'horizontal',\n});\n\nfunction ToggleGroup({\n  className,\n  variant,\n  size,\n  spacing = 2,\n  orientation = 'horizontal',\n  children,\n  ...props\n}: ToggleGroupPrimitive.Props &\n  VariantProps<typeof toggleVariants> & {\n    spacing?: number;\n    orientation?: 'horizontal' | 'vertical';\n  }) {\n  return (\n    <ToggleGroupPrimitive\n      className={cn(\n        'group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-lg data-vertical:flex-col data-vertical:items-stretch data-[size=sm]:rounded-[min(var(--radius-md),10px)]',\n        className\n      )}\n      data-orientation={orientation}\n      data-size={size}\n      data-slot=\"toggle-group\"\n      data-spacing={spacing}\n      data-variant={variant}\n      style={{ '--gap': spacing } as React.CSSProperties}\n      {...props}\n    >\n      <ToggleGroupContext.Provider\n        value={{ variant, size, spacing, orientation }}\n      >\n        {children}\n      </ToggleGroupContext.Provider>\n    </ToggleGroupPrimitive>\n  );\n}\n\nfunction ToggleGroupItem({\n  className,\n  children,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {\n  const context = React.useContext(ToggleGroupContext);\n\n  return (\n    <TogglePrimitive\n      className={cn(\n        'shrink-0 focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:rounded-none group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-lg',\n        toggleVariants({\n          variant: context.variant || variant,\n          size: context.size || size,\n        }),\n        className\n      )}\n      data-size={context.size || size}\n      data-slot=\"toggle-group-item\"\n      data-spacing={context.spacing}\n      data-variant={context.variant || variant}\n      {...props}\n    >\n      {children}\n    </TogglePrimitive>\n  );\n}\n\nexport { ToggleGroup, ToggleGroupItem };\n"
        },
        {
          "path": "components/ui/toggle.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/toggle.tsx",
          "content": "'use client';\n\nimport { Toggle as TogglePrimitive } from '@base-ui/react/toggle';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst toggleVariants = cva(\n  \"group/toggle inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-lg font-medium text-sm outline-none transition-all hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-pressed:bg-muted aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-transparent',\n        outline: 'border border-input bg-transparent hover:bg-muted',\n      },\n      size: {\n        default:\n          'h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        sm: \"h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Toggle({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive\n      className={cn(toggleVariants({ variant, size, className }))}\n      data-slot=\"toggle\"\n      {...props}\n    />\n  );\n}\n\nexport { Toggle, toggleVariants };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/hooks/use-mobile.ts",
          "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dashboard-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dashboard",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dashboard-01/app/page.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dashboard/dashboard-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dashboard",
          "analytics",
          "customers",
          "revenue",
          "后台",
          "仪表盘",
          "分析",
          "客户"
        ],
        "contentGuidelines": [
          "Keep one primary dashboard question and a consistent reporting period.",
          "Replace demo data and verify charts with sparse and long datasets."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dashboard-01/app/page.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dashboard-01"
      }
    },
    {
      "name": "dialog-01",
      "type": "registry:block",
      "title": "Dialog Confirmation",
      "description": "Dialog Confirmation. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-01/dialog-01.tsx",
          "content": "'use client';\n\nimport { Check } from 'lucide-react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\n\nexport default function Dialog01() {\n  return (\n    <Dialog defaultOpen>\n      <DialogTrigger render={<Button variant=\"outline\" />}>\n        Show Dialog\n      </DialogTrigger>\n\n      <DialogContent className=\"sm:max-w-sm\">\n        <div className=\"flex justify-center\">\n          <div className=\"mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100\">\n            <Check className=\"h-6 w-6 text-green-600\" />\n          </div>\n        </div>\n\n        <DialogHeader className=\"gap-0 text-center\">\n          <DialogTitle className=\"text-balance text-center\">\n            Payment successful\n          </DialogTitle>\n          <DialogDescription className=\"mx-auto mt-2 text-pretty text-center sm:max-w-[90%]\">\n            Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur\n            amet labore.\n          </DialogDescription>\n        </DialogHeader>\n        <DialogFooter className=\"sm:justify-center\">\n          <DialogClose render={<Button className=\"w-full\" />}>\n            Go back to dashboard\n          </DialogClose>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-01/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-01/dialog-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-01/dialog-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-01"
      }
    },
    {
      "name": "dialog-02",
      "type": "registry:block",
      "title": "Dialog with Warning Icon",
      "description": "Dialog with Warning Icon. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-02/dialog-02.tsx",
          "content": "'use client';\n\nimport { Check } from 'lucide-react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\n\nexport default function Dialog04() {\n  return (\n    <Dialog defaultOpen>\n      <DialogTrigger render={<Button variant=\"outline\" />}>\n        Show Dialog\n      </DialogTrigger>\n\n      <DialogContent className=\"sm:max-w-sm\">\n        <div className=\"flex justify-center\">\n          <div className=\"mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100\">\n            <Check className=\"h-6 w-6 text-green-600\" />\n          </div>\n        </div>\n\n        <DialogHeader className=\"gap-0 text-center\">\n          <DialogTitle className=\"text-balance text-center\">\n            Payment successful\n          </DialogTitle>\n          <DialogDescription className=\"mx-auto mt-2 text-pretty text-center sm:max-w-[90%]\">\n            Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur\n            amet labore.\n          </DialogDescription>\n        </DialogHeader>\n        <DialogFooter className=\"flex-col gap-2 sm:flex-row sm:gap-3\">\n          <DialogClose\n            render={<Button className=\"w-full sm:flex-1\" variant=\"default\" />}\n          >\n            Deactivate\n          </DialogClose>\n          <DialogClose\n            render={<Button className=\"w-full sm:flex-1\" variant=\"outline\" />}\n          >\n            Cancel\n          </DialogClose>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-02/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-02/dialog-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-02/dialog-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-02"
      }
    },
    {
      "name": "dialog-03",
      "type": "registry:block",
      "title": "Dialog with Input Field",
      "description": "Dialog with Input Field. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-03/dialog-03.tsx",
          "content": "'use client';\nimport { AlertTriangleIcon } from 'lucide-react';\nimport { useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\n\nexport default function Dialog02() {\n  const [open, setOpen] = useState(true);\n\n  return (\n    <Dialog onOpenChange={setOpen} open={open}>\n      <DialogTrigger render={<Button variant=\"destructive\" />}>\n        Deactivate\n      </DialogTrigger>\n      <DialogContent className=\"sm:max-w-lg\">\n        <div className=\"flex items-start space-x-4\">\n          <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-red-100\">\n            <AlertTriangleIcon className=\"h-6 w-6 text-red-600\" />\n          </div>\n          <DialogHeader>\n            <DialogTitle>Deactivate account</DialogTitle>\n            <DialogDescription>\n              Are you sure you want to deactivate your account? All of your data\n              will be permanently removed. This action cannot be undone.\n            </DialogDescription>\n          </DialogHeader>\n        </div>\n        <DialogFooter>\n          <DialogClose render={<Button variant=\"outline\" />}>\n            Cancel\n          </DialogClose>\n          <Button onClick={() => setOpen(false)} variant=\"destructive\">\n            Deactivate\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-03/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-03/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-03/dialog-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-03/dialog-03.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-03"
      }
    },
    {
      "name": "dialog-04",
      "type": "registry:block",
      "title": "Dialog with Two Buttons",
      "description": "Dialog with Two Buttons. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-04/dialog-04.tsx",
          "content": "'use client';\n\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\nexport default function Dialog03() {\n  return (\n    <Dialog defaultOpen>\n      <div className=\"flex items-center justify-center py-36\">\n        <DialogTrigger render={<Button />}>Edit Profile</DialogTrigger>\n      </div>\n\n      <DialogContent className=\"sm:max-w-md\">\n        <DialogHeader>\n          <DialogTitle>Edit Profile</DialogTitle>\n          <DialogDescription>\n            Make changes to your profile here. Click save when you&apos;re done.\n          </DialogDescription>\n        </DialogHeader>\n        <div className=\"space-y-2\">\n          <Label className=\"font-medium text-sm\" htmlFor=\"username\">\n            Username\n          </Label>\n          <Input\n            id=\"username\"\n            name=\"username\"\n            placeholder=\"Your username\"\n            type=\"text\"\n          />\n        </div>\n        <DialogFooter>\n          <Button className=\"w-full\" type=\"submit\">\n            Save Changes\n          </Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-04/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-04/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-04/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-04/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-04/dialog-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-04/dialog-04.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-04"
      }
    },
    {
      "name": "dialog-05",
      "type": "registry:block",
      "title": "Dialog with Password Confirmation",
      "description": "Dialog with Password Confirmation. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-05.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-05/dialog-05.tsx",
          "content": "'use client';\n\nimport { EyeIcon, EyeOffIcon } from 'lucide-react';\nimport { type FormEvent, useState } from 'react';\nimport {\n  AlertDialog,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"./shared/components/ui/alert-dialog\";\nimport { Button } from \"./shared/components/ui/button\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\nexport default function Dialog05() {\n  const [isOpen, setIsOpen] = useState(true);\n  const [isVisible, setIsVisible] = useState<boolean>(false);\n\n  const toggleVisibility = () => setIsVisible((prevState) => !prevState);\n  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n    setIsOpen(false);\n  };\n\n  return (\n    <AlertDialog onOpenChange={setIsOpen} open={isOpen}>\n      <AlertDialogTrigger render={<Button variant=\"destructive\" />}>\n        Delete Workspace\n      </AlertDialogTrigger>\n      <AlertDialogContent className=\"sm:max-w-md\">\n        <AlertDialogHeader>\n          <AlertDialogTitle className=\"text-balance\">\n            Delete workspace\n          </AlertDialogTitle>\n          <AlertDialogDescription className=\"text-pretty\">\n            All workspace data will be permanently deleted. There is no coming\n            back after you press delete.\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <form onSubmit={handleSubmit}>\n          <div>\n            <Label className=\"font-medium text-sm\" htmlFor=\"delete-workspace\">\n              Confirm password\n            </Label>\n            <div className=\"relative mt-2\">\n              <Input\n                className=\"pe-9\"\n                id=\"delete-workspace\"\n                name=\"delete-workspace\"\n                placeholder=\"Password\"\n                required\n                type={isVisible ? 'text' : 'password'}\n              />\n              <button\n                aria-controls=\"delete-workspace\"\n                aria-label={isVisible ? 'Hide password' : 'Show password'}\n                aria-pressed={isVisible}\n                className=\"absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md\"\n                onClick={toggleVisibility}\n                type=\"button\"\n              >\n                {isVisible ? (\n                  <EyeOffIcon aria-hidden=\"true\" size={16} />\n                ) : (\n                  <EyeIcon aria-hidden=\"true\" size={16} />\n                )}\n              </button>\n            </div>\n          </div>\n          <AlertDialogFooter className=\"mt-4\">\n            <AlertDialogCancel>Cancel</AlertDialogCancel>\n            <Button type=\"submit\" variant=\"destructive\">\n              Delete workspace permanently\n            </Button>\n          </AlertDialogFooter>\n        </form>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/alert-dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-05/shared/components/ui/alert-dialog.tsx",
          "content": "'use client';\n\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {\n  return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />;\n}\n\nfunction AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {\n  return (\n    <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />\n  );\n}\n\nfunction AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {\n  return (\n    <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />\n  );\n}\n\nfunction AlertDialogOverlay({\n  className,\n  ...props\n}: AlertDialogPrimitive.Backdrop.Props) {\n  return (\n    <AlertDialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"alert-dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogContent({\n  className,\n  size = 'default',\n  ...props\n}: AlertDialogPrimitive.Popup.Props & {\n  size?: 'default' | 'sm';\n}) {\n  return (\n    <AlertDialogPortal>\n      <AlertDialogOverlay />\n      <AlertDialogPrimitive.Popup\n        className={cn(\n          'group/alert-dialog-content -translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full gap-4 rounded-xl bg-popover p-4 text-popover-foreground outline-none ring-1 ring-foreground/10 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-closed:animate-out data-open:animate-in data-[size=default]:sm:max-w-sm',\n          className\n        )}\n        data-size={size}\n        data-slot=\"alert-dialog-content\"\n        {...props}\n      />\n    </AlertDialogPortal>\n  );\n}\n\nfunction AlertDialogHeader({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',\n        className\n      )}\n      data-slot=\"alert-dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogFooter({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"alert-dialog-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogMedia({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        \"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6\",\n        className\n      )}\n      data-slot=\"alert-dialog-media\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {\n  return (\n    <AlertDialogPrimitive.Title\n      className={cn(\n        'font-medium text-base sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',\n        className\n      )}\n      data-slot=\"alert-dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {\n  return (\n    <AlertDialogPrimitive.Description\n      className={cn(\n        'text-balance text-muted-foreground text-sm md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"alert-dialog-description\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogAction({\n  className,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  return (\n    <Button\n      className={cn(className)}\n      data-slot=\"alert-dialog-action\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogCancel({\n  className,\n  variant = 'outline',\n  size = 'default',\n  ...props\n}: AlertDialogPrimitive.Close.Props &\n  Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {\n  return (\n    <AlertDialogPrimitive.Close\n      className={cn(className)}\n      data-slot=\"alert-dialog-cancel\"\n      render={<Button size={size} variant={variant} />}\n      {...props}\n    />\n  );\n}\n\nexport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogMedia,\n  AlertDialogOverlay,\n  AlertDialogPortal,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-05/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-05/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-05/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-05/dialog-05.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-05/dialog-05.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-05"
      }
    },
    {
      "name": "dialog-06",
      "type": "registry:block",
      "title": "Dialog with Privacy Toggle",
      "description": "Dialog with Privacy Toggle. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-06.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-06/dialog-06.tsx",
          "content": "'use client';\n\nimport { useState } from 'react';\n\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport { Switch } from \"./shared/components/ui/switch\";\n\nexport default function Dialog06() {\n  const [open, setOpen] = useState(true);\n\n  return (\n    <Dialog onOpenChange={setOpen} open={open}>\n      <DialogTrigger render={<Button onClick={() => setOpen(true)} />}>\n        Create Workspace\n      </DialogTrigger>\n      <DialogContent className=\"p-0 sm:max-w-lg\">\n        <DialogHeader className=\"px-6 pt-4\">\n          <DialogTitle className=\"text-balance font-semibold text-foreground text-lg\">\n            Create workspace\n          </DialogTitle>\n          <DialogDescription className=\"mt-2 text-pretty text-muted-foreground text-sm leading-6\">\n            Workspaces are shared environments where teams can connect to data\n            sources, run queries and create reports.\n          </DialogDescription>\n        </DialogHeader>\n\n        <form action=\"#\" method=\"POST\">\n          <div className=\"px-6 pb-4\">\n            <Label className=\"font-medium text-sm\" htmlFor=\"workspace-name\">\n              Workspace Name<span className=\"text-destructive\">*</span>\n            </Label>\n            <Input\n              className=\"mt-2\"\n              id=\"workspace-name\"\n              name=\"workspace-name\"\n              placeholder=\"My workspace\"\n              required\n              type=\"text\"\n            />\n            <Button className=\"mt-4 w-full\" type=\"submit\">\n              Create workspace\n            </Button>\n          </div>\n          <div className=\"rounded-b-md border-t bg-muted px-6 py-4\">\n            <div className=\"flex items-start space-x-3\">\n              <div className=\"mt-1 pt-0.5\">\n                <Switch\n                  id=\"enable-private-workspace\"\n                  name=\"enable-private-workspace\"\n                />\n              </div>\n              <div>\n                <Label\n                  className=\"font-medium text-sm\"\n                  htmlFor=\"enable-private-workspace\"\n                >\n                  Set workspace to private\n                </Label>\n                <p className=\"text-pretty text-muted-foreground text-sm\">\n                  Only those invited can access or view\n                </p>\n              </div>\n            </div>\n          </div>\n        </form>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-06/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-06/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-06/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-06/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-06/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-06/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/switch.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-06/shared/components/ui/switch.tsx",
          "content": "'use client';\n\nimport { Switch as SwitchPrimitive } from '@base-ui/react/switch';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Switch({\n  className,\n  size = 'default',\n  ...props\n}: SwitchPrimitive.Root.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SwitchPrimitive.Root\n      className={cn(\n        'peer group/switch after:-inset-x-3 after:-inset-y-2 relative inline-flex shrink-0 items-center rounded-full border border-transparent outline-none transition-all after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=sm]:h-[14px] data-[size=default]:w-[32px] data-[size=sm]:w-[24px] data-disabled:cursor-not-allowed data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-50 dark:data-unchecked:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-size={size}\n      data-slot=\"switch\"\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        className=\"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground\"\n        data-slot=\"switch-thumb\"\n      />\n    </SwitchPrimitive.Root>\n  );\n}\n\nexport { Switch };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-06/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-06/dialog-06.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-06 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-06/dialog-06.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-06"
      }
    },
    {
      "name": "dialog-07",
      "type": "registry:block",
      "title": "Dialog with Member List",
      "description": "Dialog with Member List. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-07.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-07/dialog-07.tsx",
          "content": "'use client';\n\nimport { EyeIcon, EyeOffIcon } from 'lucide-react';\nimport { type FormEvent, useState } from 'react';\nimport {\n  AlertDialog,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"./shared/components/ui/alert-dialog\";\nimport { Button } from \"./shared/components/ui/button\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\nexport default function Dialog08() {\n  const [open, setOpen] = useState(true);\n  const [isVisible, setIsVisible] = useState<boolean>(false);\n\n  const toggleVisibility = () => setIsVisible((prevState) => !prevState);\n  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n    setOpen(false);\n  };\n\n  return (\n    <AlertDialog onOpenChange={setOpen} open={open}>\n      <AlertDialogTrigger render={<Button variant=\"destructive\" />}>\n        Deactivate 2FA\n      </AlertDialogTrigger>\n      <AlertDialogContent className=\"sm:max-w-lg\">\n        <AlertDialogHeader>\n          <AlertDialogTitle className=\"text-balance\">\n            Deactivate two-step authentication\n          </AlertDialogTitle>\n          <AlertDialogDescription className=\"text-pretty\">\n            Enter your password to deactivate the two-step authentication login.\n            Make sure to have your smartphone ready.\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <form className=\"space-y-4\" onSubmit={handleSubmit}>\n          <div>\n            <Label className=\"font-medium text-sm\" htmlFor=\"email\">\n              Email\n            </Label>\n            <Input\n              className=\"mt-2\"\n              disabled\n              id=\"email\"\n              name=\"email\"\n              placeholder=\"ephraim@blocks.so\"\n              type=\"email\"\n            />\n          </div>\n          <div>\n            <Label className=\"font-medium text-sm\" htmlFor=\"password\">\n              Confirm password\n            </Label>\n            <div className=\"relative mt-2\">\n              <Input\n                className=\"pe-9\"\n                id=\"password\"\n                name=\"password\"\n                placeholder=\"Password\"\n                required\n                type={isVisible ? 'text' : 'password'}\n              />\n              <button\n                aria-controls=\"password\"\n                aria-label={isVisible ? 'Hide password' : 'Show password'}\n                aria-pressed={isVisible}\n                className=\"absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md\"\n                onClick={toggleVisibility}\n                type=\"button\"\n              >\n                {isVisible ? (\n                  <EyeOffIcon aria-hidden=\"true\" size={16} />\n                ) : (\n                  <EyeIcon aria-hidden=\"true\" size={16} />\n                )}\n              </button>\n            </div>\n          </div>\n\n          <AlertDialogFooter>\n            <AlertDialogCancel>Cancel</AlertDialogCancel>\n            <Button type=\"submit\" variant=\"destructive\">\n              Deactivate\n            </Button>\n          </AlertDialogFooter>\n        </form>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-07/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-07/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/alert-dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-07/shared/components/ui/alert-dialog.tsx",
          "content": "'use client';\n\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {\n  return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />;\n}\n\nfunction AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {\n  return (\n    <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />\n  );\n}\n\nfunction AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {\n  return (\n    <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />\n  );\n}\n\nfunction AlertDialogOverlay({\n  className,\n  ...props\n}: AlertDialogPrimitive.Backdrop.Props) {\n  return (\n    <AlertDialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"alert-dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogContent({\n  className,\n  size = 'default',\n  ...props\n}: AlertDialogPrimitive.Popup.Props & {\n  size?: 'default' | 'sm';\n}) {\n  return (\n    <AlertDialogPortal>\n      <AlertDialogOverlay />\n      <AlertDialogPrimitive.Popup\n        className={cn(\n          'group/alert-dialog-content -translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full gap-4 rounded-xl bg-popover p-4 text-popover-foreground outline-none ring-1 ring-foreground/10 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-closed:animate-out data-open:animate-in data-[size=default]:sm:max-w-sm',\n          className\n        )}\n        data-size={size}\n        data-slot=\"alert-dialog-content\"\n        {...props}\n      />\n    </AlertDialogPortal>\n  );\n}\n\nfunction AlertDialogHeader({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',\n        className\n      )}\n      data-slot=\"alert-dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogFooter({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"alert-dialog-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogMedia({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        \"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6\",\n        className\n      )}\n      data-slot=\"alert-dialog-media\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogTitle({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {\n  return (\n    <AlertDialogPrimitive.Title\n      className={cn(\n        'font-medium text-base sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',\n        className\n      )}\n      data-slot=\"alert-dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogDescription({\n  className,\n  ...props\n}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {\n  return (\n    <AlertDialogPrimitive.Description\n      className={cn(\n        'text-balance text-muted-foreground text-sm md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"alert-dialog-description\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogAction({\n  className,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  return (\n    <Button\n      className={cn(className)}\n      data-slot=\"alert-dialog-action\"\n      {...props}\n    />\n  );\n}\n\nfunction AlertDialogCancel({\n  className,\n  variant = 'outline',\n  size = 'default',\n  ...props\n}: AlertDialogPrimitive.Close.Props &\n  Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) {\n  return (\n    <AlertDialogPrimitive.Close\n      className={cn(className)}\n      data-slot=\"alert-dialog-cancel\"\n      render={<Button size={size} variant={variant} />}\n      {...props}\n    />\n  );\n}\n\nexport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogMedia,\n  AlertDialogOverlay,\n  AlertDialogPortal,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-07/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-07/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-07/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-07/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-07/dialog-07.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-07 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-07/dialog-07.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-07"
      }
    },
    {
      "name": "dialog-08",
      "type": "registry:block",
      "title": "Dialog with Email and Password Fields",
      "description": "Dialog with Email and Password Fields. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-08.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-08/dialog-08.tsx",
          "content": "'use client';\n\nimport { UserPlus } from 'lucide-react';\nimport { useState } from 'react';\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport { Badge } from \"./shared/components/ui/badge\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\nimport { Input } from \"./shared/components/ui/input\";\n\nconst members = [\n  {\n    name: 'Ephraim Duncan',\n    email: 'ephraim@documenso.com',\n    avatarUrl: 'https://blocks.so/avatar-01.png',\n    initials: 'ED',\n    status: 'member',\n  },\n  {\n    name: 'Lucas Smith',\n    email: 'lucas@documenso.com',\n    avatarUrl: 'https://blocks.so/avatar-03.png',\n    initials: 'LS',\n    status: 'member',\n  },\n  {\n    name: 'Timur Ercan',\n    email: 'timur@documenso.com',\n    avatarUrl: 'https://blocks.so/avatar-02.jpg',\n    initials: 'TE',\n    status: 'member',\n  },\n  {\n    name: 'Catalin Pit',\n    email: 'catalin@documenso.com',\n    avatarUrl: 'https://blocks.so/avatar-04.jpg',\n    initials: 'CP',\n    status: 'member',\n  },\n];\n\nexport default function Dialog07() {\n  const [open, setOpen] = useState(true);\n\n  return (\n    <Dialog onOpenChange={setOpen} open={open}>\n      <DialogTrigger render={<Button onClick={() => setOpen(true)} />}>\n        Invite members\n      </DialogTrigger>\n      <DialogContent className=\"sm:max-w-2xl\">\n        <DialogHeader>\n          <DialogTitle className=\"text-balance font-semibold text-foreground\">\n            Invite members\n          </DialogTitle>\n          <DialogDescription className=\"text-pretty text-muted-foreground text-sm leading-6\">\n            Add new team members to your workspace. Please consider your\n            organization&apos;s policies when adding external people.\n          </DialogDescription>\n        </DialogHeader>\n        <form>\n          <div className=\"flex w-full items-center space-x-2\">\n            <div className=\"relative flex-1\">\n              <UserPlus className=\"-translate-y-1/2 absolute top-1/2 left-3 h-4 w-4 text-muted-foreground\" />\n              <Input\n                className=\"h-10 pl-9\"\n                id=\"inviteEmail\"\n                placeholder=\"Add email...\"\n                type=\"email\"\n              />\n            </div>\n            <Button className=\"h-10\" type=\"submit\">\n              Invite\n            </Button>\n          </div>\n        </form>\n        <h4 className=\"mt-4 text-balance font-medium text-foreground text-sm\">\n          People with existing access\n        </h4>\n        <ul className=\"divide-y\">\n          {members.map((member) => (\n            <li\n              className=\"flex items-center justify-between py-2.5\"\n              key={member.name}\n            >\n              <div className=\"flex items-center space-x-3\">\n                <Avatar className=\"h-9 w-9\">\n                  <AvatarImage alt={member.name} src={member.avatarUrl} />\n                  <AvatarFallback>{member.initials}</AvatarFallback>\n                </Avatar>\n                <span className=\"font-medium text-foreground\">\n                  {member.name}\n                </span>\n              </div>\n              <Badge\n                className=\"bg-background font-medium text-xs\"\n                variant=\"outline\"\n              >\n                {member.status}\n              </Badge>\n            </li>\n          ))}\n        </ul>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-08/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-08/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-08/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-08/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-08/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-08/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-08/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-08/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-08/dialog-08.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-08 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-08/dialog-08.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-08"
      }
    },
    {
      "name": "dialog-09",
      "type": "registry:block",
      "title": "Dialog with Link Share and Toggles",
      "description": "Dialog with Link Share and Toggles. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-09.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-09/dialog-09.tsx",
          "content": "'use client';\n\nimport { CheckIcon, CopyIcon, ExternalLink, Link, Share2 } from 'lucide-react';\nimport { useRef, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport { Switch } from \"./shared/components/ui/switch\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"./shared/components/ui/tooltip\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport default function Dialog09() {\n  const [copied, setCopied] = useState<boolean>(false);\n  const inputRef = useRef<HTMLInputElement>(null);\n\n  const handleCopy = () => {\n    if (inputRef.current) {\n      navigator.clipboard.writeText(inputRef.current.value);\n      setCopied(true);\n      setTimeout(() => setCopied(false), 1500);\n    }\n  };\n\n  return (\n    <Dialog defaultOpen>\n      <DialogTrigger render={<Button />}>\n        <Share2 className=\"mr-2 h-4 w-4\" />\n        Share\n      </DialogTrigger>\n      <DialogContent className=\"sm:max-w-md\">\n        <DialogHeader>\n          <DialogTitle>Share & Collaborate</DialogTitle>\n          <p className=\"text-pretty text-muted-foreground text-sm\">\n            Share this project with your team to collaborate on it.\n          </p>\n        </DialogHeader>\n        <div className=\"space-y-4 py-4\">\n          <div className=\"flex items-center justify-between space-x-2\">\n            <div className=\"flex items-center space-x-2\">\n              <span>Enable comments and suggestions</span>\n            </div>\n            <Switch id=\"comments\" />\n          </div>\n\n          <div className=\"space-y-1.5\">\n            <Label className=\"sr-only\" htmlFor=\"share-link\">\n              Share Link\n            </Label>\n            <div className=\"relative\">\n              <Input\n                className=\"pe-9\"\n                id=\"share-link\"\n                readOnly\n                ref={inputRef}\n                value=\"https://writer.so/app/projects/123?share=true\"\n              />\n              <TooltipProvider delay={0}>\n                <Tooltip>\n                  <TooltipTrigger\n                    render={\n                      <button\n                        aria-label={copied ? 'Copied' : 'Copy to clipboard'}\n                        className=\"absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed\"\n                        disabled={copied}\n                        onClick={handleCopy}\n                      />\n                    }\n                  >\n                    <div\n                      className={cn(\n                        'transition-[transform,opacity] duration-200 ease-out',\n                        copied ? 'scale-100 opacity-100' : 'scale-0 opacity-0'\n                      )}\n                    >\n                      <CheckIcon\n                        aria-hidden=\"true\"\n                        className=\"text-primary\"\n                        size={16}\n                      />\n                    </div>\n                    <div\n                      className={cn(\n                        'absolute transition-[transform,opacity] duration-200 ease-out',\n                        copied ? 'scale-0 opacity-0' : 'scale-100 opacity-100'\n                      )}\n                    >\n                      <CopyIcon aria-hidden=\"true\" size={16} />\n                    </div>\n                  </TooltipTrigger>\n                  <TooltipContent className=\"px-2 py-1 text-xs\">\n                    {copied ? 'Copied!' : 'Copy to clipboard'}\n                  </TooltipContent>\n                </Tooltip>\n              </TooltipProvider>\n            </div>\n          </div>\n\n          <div className=\"flex space-x-2\">\n            <Button className=\"flex-1 gap-2\" onClick={handleCopy}>\n              <Link className=\"h-4 w-4\" />\n              Copy Link\n            </Button>\n            <Button className=\"flex-1 gap-2\" variant=\"outline\">\n              <ExternalLink className=\"h-4 w-4\" />\n              Preview\n            </Button>\n          </div>\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-09/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-09/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-09/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-09/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-09/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-09/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/switch.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-09/shared/components/ui/switch.tsx",
          "content": "'use client';\n\nimport { Switch as SwitchPrimitive } from '@base-ui/react/switch';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Switch({\n  className,\n  size = 'default',\n  ...props\n}: SwitchPrimitive.Root.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SwitchPrimitive.Root\n      className={cn(\n        'peer group/switch after:-inset-x-3 after:-inset-y-2 relative inline-flex shrink-0 items-center rounded-full border border-transparent outline-none transition-all after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 group-has-[:focus-visible]/field-label:border-transparent group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=sm]:h-[14px] data-[size=default]:w-[32px] data-[size=sm]:w-[24px] data-disabled:cursor-not-allowed data-checked:bg-primary data-unchecked:bg-input data-disabled:opacity-50 dark:data-unchecked:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-size={size}\n      data-slot=\"switch\"\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        className=\"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground\"\n        data-slot=\"switch-thumb\"\n      />\n    </SwitchPrimitive.Root>\n  );\n}\n\nexport { Switch };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-09/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-09/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-09/dialog-09.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-09 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-09/dialog-09.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-09"
      }
    },
    {
      "name": "dialog-10",
      "type": "registry:block",
      "title": "Dialog with Calendar and Time Picker",
      "description": "Dialog with Calendar and Time Picker. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "date-fns@4.4.0",
        "lucide-react@0.577.0",
        "react-day-picker@10.0.2"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-10.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/dialog-10.tsx",
          "content": "'use client';\n\nimport { format } from 'date-fns';\nimport { CalendarIcon } from 'lucide-react';\nimport { useMemo, useState } from 'react';\n\nimport { Button } from \"./shared/components/ui/button\";\nimport { Calendar } from \"./shared/components/ui/calendar\";\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"./shared/components/ui/popover\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport default function Dialog10() {\n  const [date, setDate] = useState<Date | undefined>(new Date());\n  const [startTime, setStartTime] = useState('09:00');\n\n  const timeOptions = useMemo(() => {\n    const options = [];\n    for (let hour = 0; hour <= 23; hour++) {\n      for (let minute = 0; minute < 60; minute += 30) {\n        const formattedHour = hour.toString().padStart(2, '0');\n        const formattedMinute = minute.toString().padStart(2, '0');\n        const value = `${formattedHour}:${formattedMinute}`;\n        const tempDate = new Date(2000, 0, 1, hour, minute);\n        const label = format(tempDate, 'h:mm a');\n        options.push({ value, label });\n      }\n    }\n\n    if (!options.find((opt) => opt.value === '23:59')) {\n      const endOfDay = new Date(2000, 0, 1, 23, 59);\n      options.push({ value: '23:59', label: format(endOfDay, 'h:mm a') });\n    }\n\n    return options;\n  }, []);\n  return (\n    <Dialog defaultOpen>\n      <DialogTrigger render={<Button />}>Show dialog</DialogTrigger>\n      <DialogContent className=\"gap-0 p-0 sm:max-w-lg\">\n        <DialogHeader className=\"border-b px-6 py-4 pt-5\">\n          <DialogTitle>Schedule a Meeting</DialogTitle>\n        </DialogHeader>\n\n        <form action=\"#\" method=\"POST\">\n          <div className=\"space-y-6 p-6\">\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"title\">Meeting Title</Label>\n              <Input\n                id=\"title\"\n                name=\"title\"\n                placeholder=\"e.g., Project Kickoff\"\n              />\n            </div>\n\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"attendees\">Attendees</Label>\n              <Input\n                id=\"attendees\"\n                name=\"attendees\"\n                placeholder=\"user1@example.com, user2@example.com\"\n              />\n              <p className=\"text-pretty text-muted-foreground text-xs\">\n                Enter email addresses separated by commas.\n              </p>\n            </div>\n\n            <div className=\"grid grid-cols-3 gap-4\">\n              <div className=\"col-span-2 space-y-2\">\n                <Label htmlFor=\"date\">Date</Label>\n                <Popover>\n                  <PopoverTrigger\n                    render={\n                      <Button\n                        className={cn(\n                          'w-full justify-start text-left font-normal',\n                          !date && 'text-muted-foreground'\n                        )}\n                        id=\"date\"\n                        variant={'outline'}\n                      />\n                    }\n                  >\n                    <CalendarIcon className=\"mr-1 h-4 w-4 shrink-0\" />{' '}\n                    {date ? format(date, 'PPP') : <span>Pick a date</span>}\n                  </PopoverTrigger>\n                  <PopoverContent align=\"start\" className=\"w-auto p-0\">\n                    <Calendar\n                      autoFocus\n                      mode=\"single\"\n                      onSelect={setDate}\n                      selected={date}\n                    />\n                  </PopoverContent>\n                </Popover>\n              </div>\n\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"time\">Time</Label>\n                <Select\n                  items={timeOptions}\n                  onValueChange={(value) => {\n                    if (value) {\n                      setStartTime(value);\n                    }\n                  }}\n                  value={startTime}\n                >\n                  <SelectTrigger className=\"w-full\" id=\"time\">\n                    <SelectValue placeholder=\"Select time\" />\n                  </SelectTrigger>\n                  <SelectContent>\n                    {timeOptions.map((option) => (\n                      <SelectItem key={option.value} value={option.value}>\n                        {option.label}\n                      </SelectItem>\n                    ))}\n                  </SelectContent>\n                </Select>\n              </div>\n            </div>\n\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"location\">Location / Conference Link</Label>\n              <Input\n                id=\"location\"\n                name=\"location\"\n                placeholder=\"e.g., Conference Room B or https://meet.example.com/...\"\n              />\n            </div>\n\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"description\">Description</Label>\n              <Textarea\n                className=\"min-h-[100px]\"\n                id=\"description\"\n                name=\"description\"\n                placeholder=\"Optional: Add an agenda or notes...\"\n              />\n            </div>\n          </div>\n\n          <div className=\"flex items-center justify-end space-x-2 border-t p-4\">\n            <DialogClose render={<Button type=\"button\" variant=\"ghost\" />}>\n              Cancel\n            </DialogClose>\n            <Button size=\"sm\" type=\"submit\">\n              Schedule\n            </Button>\n          </div>\n        </form>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-10/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-10/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/calendar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/components/ui/calendar.tsx",
          "content": "'use client';\n\nimport {\n  ChevronDownIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n} from 'lucide-react';\nimport * as React from 'react';\nimport {\n  type DayButton,\n  DayPicker,\n  getDefaultClassNames,\n  type Locale,\n} from 'react-day-picker';\nimport { Button, buttonVariants } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Calendar({\n  className,\n  classNames,\n  showOutsideDays = true,\n  captionLayout = 'label',\n  buttonVariant = 'ghost',\n  locale,\n  formatters,\n  components,\n  ...props\n}: React.ComponentProps<typeof DayPicker> & {\n  buttonVariant?: React.ComponentProps<typeof Button>['variant'];\n}) {\n  const defaultClassNames = getDefaultClassNames();\n\n  return (\n    <DayPicker\n      captionLayout={captionLayout}\n      className={cn(\n        'group/calendar bg-background in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)]',\n        String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n        String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n        className\n      )}\n      classNames={{\n        root: cn('w-fit', defaultClassNames.root),\n        months: cn(\n          'relative flex flex-col gap-4 md:flex-row',\n          defaultClassNames.months\n        ),\n        month: cn('flex w-full flex-col gap-4', defaultClassNames.month),\n        nav: cn(\n          'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1',\n          defaultClassNames.nav\n        ),\n        button_previous: cn(\n          buttonVariants({ variant: buttonVariant }),\n          'size-(--cell-size) select-none p-0 aria-disabled:opacity-50',\n          defaultClassNames.button_previous\n        ),\n        button_next: cn(\n          buttonVariants({ variant: buttonVariant }),\n          'size-(--cell-size) select-none p-0 aria-disabled:opacity-50',\n          defaultClassNames.button_next\n        ),\n        month_caption: cn(\n          'flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)',\n          defaultClassNames.month_caption\n        ),\n        dropdowns: cn(\n          'flex h-(--cell-size) w-full items-center justify-center gap-1.5 font-medium text-sm',\n          defaultClassNames.dropdowns\n        ),\n        dropdown_root: cn(\n          'relative rounded-(--cell-radius)',\n          defaultClassNames.dropdown_root\n        ),\n        dropdown: cn(\n          'absolute inset-0 bg-popover opacity-0',\n          defaultClassNames.dropdown\n        ),\n        caption_label: cn(\n          'select-none font-medium',\n          captionLayout === 'label'\n            ? 'text-sm'\n            : 'flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground',\n          defaultClassNames.caption_label\n        ),\n        month_grid: cn('w-full border-collapse', defaultClassNames.month_grid),\n        weekdays: cn('flex', defaultClassNames.weekdays),\n        weekday: cn(\n          'flex-1 select-none rounded-(--cell-radius) font-normal text-[0.8rem] text-muted-foreground',\n          defaultClassNames.weekday\n        ),\n        week: cn('mt-2 flex w-full', defaultClassNames.week),\n        week_number_header: cn(\n          'w-(--cell-size) select-none',\n          defaultClassNames.week_number_header\n        ),\n        week_number: cn(\n          'select-none text-[0.8rem] text-muted-foreground',\n          defaultClassNames.week_number\n        ),\n        day: cn(\n          'group/day relative aspect-square h-full w-full select-none rounded-(--cell-radius) p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)',\n          props.showWeekNumber\n            ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)'\n            : '[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)',\n          defaultClassNames.day\n        ),\n        range_start: cn(\n          'relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted',\n          defaultClassNames.range_start\n        ),\n        range_middle: cn('rounded-none', defaultClassNames.range_middle),\n        range_end: cn(\n          'relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted',\n          defaultClassNames.range_end\n        ),\n        today: cn(\n          'rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none',\n          defaultClassNames.today\n        ),\n        outside: cn(\n          'text-muted-foreground aria-selected:text-muted-foreground',\n          defaultClassNames.outside\n        ),\n        disabled: cn(\n          'text-muted-foreground opacity-50',\n          defaultClassNames.disabled\n        ),\n        hidden: cn('invisible', defaultClassNames.hidden),\n        ...classNames,\n      }}\n      components={{\n        Root: ({ className, rootRef, ...props }) => {\n          return (\n            <div\n              className={cn(className)}\n              data-slot=\"calendar\"\n              ref={rootRef}\n              {...props}\n            />\n          );\n        },\n        Chevron: ({ className, orientation, ...props }) => {\n          if (orientation === 'left') {\n            return (\n              <ChevronLeftIcon className={cn('size-4', className)} {...props} />\n            );\n          }\n\n          if (orientation === 'right') {\n            return (\n              <ChevronRightIcon\n                className={cn('size-4', className)}\n                {...props}\n              />\n            );\n          }\n\n          return (\n            <ChevronDownIcon className={cn('size-4', className)} {...props} />\n          );\n        },\n        DayButton: ({ ...props }) => (\n          <CalendarDayButton locale={locale} {...props} />\n        ),\n        WeekNumber: ({ children, ...props }) => {\n          return (\n            <td {...props}>\n              <div className=\"flex size-(--cell-size) items-center justify-center text-center\">\n                {children}\n              </div>\n            </td>\n          );\n        },\n        ...components,\n      }}\n      formatters={{\n        formatMonthDropdown: (date) =>\n          date.toLocaleString(locale?.code, { month: 'short' }),\n        ...formatters,\n      }}\n      locale={locale}\n      showOutsideDays={showOutsideDays}\n      {...props}\n    />\n  );\n}\n\nfunction CalendarDayButton({\n  className,\n  day,\n  modifiers,\n  locale,\n  ...props\n}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {\n  const defaultClassNames = getDefaultClassNames();\n\n  const ref = React.useRef<HTMLButtonElement>(null);\n  React.useEffect(() => {\n    if (modifiers.focused) {\n      ref.current?.focus();\n    }\n  }, [modifiers.focused]);\n\n  return (\n    <Button\n      className={cn(\n        'relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 font-normal leading-none data-[range-end=true]:rounded-(--cell-radius) data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-end=true]:bg-primary data-[range-middle=true]:bg-muted data-[range-start=true]:bg-primary data-[selected-single=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:text-foreground data-[range-start=true]:text-primary-foreground data-[selected-single=true]:text-primary-foreground group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70',\n        defaultClassNames.day,\n        className\n      )}\n      data-day={day.date.toLocaleDateString(locale?.code)}\n      data-range-end={modifiers.range_end}\n      data-range-middle={modifiers.range_middle}\n      data-range-start={modifiers.range_start}\n      data-selected-single={\n        modifiers.selected &&\n        !modifiers.range_start &&\n        !modifiers.range_end &&\n        !modifiers.range_middle\n      }\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    />\n  );\n}\n\nexport { Calendar, CalendarDayButton };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/popover.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/components/ui/popover.tsx",
          "content": "'use client';\n\nimport { Popover as PopoverPrimitive } from '@base-ui/react/popover';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Popover({ ...props }: PopoverPrimitive.Root.Props) {\n  return <PopoverPrimitive.Root data-slot=\"popover\" {...props} />;\n}\n\nfunction PopoverTrigger({ ...props }: PopoverPrimitive.Trigger.Props) {\n  return <PopoverPrimitive.Trigger data-slot=\"popover-trigger\" {...props} />;\n}\n\nfunction PopoverContent({\n  className,\n  align = 'center',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  ...props\n}: PopoverPrimitive.Popup.Props &\n  Pick<\n    PopoverPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <PopoverPrimitive.Portal>\n      <PopoverPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <PopoverPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-lg bg-popover p-2.5 text-popover-foreground text-sm shadow-md outline-hidden ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-slot=\"popover-content\"\n          {...props}\n        />\n      </PopoverPrimitive.Positioner>\n    </PopoverPrimitive.Portal>\n  );\n}\n\nfunction PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 text-sm', className)}\n      data-slot=\"popover-header\"\n      {...props}\n    />\n  );\n}\n\nfunction PopoverTitle({ className, ...props }: PopoverPrimitive.Title.Props) {\n  return (\n    <PopoverPrimitive.Title\n      className={cn('font-medium', className)}\n      data-slot=\"popover-title\"\n      {...props}\n    />\n  );\n}\n\nfunction PopoverDescription({\n  className,\n  ...props\n}: PopoverPrimitive.Description.Props) {\n  return (\n    <PopoverPrimitive.Description\n      className={cn('text-muted-foreground', className)}\n      data-slot=\"popover-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Popover,\n  PopoverContent,\n  PopoverDescription,\n  PopoverHeader,\n  PopoverTitle,\n  PopoverTrigger,\n};\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-10/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-10/dialog-10.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-10 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-10/dialog-10.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-10"
      }
    },
    {
      "name": "dialog-11",
      "type": "registry:block",
      "title": "Dialog Multi-Step Wizard",
      "description": "Dialog Multi-Step Wizard. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-11.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-11/dialog-11.tsx",
          "content": "'use client';\n\nimport { AppWindowIcon as Apps } from 'lucide-react';\n\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\nimport { Label } from \"./shared/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport { Separator } from \"./shared/components/ui/separator\";\n\nexport default function Dialog11() {\n  return (\n    <Dialog defaultOpen>\n      <DialogTrigger render={<Button />}>Initialize New Project</DialogTrigger>\n      <DialogContent className=\"gap-0 overflow-visible p-0 sm:max-w-2xl\">\n        <DialogHeader className=\"mb-0 border-b px-6 py-4\">\n          <DialogTitle>Initialize New Project</DialogTitle>\n        </DialogHeader>\n\n        <form action=\"#\" method=\"POST\">\n          <div className=\"flex flex-col-reverse md:flex-row\">\n            <div className=\"flex flex-col justify-between md:w-80 md:border-r\">\n              <div className=\"flex-1 grow\">\n                <div className=\"border-t p-6 md:border-none\">\n                  <div className=\"flex items-center space-x-3\">\n                    <div className=\"inline-flex shrink-0 items-center justify-center rounded-sm bg-muted p-3\">\n                      <Apps\n                        aria-hidden={true}\n                        className=\"size-5 text-foreground\"\n                      />\n                    </div>\n                    <div className=\"space-y-0.5\">\n                      <h3 className=\"text-balance font-medium text-foreground text-sm\">\n                        Project Starter\n                      </h3>\n                      <p className=\"text-pretty text-muted-foreground text-sm\">\n                        Configure your new codebase\n                      </p>\n                    </div>\n                  </div>\n                  <Separator className=\"my-4\" />\n                  <h4 className=\"text-balance font-medium text-foreground text-sm\">\n                    Description\n                  </h4>\n                  <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6\">\n                    Quickly set up the foundational tools for your project.\n                  </p>\n                  <h4 className=\"mt-6 text-balance font-medium text-foreground text-sm\">\n                    Info\n                  </h4>\n                  <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6\">\n                    Select your preferred stack and configurations.\n                  </p>\n                </div>\n              </div>\n              <div className=\"flex items-center justify-between border-t p-4\">\n                <DialogClose render={<Button type=\"button\" variant=\"ghost\" />}>\n                  Cancel\n                </DialogClose>\n                <Button size=\"sm\" type=\"submit\">\n                  Initialize\n                </Button>\n              </div>\n            </div>\n\n            <div className=\"flex-1 space-y-6 p-6 md:px-6 md:pt-6 md:pb-8\">\n              <div className=\"space-y-2\">\n                <div className=\"flex items-center space-x-3\">\n                  <div className=\"inline-flex size-6 items-center justify-center rounded-sm bg-muted text-foreground text-sm\">\n                    1\n                  </div>\n                  <Label\n                    className=\"font-medium text-foreground text-sm\"\n                    htmlFor=\"framework\"\n                  >\n                    Select Framework\n                  </Label>\n                </div>\n                <Select\n                  defaultValue=\"react\"\n                  items={{\n                    react: 'Next.js',\n                    vue: 'React Router',\n                    angular: 'Tanstack Start',\n                    svelte: 'SvelteKit',\n                    vanilla: 'SolidStart',\n                  }}\n                >\n                  <SelectTrigger\n                    className=\"w-full\"\n                    id=\"framework\"\n                    name=\"framework\"\n                  >\n                    <SelectValue placeholder=\"Select framework\" />\n                  </SelectTrigger>\n                  <SelectContent>\n                    <SelectItem value=\"react\">Next.js</SelectItem>\n                    <SelectItem value=\"vue\">React Router</SelectItem>\n                    <SelectItem value=\"angular\">Tanstack Start</SelectItem>\n                    <SelectItem value=\"svelte\">SvelteKit</SelectItem>\n                    <SelectItem value=\"vanilla\">SolidStart</SelectItem>\n                  </SelectContent>\n                </Select>\n              </div>\n              <div className=\"space-y-2\">\n                <div className=\"flex items-center space-x-3\">\n                  <div className=\"inline-flex size-6 items-center justify-center rounded-sm bg-muted text-foreground text-sm\">\n                    2\n                  </div>\n                  <Label\n                    className=\"font-medium text-foreground text-sm\"\n                    htmlFor=\"package-manager\"\n                  >\n                    Choose Package Manager\n                  </Label>\n                </div>\n                <Select\n                  defaultValue=\"npm\"\n                  items={{ npm: 'npm', yarn: 'yarn', pnpm: 'pnpm', bun: 'bun' }}\n                >\n                  <SelectTrigger\n                    className=\"w-full\"\n                    id=\"package-manager\"\n                    name=\"package-manager\"\n                  >\n                    <SelectValue placeholder=\"Select manager\" />\n                  </SelectTrigger>\n                  <SelectContent>\n                    <SelectItem value=\"npm\">npm</SelectItem>\n                    <SelectItem value=\"yarn\">yarn</SelectItem>\n                    <SelectItem value=\"pnpm\">pnpm</SelectItem>\n                    <SelectItem value=\"bun\">bun</SelectItem>\n                  </SelectContent>\n                </Select>\n              </div>\n              <div>\n                <div className=\"flex items-center space-x-3\">\n                  <div className=\"inline-flex size-6 items-center justify-center rounded-sm bg-muted text-foreground text-sm\">\n                    3\n                  </div>\n                  <Label\n                    className=\"font-medium text-foreground text-sm\"\n                    htmlFor=\"linter\"\n                  >\n                    Configure Linter/Formatter\n                  </Label>\n                </div>\n                <p className=\"mt-1 text-pretty text-muted-foreground text-xs\">\n                  Ensure code quality and consistency.\n                </p>\n                <Select\n                  defaultValue=\"eslint-prettier\"\n                  items={{\n                    'eslint-prettier': 'ESLint + Prettier',\n                    eslint: 'ESLint Only',\n                    prettier: 'Prettier Only',\n                    biome: 'Biome',\n                    oxlint: 'Oxlint',\n                    none: 'None',\n                  }}\n                >\n                  <SelectTrigger\n                    className=\"mt-4 w-full\"\n                    id=\"linter\"\n                    name=\"linter\"\n                  >\n                    <SelectValue placeholder=\"Select tools\" />\n                  </SelectTrigger>\n                  <SelectContent>\n                    <SelectItem value=\"eslint-prettier\">\n                      ESLint + Prettier\n                    </SelectItem>\n                    <SelectItem value=\"eslint\">ESLint Only</SelectItem>\n                    <SelectItem value=\"prettier\">Prettier Only</SelectItem>\n                    <SelectItem value=\"biome\">Biome</SelectItem>\n                    <SelectItem value=\"oxlint\">Oxlint</SelectItem>\n                    <SelectItem value=\"none\">None</SelectItem>\n                  </SelectContent>\n                </Select>\n              </div>\n              <div>\n                <div className=\"flex items-center space-x-3\">\n                  <div className=\"inline-flex size-6 items-center justify-center rounded-sm bg-muted text-foreground text-sm\">\n                    4\n                  </div>\n                  <Label\n                    className=\"font-medium text-foreground text-sm\"\n                    htmlFor=\"testing-tool\"\n                  >\n                    Select Testing Tool\n                  </Label>\n                </div>\n                <p className=\"mt-1 text-pretty text-muted-foreground text-xs\">\n                  Choose a framework for unit/integration tests.\n                </p>\n                <Select\n                  defaultValue=\"jest\"\n                  items={{\n                    jest: 'Jest',\n                    vitest: 'Vitest',\n                    cypress: 'Cypress',\n                    none: 'None',\n                  }}\n                >\n                  <SelectTrigger\n                    className=\"mt-4 w-full\"\n                    id=\"testing-tool\"\n                    name=\"testing-tool\"\n                  >\n                    <SelectValue placeholder=\"Select tool\" />\n                  </SelectTrigger>\n                  <SelectContent>\n                    <SelectItem value=\"jest\">Jest</SelectItem>\n                    <SelectItem value=\"vitest\">Vitest</SelectItem>\n                    <SelectItem value=\"cypress\">Cypress</SelectItem>\n                    <SelectItem value=\"none\">None</SelectItem>\n                  </SelectContent>\n                </Select>\n              </div>\n            </div>\n          </div>\n        </form>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-11/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-11/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-11/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-11/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-11/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-11/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-11/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-11/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-11/dialog-11.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-11 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-11/dialog-11.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-11"
      }
    },
    {
      "name": "dialog-12",
      "type": "registry:block",
      "title": "Dialog with Avatar Upload",
      "description": "Dialog with Avatar Upload. Focused modal dialogs for confirmations, editing, and multi-step product actions.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/dialogs/dialog-12.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-12/dialog-12.tsx",
          "content": "'use client';\n\nimport { Plus, UserRoundIcon, X } from 'lucide-react';\nimport { useRef, useState } from 'react';\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"./shared/components/ui/dialog\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\nexport default function Dialog12() {\n  const [open, setOpen] = useState(true);\n  const [authorName, setAuthorName] = useState('Ephraim Duncan');\n  const [title, setTitle] = useState('Design Engineer');\n  const [image, setImage] = useState<string | null>(null);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const file = e.target.files?.[0];\n    if (file) {\n      if (file.size > 1_048_576) {\n        alert('File size exceeds 1MB limit');\n        return;\n      }\n\n      const reader = new FileReader();\n      reader.onload = (event) => {\n        setImage(event.target?.result as string);\n      };\n      reader.readAsDataURL(file);\n    }\n  };\n\n  const triggerFileInput = () => {\n    fileInputRef.current?.click();\n  };\n\n  return (\n    <Dialog onOpenChange={setOpen} open={open}>\n      <DialogTrigger render={<Button />}>Open Dialog</DialogTrigger>\n      <DialogContent className=\"gap-0 rounded-3xl p-0 sm:max-w-lg\">\n        <DialogHeader className=\"border-b px-6 py-4\">\n          <DialogTitle className=\"text-balance font-medium\">\n            Add a writer\n          </DialogTitle>\n        </DialogHeader>\n\n        <div className=\"grid grid-cols-1 px-6 pt-4 pb-6 md:grid-cols-5\">\n          <div className=\"flex flex-col items-center justify-center md:col-span-2\">\n            <div className=\"relative mb-2\">\n              <Avatar className=\"h-24 w-24 border-2 border-muted\">\n                <AvatarImage alt=\"Profile\" src={image || undefined} />\n                <AvatarFallback>\n                  <UserRoundIcon\n                    aria-hidden=\"true\"\n                    className=\"text-muted-foreground\"\n                    size={52}\n                  />\n                </AvatarFallback>\n              </Avatar>\n              <Button\n                className=\"-top-0.5 -right-0.5 absolute rounded-full border-[3px] border-background bg-accent hover:bg-accent\"\n                onClick={() => {\n                  if (image) {\n                    setImage(null);\n                    if (fileInputRef.current) {\n                      fileInputRef.current.value = '';\n                    }\n                  } else {\n                    triggerFileInput();\n                  }\n                }}\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              >\n                {image ? (\n                  <X className=\"h-4 w-4 text-muted-foreground\" />\n                ) : (\n                  <Plus className=\"h-3 w-3 text-muted-foreground\" />\n                )}\n                <span className=\"sr-only\">\n                  {image ? 'Remove image' : 'Upload image'}\n                </span>\n              </Button>\n            </div>\n\n            <p className=\"text-pretty text-center font-medium\">Upload Image</p>\n            <p className=\"text-pretty text-center text-muted-foreground text-sm\">\n              Max file size: 1MB\n            </p>\n            <input\n              accept=\"image/*\"\n              className=\"hidden\"\n              onChange={handleFileChange}\n              ref={fileInputRef}\n              type=\"file\"\n            />\n            <Button\n              className=\"mt-2\"\n              onClick={triggerFileInput}\n              size=\"sm\"\n              variant=\"outline\"\n            >\n              Add Image\n            </Button>\n          </div>\n\n          <div className=\"flex flex-col justify-between md:col-span-3\">\n            <div className=\"space-y-4\">\n              <div className=\"space-y-1\">\n                <Label className=\"flex items-center\" htmlFor=\"author-name\">\n                  Author name <span className=\"text-primary\">*</span>\n                </Label>\n                <Input\n                  id=\"author-name\"\n                  onChange={(e) => setAuthorName(e.target.value)}\n                  required\n                  value={authorName}\n                />\n              </div>\n\n              <div className=\"space-y-1\">\n                <div className=\"flex items-center\">\n                  <Label htmlFor=\"title\">Title</Label>\n                </div>\n                <Input\n                  id=\"title\"\n                  onChange={(e) => setTitle(e.target.value)}\n                  value={title}\n                />\n              </div>\n            </div>\n\n            <div className=\"flex justify-end gap-2\">\n              <Button onClick={() => setOpen(false)} variant=\"outline\">\n                Cancel\n              </Button>\n              <Button className=\"bg-foreground text-background hover:bg-foreground/90\">\n                Save Changes\n              </Button>\n            </div>\n          </div>\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
        },
        {
          "path": "licenses/dialog-12/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/dialog-12/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-12/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-12/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-12/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-12/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-12/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/dialog-12/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "dialogs",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/dialog-12/dialog-12.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/dialogs/dialog-12 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "dialog",
          "modal",
          "confirmation",
          "弹窗",
          "对话框",
          "确认",
          "编辑"
        ],
        "contentGuidelines": [
          "Use one primary action and a clear cancel path.",
          "Connect actions to product logic and verify keyboard focus behavior."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/dialog-12/dialog-12.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/dialog-12"
      }
    },
    {
      "name": "faq-01",
      "type": "registry:block",
      "title": "Product FAQ",
      "description": "A keyboard-accessible FAQ accordion for product fit, privacy, and billing questions.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/marketing/faq-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/faq-01/faq-01.tsx",
          "content": "\"use client\";\n\nimport { ArrowUpRight, Minus, Plus } from \"lucide-react\";\nimport { useId, useState } from \"react\";\n\nexport type Faq01Props = {\n  title?: string;\n  description?: string;\n  items?: { question: string; answer: string }[];\n  actionLabel?: string;\n  actionHref?: string;\n};\n\n/** Single-open FAQ with semantic controls and content-sized answer panels. */\nexport default function Faq01({\n  title = \"A few things you might be wondering.\",\n  description = \"A little clarity before you give it a try.\",\n  actionLabel = \"Try the workspace\",\n  actionHref = \"/preview/recipes/tool-workspace\",\n  items = [\n    {\n      question: \"Is this a real file processing service?\",\n      answer:\n        \"Frame is a fictional product used to demonstrate this UIBlocks page recipe. Its workspace shows local interface interactions; it does not upload, convert, or store your files on a server.\",\n    },\n    {\n      question: \"What happens to files I choose in the demo?\",\n      answer:\n        \"Selected file details stay in your browser session. The demo can create a downloadable SVG demo preview to show the export interaction. It does not convert your selected files. Closing or refreshing the page clears the session.\",\n    },\n    {\n      question: \"Can I use these sections in my own product?\",\n      answer:\n        \"Yes. The UIBlocks-original sections in this recipe use the MIT license. You can install the source, adapt the copy and brand, and connect your own product behavior. Retain the included license notices.\",\n    },\n    {\n      question: \"How do I keep the whole page consistent?\",\n      answer:\n        \"Start with the complete recipe, then change your text, product name, and accent color together. Keep the shared content width, type scale, borders, and section spacing as you adapt the page.\",\n    },\n  ],\n}: Faq01Props) {\n  const [openIndex, setOpenIndex] = useState<number | null>(0);\n  const baseId = useId();\n  return (\n    <section\n      className=\"w-full scroll-mt-10 bg-white py-20 text-zinc-950 md:py-24 dark:bg-zinc-950 dark:text-white\"\n      id=\"faq\"\n    >\n      <div className=\"mx-auto grid max-w-6xl gap-10 px-6 lg:grid-cols-[0.9fr_1.1fr] lg:gap-20 lg:px-8\">\n        <div>\n          <p className=\"text-[10px] font-semibold tracking-[0.16em] text-violet-600 dark:text-violet-400\">\n            GOOD QUESTIONS\n          </p>\n          <h2 className=\"mt-4 max-w-sm text-balance text-3xl font-semibold leading-tight tracking-[-0.035em] sm:text-4xl\">\n            {title}\n          </h2>\n          <p className=\"mt-4 text-sm leading-6 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n          <a\n            className=\"mt-6 inline-flex min-h-10 items-center gap-2 rounded text-sm font-medium text-violet-600 hover:text-violet-700 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-violet-400\"\n            href={actionHref}\n          >\n            {actionLabel}\n            <ArrowUpRight aria-hidden=\"true\" className=\"size-3.5\" />\n          </a>\n        </div>\n        <div className=\"border-t border-zinc-200 dark:border-zinc-800\">\n          {items.map((item, index) => {\n            const isOpen = openIndex === index;\n            const panelId = `${baseId}-answer-${index}`;\n            const buttonId = `${baseId}-question-${index}`;\n            return (\n              <div\n                className=\"border-b border-zinc-200 dark:border-zinc-800\"\n                key={item.question}\n              >\n                <h3>\n                  <button\n                    aria-controls={panelId}\n                    aria-expanded={isOpen}\n                    className=\"flex w-full items-center justify-between gap-5 py-5 text-left text-sm font-medium leading-6 transition-colors hover:text-violet-600 focus-visible:outline-2 focus-visible:outline-violet-500 dark:hover:text-violet-400\"\n                    id={buttonId}\n                    onClick={() => setOpenIndex(isOpen ? null : index)}\n                    type=\"button\"\n                  >\n                    {item.question}\n                    {isOpen ? (\n                      <Minus\n                        aria-hidden=\"true\"\n                        className=\"size-4 shrink-0 text-zinc-400\"\n                      />\n                    ) : (\n                      <Plus\n                        aria-hidden=\"true\"\n                        className=\"size-4 shrink-0 text-zinc-400\"\n                      />\n                    )}\n                  </button>\n                </h3>\n                <div\n                  aria-labelledby={buttonId}\n                  hidden={!isOpen}\n                  id={panelId}\n                  role=\"region\"\n                >\n                  <p className=\"pb-6 pr-6 text-sm leading-7 text-zinc-500 dark:text-zinc-400\">\n                    {item.answer}\n                  </p>\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "licenses/faq-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/faq-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        }
      ],
      "categories": [
        "marketing",
        "marketing",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/faq-01/faq-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/marketing/faq-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "marketing",
        "featured": true,
        "tags": [
          "faq",
          "accordion",
          "questions",
          "support",
          "常见问题",
          "问答",
          "折叠",
          "帮助"
        ],
        "contentGuidelines": [
          "Write five or fewer common questions with direct, short answers.",
          "Replace demo service and privacy claims with statements that match your product."
        ],
        "recommendedWith": [
          "pricing-01",
          "footer-01"
        ],
        "entry": "@components/uiblocks/faq-01/faq-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/faq-01"
      }
    },
    {
      "name": "features-01",
      "type": "registry:block",
      "title": "Workflow Feature Grid",
      "description": "A three-part feature grid that explains an input, processing, and output workflow.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/marketing/features-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/features-01/features-01.tsx",
          "content": "import {\n  ArrowRight,\n  Download,\n  FolderOpen,\n  SlidersHorizontal,\n} from \"lucide-react\";\n\nexport type Features01Props = {\n  eyebrow?: string;\n  title?: string;\n  description?: string;\n  items?: { title: string; description: string; detail: string }[];\n};\n\n/** Three-step feature section. Keep each description to roughly two sentences. */\nexport default function Features01({\n  eyebrow = \"FROM FIRST FILE TO FINAL EXPORT\",\n  title = \"A clear path from here to done.\",\n  description = \"The little details stay organized, so your next step always feels obvious.\",\n  items = [\n    {\n      title: \"Bring everything together\",\n      description:\n        \"Drop in a file or a whole batch. See names, formats, and sizes together before you start.\",\n      detail: \"One tidy starting point\",\n    },\n    {\n      title: \"Make it your workflow\",\n      description:\n        \"Choose your output settings once. Keep an eye on each task without losing your place.\",\n      detail: \"Small steps, clear progress\",\n    },\n    {\n      title: \"Leave with something useful\",\n      description:\n        \"Review your results, find the right version, and download it when you’re ready.\",\n      detail: \"An easy last mile\",\n    },\n  ],\n}: Features01Props) {\n  const icons = [FolderOpen, SlidersHorizontal, Download];\n  return (\n    <section\n      className=\"w-full scroll-mt-10 bg-white py-20 text-zinc-950 md:py-24 dark:bg-zinc-950 dark:text-white\"\n      id=\"features\"\n    >\n      <div className=\"mx-auto max-w-6xl px-6 lg:px-8\">\n        <div className=\"max-w-xl\">\n          <p className=\"text-[10px] font-semibold tracking-[0.16em] text-violet-600 dark:text-violet-400\">\n            {eyebrow}\n          </p>\n          <h2 className=\"mt-4 text-balance text-3xl font-semibold leading-tight tracking-[-0.035em] sm:text-4xl\">\n            {title}\n          </h2>\n          <p className=\"mt-4 max-w-lg text-pretty text-sm leading-7 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n        </div>\n        <div className=\"mt-12 grid gap-8 md:grid-cols-3 md:gap-10\">\n          {items.map((item, index) => {\n            const Icon = icons[index % icons.length];\n            return (\n              <article\n                className=\"relative border-t border-zinc-200 pt-6 dark:border-zinc-800\"\n                key={item.title}\n              >\n                <div className=\"flex items-center justify-between\">\n                  <span className=\"grid size-10 place-items-center rounded-lg border border-zinc-200 bg-zinc-50 text-zinc-700 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300\">\n                    <Icon aria-hidden=\"true\" className=\"size-4.5\" />\n                  </span>\n                  <span\n                    aria-hidden=\"true\"\n                    className=\"font-mono text-xs text-zinc-300 dark:text-zinc-600\"\n                  >\n                    0{index + 1}\n                  </span>\n                </div>\n                <h3 className=\"mt-5 text-base font-semibold tracking-tight\">\n                  {item.title}\n                </h3>\n                <p className=\"mt-3 text-sm leading-6 text-zinc-500 dark:text-zinc-400\">\n                  {item.description}\n                </p>\n                <p className=\"mt-5 flex items-center gap-2 text-xs font-medium text-violet-600 dark:text-violet-400\">\n                  {item.detail}\n                  <ArrowRight aria-hidden=\"true\" className=\"size-3\" />\n                </p>\n              </article>\n            );\n          })}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "licenses/features-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/features-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        }
      ],
      "categories": [
        "marketing",
        "marketing",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/features-01/features-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/marketing/features-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "marketing",
        "featured": true,
        "tags": [
          "features",
          "bento",
          "workflow",
          "benefits",
          "功能",
          "网格",
          "流程",
          "优势"
        ],
        "contentGuidelines": [
          "Keep each feature focused on one concrete user outcome.",
          "Use three equally detailed feature descriptions for balanced cards."
        ],
        "recommendedWith": [
          "hero-01",
          "pricing-01",
          "faq-01"
        ],
        "entry": "@components/uiblocks/features-01/features-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/features-01"
      }
    },
    {
      "name": "file-upload-01",
      "type": "registry:block",
      "title": "File Upload with Preview",
      "description": "File Upload with Preview. File selection, drag and drop, previews, and upload progress for a product workspace.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/file-upload/file-upload-01/dropzone.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/dropzone.tsx",
          "content": "import { Upload } from 'lucide-react';\nimport type React from 'react';\nimport type { RefObject } from 'react';\n\ninterface FileDropzoneProps {\n  fileInputRef: RefObject<HTMLInputElement | null>;\n  handleBoxClick: () => void;\n  handleDragOver: (e: React.DragEvent) => void;\n  handleDrop: (e: React.DragEvent) => void;\n  handleFileSelect: (files: FileList | null) => void;\n}\n\nexport function FileDropzone({\n  fileInputRef,\n  handleBoxClick,\n  handleDragOver,\n  handleDrop,\n  handleFileSelect,\n}: FileDropzoneProps) {\n  return (\n    <div className=\"px-6\">\n      <div\n        className=\"flex cursor-pointer flex-col items-center justify-center rounded-md border-2 border-border border-dashed p-8 text-center\"\n        onClick={handleBoxClick}\n        onDragOver={handleDragOver}\n        onDrop={handleDrop}\n      >\n        <div className=\"mb-2 rounded-full bg-muted p-3\">\n          <Upload className=\"h-5 w-5 text-muted-foreground\" />\n        </div>\n        <p className=\"font-medium text-foreground text-sm\">\n          Upload a project image\n        </p>\n        <p className=\"mt-1 text-muted-foreground text-sm\">\n          or,{' '}\n          <label\n            className=\"cursor-pointer font-medium text-primary hover:text-primary/90\"\n            htmlFor=\"fileUpload\"\n            onClick={(e) => e.stopPropagation()} // Prevent triggering handleBoxClick\n          >\n            click to browse\n          </label>{' '}\n          (4MB max)\n        </p>\n        <input\n          accept=\"image/*\"\n          className=\"hidden\"\n          id=\"fileUpload\"\n          onChange={(e) => handleFileSelect(e.target.files)}\n          ref={fileInputRef}\n          type=\"file\"\n        />\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/file-upload/file-upload-01/file-item.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/file-item.tsx",
          "content": "import { Trash2 } from 'lucide-react';\nimport { useEffect } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\n\ninterface UploadedFileItemProps {\n  file: File;\n  progress: number;\n  onRemove: (filename: string) => void;\n}\n\nexport function UploadedFileItem({\n  file,\n  progress,\n  onRemove,\n}: UploadedFileItemProps) {\n  const imageUrl = URL.createObjectURL(file);\n\n  useEffect(() => {\n    return () => URL.revokeObjectURL(imageUrl);\n  }, [imageUrl]);\n\n  return (\n    <div\n      className=\"flex flex-col rounded-lg border border-border p-2\"\n      key={file.name}\n    >\n      <div className=\"flex items-center gap-2\">\n        <div className=\"row-span-2 flex h-14 w-18 items-center justify-center self-start overflow-hidden rounded-sm bg-muted\">\n          <img\n            alt={file.name}\n            className=\"h-full w-full object-cover\"\n            src={imageUrl}\n          />\n        </div>\n\n        <div className=\"flex-1 pr-1\">\n          <div className=\"flex items-center justify-between\">\n            <div className=\"flex items-center gap-2\">\n              <span className=\"max-w-[250px] truncate text-foreground text-sm\">\n                {file.name}\n              </span>\n              <span className=\"whitespace-nowrap text-muted-foreground text-sm\">\n                {Math.round(file.size / 1024)} KB\n              </span>\n            </div>\n            <Button\n              className=\"bg-transparent! hover:text-red-500\"\n              onClick={() => onRemove(file.name)}\n              size=\"icon-sm\"\n              variant=\"ghost\"\n            >\n              <Trash2 className=\"h-4 w-4\" />\n            </Button>\n          </div>\n\n          <div className=\"flex items-center gap-2\">\n            <div className=\"h-2 flex-1 overflow-hidden rounded-full bg-muted\">\n              <div\n                className=\"h-full bg-primary\"\n                style={{\n                  width: `${progress || 0}%`,\n                }}\n              />\n            </div>\n            <span className=\"whitespace-nowrap text-muted-foreground text-xs\">\n              {Math.round(progress || 0)}%\n            </span>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/file-upload/file-upload-01/file-list.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/file-list.tsx",
          "content": "import { cn } from \"./shared/lib/utils\";\nimport { UploadedFileItem } from \"./file-item\";\n\ninterface UploadedFileListProps {\n  uploadedFiles: File[];\n  fileProgresses: Record<string, number>;\n  removeFile: (filename: string) => void;\n}\n\nexport function FileList({\n  uploadedFiles,\n  fileProgresses,\n  removeFile,\n}: UploadedFileListProps) {\n  if (uploadedFiles.length === 0) {\n    return null;\n  }\n\n  return (\n    <div className={cn('mt-4 space-y-3 px-6 pb-5')}>\n      {uploadedFiles.map((file, index) => (\n        <UploadedFileItem\n          file={file}\n          key={file.name + index}\n          onRemove={removeFile}\n          progress={fileProgresses[file.name] || 0}\n        />\n      ))}\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/file-upload/file-upload-01/form.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/form.tsx",
          "content": "import { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\n\nexport function Form() {\n  return (\n    <div className=\"mt-2 px-6 pb-4\">\n      <div className=\"grid grid-cols-2 gap-4\">\n        <div>\n          <Label className=\"mb-2\" htmlFor=\"projectName\">\n            Project name\n          </Label>\n          <Input\n            defaultValue=\"Open Source Stripe\"\n            id=\"projectName\"\n            type=\"text\"\n          />\n        </div>\n\n        <div>\n          <Label className=\"mb-2\" htmlFor=\"projectLead\">\n            Project lead\n          </Label>\n          <Select\n            defaultValue=\"1\"\n            items={{\n              '1': 'Ephraim Duncan',\n              '2': 'Lucas Smith',\n              '3': 'Timur Ercan',\n            }}\n          >\n            <SelectTrigger className=\"ps-2\" id=\"projectLead\">\n              <SelectValue placeholder=\"Select framework\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectGroup>\n                <SelectItem value=\"1\">\n                  <img\n                    alt=\"Ephraim Duncan\"\n                    className=\"size-5 rounded\"\n                    height={20}\n                    src=\"https://blocks.so/avatar-01.png\"\n                    width={20}\n                  />\n                  <span className=\"truncate\">Ephraim Duncan</span>\n                </SelectItem>\n                <SelectItem value=\"2\">\n                  <img\n                    alt=\"Lucas Smith\"\n                    className=\"size-5 rounded\"\n                    height={20}\n                    src=\"https://blocks.so/avatar-03.png\"\n                    width={20}\n                  />\n                  <span className=\"truncate\">Lucas Smith</span>\n                </SelectItem>\n                <SelectItem value=\"3\">\n                  <img\n                    alt=\"Timur Ercan\"\n                    className=\"size-5 rounded\"\n                    height={20}\n                    src=\"https://blocks.so/avatar-02.jpg\"\n                    width={20}\n                  />\n                  <span className=\"truncate\">Timur Ercan</span>\n                </SelectItem>\n              </SelectGroup>\n            </SelectContent>\n          </Select>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/file-upload/file-upload-01/index.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/index.tsx",
          "content": "'use client';\n\nimport { HelpCircle } from 'lucide-react';\nimport { useRef, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"./shared/components/ui/tooltip\";\nimport { FileDropzone } from \"./dropzone\";\nimport { FileList } from \"./file-list\";\nimport { Form } from \"./form\";\n\nexport default function FileUpload01() {\n  const fileInputRef = useRef<HTMLInputElement>(null);\n  const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);\n  const [fileProgresses, setFileProgresses] = useState<Record<string, number>>(\n    {}\n  );\n\n  const handleFileSelect = (files: FileList | null) => {\n    if (!files) return;\n\n    const newFiles = Array.from(files);\n    setUploadedFiles((prev) => [...prev, ...newFiles]);\n\n    newFiles.forEach((file) => {\n      let progress = 0;\n      const interval = setInterval(() => {\n        progress += Math.random() * 10;\n        if (progress >= 100) {\n          progress = 100;\n          clearInterval(interval);\n        }\n        setFileProgresses((prev) => ({\n          ...prev,\n          [file.name]: Math.min(progress, 100),\n        }));\n      }, 300);\n    });\n  };\n\n  const handleBoxClick = () => {\n    fileInputRef.current?.click();\n  };\n\n  const handleDragOver = (e: React.DragEvent) => {\n    e.preventDefault();\n  };\n\n  const handleDrop = (e: React.DragEvent) => {\n    e.preventDefault();\n    handleFileSelect(e.dataTransfer.files);\n  };\n\n  const removeFile = (filename: string) => {\n    setUploadedFiles((prev) => prev.filter((file) => file.name !== filename));\n    setFileProgresses((prev) => {\n      const newProgresses = { ...prev };\n      delete newProgresses[filename];\n      return newProgresses;\n    });\n  };\n\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <Card className=\"mx-auto w-full max-w-lg rounded-lg bg-background p-0 shadow-md\">\n        <CardContent className=\"p-0\">\n          <div className=\"p-6 pb-4\">\n            <div className=\"flex items-start justify-between\">\n              <div>\n                <h2 className=\"font-medium text-foreground text-lg\">\n                  Create a new project\n                </h2>\n                <p className=\"mt-1 text-muted-foreground text-sm\">\n                  Drag and drop files to create a new project.\n                </p>\n              </div>\n            </div>\n          </div>\n          <Form />\n          <FileDropzone\n            fileInputRef={fileInputRef}\n            handleBoxClick={handleBoxClick}\n            handleDragOver={handleDragOver}\n            handleDrop={handleDrop}\n            handleFileSelect={handleFileSelect}\n          />\n          <FileList\n            fileProgresses={fileProgresses}\n            removeFile={removeFile}\n            uploadedFiles={uploadedFiles}\n          />\n          <div className=\"flex items-center justify-between rounded-b-lg border-border border-t bg-muted px-6 py-3\">\n            <TooltipProvider delay={0}>\n              <Tooltip>\n                <TooltipTrigger\n                  render={\n                    <Button\n                      className=\"flex items-center text-muted-foreground hover:text-foreground\"\n                      size=\"sm\"\n                      variant=\"ghost\"\n                    />\n                  }\n                >\n                  <HelpCircle className=\"mr-1 h-4 w-4\" />\n                  Need help?\n                </TooltipTrigger>\n                <TooltipContent className=\"border bg-background py-3 text-foreground\">\n                  <div className=\"space-y-1\">\n                    <p className=\"font-medium text-[13px]\">Need assistance?</p>\n                    <p className=\"max-w-[200px] text-muted-foreground text-xs dark:text-muted-background\">\n                      Upload project images by dragging and dropping files or\n                      using the file browser. Supported formats: JPG, PNG, SVG.\n                      Maximum file size: 4MB.\n                    </p>\n                  </div>\n                </TooltipContent>\n              </Tooltip>\n            </TooltipProvider>\n\n            <div className=\"flex gap-2\">\n              <Button\n                className=\"h-9 px-4 font-medium text-sm\"\n                variant=\"outline\"\n              >\n                Cancel\n              </Button>\n              <Button className=\"h-9 px-4 font-medium text-sm\">Continue</Button>\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/file-upload-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/file-upload-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "file-upload",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/file-upload-01/index.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/file-upload/file-upload-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "upload",
          "file",
          "dropzone",
          "media",
          "上传",
          "文件",
          "拖拽",
          "工作台"
        ],
        "contentGuidelines": [
          "Connect file validation and upload callbacks to your own service.",
          "Test long filenames, rejected files, and the empty state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/file-upload-01/index.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/file-upload-01"
      }
    },
    {
      "name": "file-upload-02",
      "type": "registry:block",
      "title": "File Upload Simple",
      "description": "File Upload Simple. File selection, drag and drop, previews, and upload progress for a product workspace.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "content/components/file-upload/file-upload-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-02/file-upload-02.tsx",
          "content": "import { Button } from \"./shared/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"./shared/components/ui/card\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\nexport default function FileUpload02() {\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <Card className=\"shadow-none\">\n        <CardHeader>\n          <CardTitle>Set up your first workspace</CardTitle>\n          <CardDescription>\n            Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\n          </CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form action=\"#\" method=\"POST\">\n            <div className=\"space-y-6\">\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"workspace-name\">\n                  Workspace <span className=\"text-destructive\">*</span>\n                </Label>\n                <Input\n                  autoComplete=\"workspace-name\"\n                  id=\"workspace-name\"\n                  name=\"workspace-name\"\n                  placeholder=\"Workspace name\"\n                  required\n                  type=\"text\"\n                />\n              </div>\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"file-1\">\n                  Upload file <span className=\"text-destructive\">*</span>\n                </Label>\n                <Input\n                  accept=\".csv, .xlsx, .xls\"\n                  id=\"file-1\"\n                  name=\"file-1\"\n                  type=\"file\"\n                />\n                <p className=\"text-pretty text-muted-foreground text-sm\">\n                  You are only allowed to upload CSV, XLSX or XLS files.\n                </p>\n              </div>\n            </div>\n            <div className=\"mt-8 flex justify-end space-x-3\">\n              <Button type=\"button\" variant=\"outline\">\n                Cancel\n              </Button>\n              <Button type=\"submit\">Submit</Button>\n            </div>\n          </form>\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/file-upload-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/file-upload-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-02/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-02/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-02/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "file-upload",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/file-upload-02/file-upload-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/file-upload/file-upload-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "upload",
          "file",
          "dropzone",
          "media",
          "上传",
          "文件",
          "拖拽",
          "工作台"
        ],
        "contentGuidelines": [
          "Connect file validation and upload callbacks to your own service.",
          "Test long filenames, rejected files, and the empty state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/file-upload-02/file-upload-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/file-upload-02"
      }
    },
    {
      "name": "file-upload-03",
      "type": "registry:block",
      "title": "File Upload Multi-File Dropzone",
      "description": "File Upload Multi-File Dropzone. File selection, drag and drop, previews, and upload progress for a product workspace.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0",
        "react-dropzone@14.4.1"
      ],
      "files": [
        {
          "path": "content/components/file-upload/file-upload-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-03/file-upload-03.tsx",
          "content": "'use client';\n\nimport { File, Trash } from 'lucide-react';\nimport React from 'react';\nimport { useDropzone } from 'react-dropzone';\n\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"./shared/components/ui/card\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport { Separator } from \"./shared/components/ui/separator\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport default function FileUpload03() {\n  const [files, setFiles] = React.useState<File[]>([]);\n  const { getRootProps, getInputProps, isDragActive } = useDropzone({\n    onDrop: (acceptedFiles) => setFiles(acceptedFiles),\n  });\n\n  const filesList = files.map((file) => (\n    <li className=\"relative\" key={file.name}>\n      <Card className=\"relative p-4 shadow-none\">\n        <div className=\"-translate-y-1/2 absolute top-1/2 right-4\">\n          <Button\n            aria-label=\"Remove file\"\n            onClick={() =>\n              setFiles((prevFiles) =>\n                prevFiles.filter((prevFile) => prevFile.name !== file.name)\n              )\n            }\n            size=\"icon\"\n            type=\"button\"\n            variant=\"ghost\"\n          >\n            <Trash aria-hidden={true} className=\"h-5 w-5\" />\n          </Button>\n        </div>\n        <CardContent className=\"flex items-center space-x-3 p-0\">\n          <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-muted\">\n            <File aria-hidden={true} className=\"h-5 w-5 text-foreground\" />\n          </span>\n          <div>\n            <p className=\"text-pretty font-medium text-foreground\">\n              {file.name}\n            </p>\n            <p className=\"mt-0.5 text-pretty text-muted-foreground text-sm\">\n              {file.size} bytes\n            </p>\n          </div>\n        </CardContent>\n      </Card>\n    </li>\n  ));\n\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <Card className=\"shadow-none sm:mx-auto sm:max-w-xl\">\n        <CardHeader>\n          <CardTitle>Set up your first cloud storage</CardTitle>\n          <CardDescription>\n            Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\n          </CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form action=\"#\" method=\"post\">\n            <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-6\">\n              <div className=\"col-span-full sm:col-span-3\">\n                <Label className=\"font-medium\" htmlFor=\"bucket-name\">\n                  Bucket name\n                </Label>\n                <Input\n                  className=\"mt-2\"\n                  id=\"bucket-name\"\n                  name=\"bucket-name\"\n                  placeholder=\"Bucket name\"\n                  type=\"text\"\n                />\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Label className=\"font-medium\" htmlFor=\"visibility\">\n                  Visibility\n                </Label>\n                <Select\n                  defaultValue=\"private\"\n                  disabled\n                  items={{ private: 'Private', public: 'Public' }}\n                >\n                  <SelectTrigger\n                    className=\"mt-2 w-full\"\n                    id=\"visibility\"\n                    name=\"visibility\"\n                  >\n                    <SelectValue placeholder=\"Select visibility\" />\n                  </SelectTrigger>\n                  <SelectContent>\n                    <SelectItem value=\"private\">Private</SelectItem>\n                    <SelectItem value=\"public\">Public</SelectItem>\n                  </SelectContent>\n                </Select>\n                <p className=\"mt-2 text-pretty text-muted-foreground text-sm\">\n                  Only admins can change visibility.\n                </p>\n              </div>\n              <div className=\"col-span-full\">\n                <Label className=\"font-medium\" htmlFor=\"file-upload-2\">\n                  File(s) upload\n                </Label>\n                <div\n                  {...getRootProps()}\n                  className={cn(\n                    isDragActive\n                      ? 'border-primary bg-primary/10 ring-2 ring-primary/20'\n                      : 'border-border',\n                    'mt-2 flex justify-center rounded-md border border-dashed px-6 py-20 transition-colors duration-200'\n                  )}\n                >\n                  <div>\n                    <File\n                      aria-hidden={true}\n                      className=\"mx-auto h-12 w-12 text-muted-foreground/80\"\n                    />\n                    <div className=\"mt-4 flex text-muted-foreground\">\n                      <p>Drag and drop or</p>\n                      <label\n                        className=\"relative cursor-pointer rounded-sm pl-1 font-medium text-primary hover:text-primary/80 hover:underline hover:underline-offset-4\"\n                        htmlFor=\"file\"\n                      >\n                        <span>choose file(s)</span>\n                        <input\n                          {...getInputProps()}\n                          className=\"sr-only\"\n                          id=\"file-upload-2\"\n                          name=\"file-upload-2\"\n                          type=\"file\"\n                        />\n                      </label>\n                      <p className=\"text-pretty pl-1\">to upload</p>\n                    </div>\n                  </div>\n                </div>\n                <p className=\"mt-2 text-pretty text-muted-foreground text-sm leading-5 sm:flex sm:items-center sm:justify-between\">\n                  <span>All file types are allowed to upload.</span>\n                  <span className=\"pl-1 sm:pl-0\">Max. size per file: 50MB</span>\n                </p>\n                {filesList.length > 0 && (\n                  <>\n                    <h4 className=\"mt-6 text-balance font-medium text-foreground\">\n                      File(s) to upload\n                    </h4>\n                    <ul className=\"mt-4 space-y-4\" role=\"list\">\n                      {filesList}\n                    </ul>\n                  </>\n                )}\n              </div>\n            </div>\n            <Separator className=\"my-6\" />\n            <div className=\"flex items-center justify-end space-x-3\">\n              <Button type=\"button\" variant=\"outline\">\n                Cancel\n              </Button>\n              <Button type=\"submit\">Upload</Button>\n            </div>\n          </form>\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/file-upload-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/file-upload-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-03/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-03/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-03/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-03/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-03/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-03/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "file-upload",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/file-upload-03/file-upload-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/file-upload/file-upload-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "upload",
          "file",
          "dropzone",
          "media",
          "上传",
          "文件",
          "拖拽",
          "工作台"
        ],
        "contentGuidelines": [
          "Connect file validation and upload callbacks to your own service.",
          "Test long filenames, rejected files, and the empty state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/file-upload-03/file-upload-03.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/file-upload-03"
      }
    },
    {
      "name": "file-upload-04",
      "type": "registry:block",
      "title": "File Upload Drag & Drop",
      "description": "File Upload Drag & Drop. File selection, drag and drop, previews, and upload progress for a product workspace.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0",
        "sonner@2.0.8"
      ],
      "files": [
        {
          "path": "content/components/file-upload/file-upload-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-04/file-upload-04.tsx",
          "content": "'use client';\n\nimport { File, FileSpreadsheet, X } from 'lucide-react';\nimport { type ChangeEvent, type DragEvent, useRef, useState } from 'react';\nimport { toast } from 'sonner';\n\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card } from \"./shared/components/ui/card\";\nimport { Progress } from \"./shared/components/ui/progress\";\n\nexport default function FileUpload04() {\n  const [uploadState, setUploadState] = useState<{\n    file: File | null;\n    progress: number;\n    uploading: boolean;\n  }>({\n    file: null,\n    progress: 0,\n    uploading: false,\n  });\n  const [showDummy, setShowDummy] = useState(true);\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const validFileTypes = [\n    'text/csv',\n    'application/vnd.ms-excel',\n    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n  ];\n\n  const handleFile = (file: File | undefined) => {\n    if (!file) return;\n\n    if (validFileTypes.includes(file.type)) {\n      setUploadState({ file, progress: 0, uploading: true });\n\n      const interval = setInterval(() => {\n        setUploadState((prev) => {\n          const newProgress = prev.progress + 5;\n          if (newProgress >= 100) {\n            clearInterval(interval);\n            return { ...prev, progress: 100, uploading: false };\n          }\n          return { ...prev, progress: newProgress };\n        });\n      }, 200);\n    } else {\n      toast.error('Please upload a CSV, XLSX, or XLS file.', {\n        position: 'bottom-right',\n        duration: 3000,\n      });\n    }\n  };\n\n  const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {\n    handleFile(event.target.files?.[0]);\n  };\n\n  const handleDrop = (event: DragEvent<HTMLDivElement>) => {\n    event.preventDefault();\n    handleFile(event.dataTransfer.files?.[0]);\n  };\n\n  const resetFile = () => {\n    setUploadState({ file: null, progress: 0, uploading: false });\n    if (fileInputRef.current) {\n      fileInputRef.current.value = '';\n    }\n  };\n\n  const getFileIcon = () => {\n    if (!uploadState.file) return <File />;\n\n    const fileExt = uploadState.file.name.split('.').pop()?.toLowerCase() || '';\n    return ['csv', 'xlsx', 'xls'].includes(fileExt) ? (\n      <FileSpreadsheet className=\"h-5 w-5 text-foreground\" />\n    ) : (\n      <File className=\"h-5 w-5 text-foreground\" />\n    );\n  };\n\n  const formatFileSize = (bytes: number) => {\n    if (bytes === 0) return '0 Bytes';\n    const k = 1024;\n    const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n    return Number.parseFloat((bytes / k ** i).toFixed(1)) + ' ' + sizes[i];\n  };\n\n  const { file, progress, uploading } = uploadState;\n\n  return (\n    <div className=\"flex w-full max-w-lg items-center justify-center p-10\">\n      <form className=\"w-full\" onSubmit={(e) => e.preventDefault()}>\n        <h3 className=\"text-balance font-semibold text-foreground text-lg\">\n          File Upload\n        </h3>\n\n        <div\n          className=\"mt-2 flex justify-center rounded-md border border-input border-dashed px-6 py-12\"\n          onDragOver={(e) => e.preventDefault()}\n          onDrop={handleDrop}\n        >\n          <div>\n            <File\n              aria-hidden={true}\n              className=\"mx-auto h-12 w-12 text-muted-foreground\"\n            />\n            <div className=\"flex text-muted-foreground text-sm leading-6\">\n              <p>Drag and drop or</p>\n              <label\n                className=\"relative cursor-pointer rounded-sm pl-1 font-medium text-primary hover:underline hover:underline-offset-4\"\n                htmlFor=\"file-upload-03\"\n              >\n                <span>choose file</span>\n                <input\n                  accept=\".csv, .xlsx, .xls\"\n                  className=\"sr-only\"\n                  id=\"file-upload-03\"\n                  name=\"file-upload-03\"\n                  onChange={handleFileChange}\n                  ref={fileInputRef}\n                  type=\"file\"\n                />\n              </label>\n              <p className=\"text-pretty pl-1\">to upload</p>\n            </div>\n          </div>\n        </div>\n\n        <p className=\"mt-2 text-pretty text-muted-foreground text-xs leading-5 sm:flex sm:items-center sm:justify-between\">\n          <span>Accepted file types: CSV, XLSX or XLS files.</span>\n          <span className=\"pl-1 sm:pl-0\">Max. size: 10MB</span>\n        </p>\n\n        {!file && showDummy && (\n          <Card className=\"relative mt-8 gap-4 bg-muted p-4 shadow-none\">\n            <Button\n              aria-label=\"Remove\"\n              className=\"absolute top-1 right-1 text-muted-foreground hover:text-foreground\"\n              onClick={() => setShowDummy(false)}\n              size=\"icon-sm\"\n              type=\"button\"\n              variant=\"ghost\"\n            >\n              <X aria-hidden={true} className=\"h-5 w-5 shrink-0\" />\n            </Button>\n\n            <div className=\"flex items-center space-x-2.5\">\n              <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-sm bg-background shadow-sm ring-1 ring-border ring-inset\">\n                <FileSpreadsheet\n                  aria-hidden={true}\n                  className=\"h-5 w-5 text-foreground\"\n                />\n              </span>\n              <div>\n                <p className=\"text-pretty font-medium text-foreground text-xs\">\n                  Revenue_Q1_2024.xlsx\n                </p>\n                <p className=\"mt-0.5 text-pretty text-muted-foreground text-xs\">\n                  3.1 MB\n                </p>\n              </div>\n            </div>\n\n            <div className=\"flex items-center space-x-3\">\n              <Progress className=\"h-1.5\" value={45} />\n              <span className=\"text-muted-foreground text-xs\">45%</span>\n            </div>\n          </Card>\n        )}\n\n        {file && (\n          <Card className=\"relative mt-8 gap-4 bg-muted p-4 shadow-none\">\n            <Button\n              aria-label=\"Remove\"\n              className=\"absolute top-1 right-1 text-muted-foreground hover:text-foreground\"\n              onClick={resetFile}\n              size=\"icon-sm\"\n              type=\"button\"\n              variant=\"ghost\"\n            >\n              <X aria-hidden={true} className=\"h-5 w-5 shrink-0\" />\n            </Button>\n\n            <div className=\"flex items-center space-x-2.5\">\n              <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-sm bg-background shadow-sm ring-1 ring-border ring-inset\">\n                {getFileIcon()}\n              </span>\n              <div>\n                <p className=\"text-pretty font-medium text-foreground text-xs\">\n                  {file?.name}\n                </p>\n                <p className=\"mt-0.5 text-pretty text-muted-foreground text-xs\">\n                  {file && formatFileSize(file.size)}\n                </p>\n              </div>\n            </div>\n\n            <div className=\"flex items-center space-x-3\">\n              <Progress className=\"h-1.5\" value={progress} />\n              <span className=\"text-muted-foreground text-xs\">{progress}%</span>\n            </div>\n          </Card>\n        )}\n\n        <div className=\"mt-8 flex items-center justify-end space-x-3\">\n          <Button\n            className=\"whitespace-nowrap\"\n            disabled={!file}\n            onClick={resetFile}\n            type=\"button\"\n            variant=\"outline\"\n          >\n            Cancel\n          </Button>\n          <Button\n            className=\"whitespace-nowrap\"\n            disabled={!file || uploading || progress < 100}\n            type=\"submit\"\n          >\n            Upload\n          </Button>\n        </div>\n      </form>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/file-upload-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/file-upload-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-04/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-04/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/progress.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-04/shared/components/ui/progress.tsx",
          "content": "'use client';\n\nimport { Progress as ProgressPrimitive } from '@base-ui/react/progress';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Progress({\n  className,\n  children,\n  value,\n  ...props\n}: ProgressPrimitive.Root.Props) {\n  return (\n    <ProgressPrimitive.Root\n      className={cn('flex flex-wrap gap-3', className)}\n      data-slot=\"progress\"\n      value={value}\n      {...props}\n    >\n      {children}\n      <ProgressTrack>\n        <ProgressIndicator />\n      </ProgressTrack>\n    </ProgressPrimitive.Root>\n  );\n}\n\nfunction ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {\n  return (\n    <ProgressPrimitive.Track\n      className={cn(\n        'relative flex h-1 w-full items-center overflow-x-hidden rounded-full bg-muted',\n        className\n      )}\n      data-slot=\"progress-track\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressIndicator({\n  className,\n  ...props\n}: ProgressPrimitive.Indicator.Props) {\n  return (\n    <ProgressPrimitive.Indicator\n      className={cn('h-full bg-primary transition-all', className)}\n      data-slot=\"progress-indicator\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {\n  return (\n    <ProgressPrimitive.Label\n      className={cn('font-medium text-sm', className)}\n      data-slot=\"progress-label\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {\n  return (\n    <ProgressPrimitive.Value\n      className={cn(\n        'ml-auto text-muted-foreground text-sm tabular-nums',\n        className\n      )}\n      data-slot=\"progress-value\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Progress,\n  ProgressTrack,\n  ProgressIndicator,\n  ProgressLabel,\n  ProgressValue,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "file-upload",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/file-upload-04/file-upload-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/file-upload/file-upload-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "upload",
          "file",
          "dropzone",
          "media",
          "上传",
          "文件",
          "拖拽",
          "工作台"
        ],
        "contentGuidelines": [
          "Connect file validation and upload callbacks to your own service.",
          "Test long filenames, rejected files, and the empty state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/file-upload-04/file-upload-04.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/file-upload-04"
      }
    },
    {
      "name": "file-upload-05",
      "type": "registry:block",
      "title": "File Upload with Progress",
      "description": "File Upload with Progress. File selection, drag and drop, previews, and upload progress for a product workspace.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/file-upload/file-upload-05.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-05/file-upload-05.tsx",
          "content": "import { FileSpreadsheet, Upload, X } from 'lucide-react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Label } from \"./shared/components/ui/label\";\n\nexport default function FileUpload05() {\n  return (\n    <div className=\"flex w-full max-w-lg items-center justify-center p-10 sm:mx-auto sm:max-w-lg\">\n      <form>\n        <h3 className=\"text-balance font-semibold text-foreground text-lg\">\n          File Upload\n        </h3>\n        <div className=\"mt-4 flex justify-center space-x-4 rounded-md border border-input border-dashed px-6 py-10\">\n          <div className=\"sm:flex sm:items-center sm:gap-x-3\">\n            <Upload\n              aria-hidden={true}\n              className=\"mx-auto h-8 w-8 text-muted-foreground sm:mx-0 sm:h-6 sm:w-6\"\n            />\n            <div className=\"mt-4 flex text-foreground text-sm leading-6 sm:mt-0\">\n              <p>Drag and drop or</p>\n              <Label\n                className=\"relative cursor-pointer rounded-sm pl-1 font-medium text-primary hover:underline hover:underline-offset-4\"\n                htmlFor=\"file-upload-4\"\n              >\n                <span>choose file</span>\n                <input\n                  className=\"sr-only\"\n                  id=\"file-upload-4\"\n                  name=\"file-upload-4\"\n                  type=\"file\"\n                />\n              </Label>\n              <p className=\"text-pretty pl-1\">to upload</p>\n            </div>\n          </div>\n        </div>\n        <p className=\"mt-2 flex items-center justify-between text-pretty text-muted-foreground text-xs leading-5\">\n          Recommended max. size: 10 MB, Accepted file types: XLSX, XLS, CSV.\n        </p>\n        <div className=\"relative mt-8 rounded-lg bg-muted p-3\">\n          <div className=\"absolute top-1 right-1\">\n            <Button\n              aria-label=\"Remove\"\n              className=\"rounded-sm p-2 text-muted-foreground hover:text-foreground\"\n              size=\"sm\"\n              type=\"button\"\n              variant=\"ghost\"\n            >\n              <X aria-hidden={true} className=\"size-4 shrink-0\" />\n            </Button>\n          </div>\n          <div className=\"flex items-center space-x-2.5\">\n            <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-sm bg-background shadow-sm ring-1 ring-input ring-inset\">\n              <FileSpreadsheet\n                aria-hidden={true}\n                className=\"size-5 text-foreground\"\n              />\n            </span>\n            <div className=\"w-full\">\n              <p className=\"text-pretty font-medium text-foreground text-xs\">\n                Revenue_Q1_2024.xlsx\n              </p>\n              <p className=\"mt-0.5 flex justify-between text-pretty text-muted-foreground text-xs\">\n                <span>3.1 MB</span>\n                <span>Completed</span>\n              </p>\n            </div>\n          </div>\n        </div>\n        <div className=\"mt-8 flex items-center justify-end space-x-3\">\n          <Button\n            className=\"whitespace-nowrap rounded-sm border border-input px-4 py-2 font-medium text-foreground text-sm shadow-sm hover:bg-accent hover:text-foreground\"\n            type=\"button\"\n            variant=\"outline\"\n          >\n            Cancel\n          </Button>\n          <Button\n            className=\"whitespace-nowrap rounded-sm bg-primary px-4 py-2 font-medium text-primary-foreground text-sm shadow-sm hover:bg-primary/90\"\n            type=\"submit\"\n            variant=\"default\"\n          >\n            Upload\n          </Button>\n        </div>\n      </form>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/file-upload-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/file-upload-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-05/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-05/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "file-upload",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/file-upload-05/file-upload-05.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/file-upload/file-upload-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "upload",
          "file",
          "dropzone",
          "media",
          "上传",
          "文件",
          "拖拽",
          "工作台"
        ],
        "contentGuidelines": [
          "Connect file validation and upload callbacks to your own service.",
          "Test long filenames, rejected files, and the empty state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/file-upload-05/file-upload-05.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/file-upload-05"
      }
    },
    {
      "name": "file-upload-06",
      "type": "registry:block",
      "title": "File Upload with Status Sections",
      "description": "File Upload with Status Sections. File selection, drag and drop, previews, and upload progress for a product workspace.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/file-upload/file-upload-06.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-06/file-upload-06.tsx",
          "content": "'use client';\n\nimport { CheckCircle, FileText, Loader2, Upload, X } from 'lucide-react';\nimport type React from 'react';\nimport { useRef, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card } from \"./shared/components/ui/card\";\nimport { Progress } from \"./shared/components/ui/progress\";\nimport { Separator } from \"./shared/components/ui/separator\";\n\ninterface UploadItem {\n  id: string;\n  name: string;\n  progress: number;\n  status: 'uploading' | 'completed';\n}\n\nexport default function FileUpload06() {\n  const [uploads, setUploads] = useState<UploadItem[]>([\n    {\n      id: 'a1',\n      name: 'design-mock-landing.png',\n      progress: 62,\n      status: 'uploading',\n    },\n    {\n      id: 'b2',\n      name: 'team-headshot-2025-01-09.jpg',\n      progress: 28,\n      status: 'uploading',\n    },\n    {\n      id: 'c3',\n      name: 'logo-v3-final.gif',\n      progress: 100,\n      status: 'completed',\n    },\n  ]);\n  const filePickerRef = useRef<HTMLInputElement>(null);\n\n  const openFilePicker = () => {\n    filePickerRef.current?.click();\n  };\n\n  const onFileInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    const selectedFiles = event.target.files;\n    if (selectedFiles) {\n      console.log('Files selected:', selectedFiles);\n    }\n  };\n\n  const onDragOver = (event: React.DragEvent) => {\n    event.preventDefault();\n  };\n\n  const onDropFiles = (event: React.DragEvent) => {\n    event.preventDefault();\n    const droppedFiles = event.dataTransfer.files;\n    if (droppedFiles) {\n      console.log('Files dropped:', droppedFiles);\n    }\n  };\n\n  const removeUploadById = (id: string) => {\n    setUploads(uploads.filter((file) => file.id !== id));\n  };\n\n  const activeUploads = uploads.filter((file) => file.status === 'uploading');\n  const completedUploads = uploads.filter(\n    (file) => file.status === 'completed'\n  );\n\n  return (\n    <div className=\"mx-auto flex w-full max-w-sm flex-col gap-y-6\">\n      <Card\n        className=\"group flex max-h-[200px] w-full cursor-pointer flex-col items-center justify-center gap-4 border-dashed py-8 text-sm shadow-none transition-colors hover:bg-muted/50\"\n        onClick={openFilePicker}\n        onDragOver={onDragOver}\n        onDrop={onDropFiles}\n      >\n        <div className=\"grid space-y-3\">\n          <div className=\"flex items-center gap-x-2 text-muted-foreground\">\n            <Upload className=\"size-5\" />\n            <div>\n              Drop files here or{' '}\n              <Button\n                className=\"h-auto p-0 font-normal text-primary\"\n                onClick={openFilePicker}\n                variant=\"link\"\n              >\n                browse files\n              </Button>{' '}\n              to add\n            </div>\n          </div>\n        </div>\n        <input\n          accept=\"image/png,image/jpeg,image/gif\"\n          className=\"hidden\"\n          multiple\n          onChange={onFileInputChange}\n          ref={filePickerRef}\n          type=\"file\"\n        />\n        <span className=\"mt-2 block text-base/6 text-muted-foreground group-disabled:opacity-50 sm:text-xs\">\n          Supported: JPG, PNG, GIF (max 10 MB)\n        </span>\n      </Card>\n\n      <div className=\"flex flex-col gap-y-4\">\n        {activeUploads.length > 0 && (\n          <div>\n            <h2 className=\"mb-4 flex items-center text-balance font-mono font-normal text-foreground text-lg uppercase sm:text-xs\">\n              <Loader2 className=\"mr-1 size-4 animate-spin\" />\n              Uploading\n            </h2>\n            <div className=\"-mt-2 divide-y\">\n              {activeUploads.map((file) => (\n                <div className=\"group flex items-center py-4\" key={file.id}>\n                  <div className=\"mr-3 grid size-10 shrink-0 place-content-center rounded border bg-muted\">\n                    <FileText className=\"inline size-4 group-hover:hidden\" />\n                    <Button\n                      aria-label=\"Cancel\"\n                      className=\"hidden size-4 h-auto p-0 group-hover:inline\"\n                      onClick={() => removeUploadById(file.id)}\n                      size=\"icon\"\n                      variant=\"ghost\"\n                    >\n                      <X className=\"size-4\" />\n                    </Button>\n                  </div>\n                  <div className=\"mb-1 flex w-full flex-col\">\n                    <div className=\"flex justify-between gap-2\">\n                      <span className=\"select-none text-base/6 text-foreground group-disabled:opacity-50 sm:text-sm/6\">\n                        {file.name}\n                      </span>\n                      <span className=\"text-muted-foreground text-sm tabular-nums\">\n                        {file.progress}%\n                      </span>\n                    </div>\n                    <Progress\n                      className=\"mt-1 h-2 min-w-64\"\n                      value={file.progress}\n                    />\n                  </div>\n                </div>\n              ))}\n            </div>\n          </div>\n        )}\n\n        {activeUploads.length > 0 && completedUploads.length > 0 && (\n          <Separator className=\"my-0\" />\n        )}\n\n        {completedUploads.length > 0 && (\n          <div>\n            <h2 className=\"mb-4 flex items-center text-balance font-mono font-normal text-foreground text-lg uppercase sm:text-xs\">\n              <CheckCircle className=\"mr-1 size-4\" />\n              Finished\n            </h2>\n            <div className=\"-mt-2 divide-y\">\n              {completedUploads.map((file) => (\n                <div className=\"group flex items-center py-4\" key={file.id}>\n                  <div className=\"mr-3 grid size-10 shrink-0 place-content-center rounded border bg-muted\">\n                    <FileText className=\"inline size-4 group-hover:hidden\" />\n                    <Button\n                      aria-label=\"Remove\"\n                      className=\"hidden size-4 h-auto p-0 group-hover:inline\"\n                      onClick={() => removeUploadById(file.id)}\n                      size=\"icon\"\n                      variant=\"ghost\"\n                    >\n                      <X className=\"size-4\" />\n                    </Button>\n                  </div>\n                  <div className=\"mb-1 flex w-full flex-col\">\n                    <div className=\"flex justify-between gap-2\">\n                      <span className=\"select-none text-base/6 text-foreground group-disabled:opacity-50 sm:text-sm/6\">\n                        {file.name}\n                      </span>\n                      <span className=\"text-muted-foreground text-sm tabular-nums\">\n                        {file.progress}%\n                      </span>\n                    </div>\n                    <Progress\n                      className=\"mt-1 h-2 min-w-64\"\n                      value={file.progress}\n                    />\n                  </div>\n                </div>\n              ))}\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/file-upload-06/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/file-upload-06/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-06/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-06/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/progress.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-06/shared/components/ui/progress.tsx",
          "content": "'use client';\n\nimport { Progress as ProgressPrimitive } from '@base-ui/react/progress';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Progress({\n  className,\n  children,\n  value,\n  ...props\n}: ProgressPrimitive.Root.Props) {\n  return (\n    <ProgressPrimitive.Root\n      className={cn('flex flex-wrap gap-3', className)}\n      data-slot=\"progress\"\n      value={value}\n      {...props}\n    >\n      {children}\n      <ProgressTrack>\n        <ProgressIndicator />\n      </ProgressTrack>\n    </ProgressPrimitive.Root>\n  );\n}\n\nfunction ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {\n  return (\n    <ProgressPrimitive.Track\n      className={cn(\n        'relative flex h-1 w-full items-center overflow-x-hidden rounded-full bg-muted',\n        className\n      )}\n      data-slot=\"progress-track\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressIndicator({\n  className,\n  ...props\n}: ProgressPrimitive.Indicator.Props) {\n  return (\n    <ProgressPrimitive.Indicator\n      className={cn('h-full bg-primary transition-all', className)}\n      data-slot=\"progress-indicator\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {\n  return (\n    <ProgressPrimitive.Label\n      className={cn('font-medium text-sm', className)}\n      data-slot=\"progress-label\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {\n  return (\n    <ProgressPrimitive.Value\n      className={cn(\n        'ml-auto text-muted-foreground text-sm tabular-nums',\n        className\n      )}\n      data-slot=\"progress-value\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Progress,\n  ProgressTrack,\n  ProgressIndicator,\n  ProgressLabel,\n  ProgressValue,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-06/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/file-upload-06/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "file-upload",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/file-upload-06/file-upload-06.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/file-upload/file-upload-06 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "upload",
          "file",
          "dropzone",
          "media",
          "上传",
          "文件",
          "拖拽",
          "工作台"
        ],
        "contentGuidelines": [
          "Connect file validation and upload callbacks to your own service.",
          "Test long filenames, rejected files, and the empty state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/file-upload-06/file-upload-06.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/file-upload-06"
      }
    },
    {
      "name": "footer-01",
      "type": "registry:block",
      "title": "Product Footer",
      "description": "A minimal product footer with a brand statement, useful links, and a final workspace entry.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/marketing/footer-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/footer-01/footer-01.tsx",
          "content": "import { ArrowUpRight, Layers2 } from \"lucide-react\";\n\nexport type Footer01Props = {\n  brand?: string;\n  homeHref?: string;\n  tagline?: string;\n  links?: { label: string; href: string }[];\n  note?: string;\n};\n\n/** Compact product footer; replace the demonstration notice for a live product. */\nexport default function Footer01({\n  brand = \"Frame\",\n  homeHref = \"/preview/recipes/tool-landing#frame-top\",\n  tagline = \"More space for the work you love.\",\n  links = [\n    { label: \"Features\", href: \"/preview/recipes/tool-landing#features\" },\n    { label: \"Pricing\", href: \"/preview/recipes/tool-landing#pricing\" },\n    { label: \"Questions\", href: \"/preview/recipes/tool-landing#faq\" },\n    { label: \"Workspace\", href: \"/preview/recipes/tool-workspace\" },\n  ],\n  note = \"Frame is a fictional product. A page recipe by UIBlocks.\",\n}: Footer01Props) {\n  return (\n    <footer className=\"w-full border-t border-zinc-200/70 bg-zinc-50/70 py-10 text-zinc-950 dark:border-zinc-800 dark:bg-zinc-900/30 dark:text-white\">\n      <div className=\"mx-auto max-w-6xl px-6 lg:px-8\">\n        <div className=\"flex flex-col justify-between gap-8 sm:flex-row sm:items-center\">\n          <div>\n            <a\n              className=\"inline-flex items-center gap-2.5 rounded text-lg font-semibold tracking-tight focus-visible:outline-2 focus-visible:outline-violet-500\"\n              href={homeHref}\n            >\n              <span className=\"grid size-7 place-items-center rounded-lg bg-violet-600 text-white\">\n                <Layers2 aria-hidden=\"true\" className=\"size-4\" />\n              </span>\n              {brand}\n            </a>\n            <p className=\"mt-3 text-xs text-zinc-500 dark:text-zinc-400\">\n              {tagline}\n            </p>\n          </div>\n          <nav\n            aria-label=\"Footer product navigation\"\n            className=\"flex flex-wrap gap-x-7 gap-y-4\"\n          >\n            {links.map((link) => (\n              <a\n                className=\"inline-flex items-center gap-1.5 rounded text-xs text-zinc-500 transition-colors hover:text-zinc-950 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-zinc-400 dark:hover:text-white\"\n                href={link.href}\n                key={link.href}\n              >\n                {link.label}\n                <ArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n              </a>\n            ))}\n          </nav>\n        </div>\n        <div className=\"mt-8 border-t border-zinc-200 pt-6 text-[10px] leading-5 text-zinc-400 dark:border-zinc-800\">\n          {note}\n        </div>\n      </div>\n    </footer>\n  );\n}\n"
        },
        {
          "path": "licenses/footer-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/footer-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        }
      ],
      "categories": [
        "marketing",
        "marketing",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/footer-01/footer-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/marketing/footer-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "marketing",
        "featured": true,
        "tags": [
          "footer",
          "navigation",
          "links",
          "legal",
          "页脚",
          "底部",
          "链接",
          "版权"
        ],
        "contentGuidelines": [
          "Only show links that have a real destination.",
          "Set the product name, copyright owner, and legal destinations for your product."
        ],
        "recommendedWith": [
          "navigation-01",
          "faq-01"
        ],
        "entry": "@components/uiblocks/footer-01/footer-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/footer-01"
      }
    },
    {
      "name": "form-layout-01",
      "type": "registry:block",
      "title": "Form with Workspace Fields",
      "description": "Form with Workspace Fields. Structured product forms with labeled fields, preferences, and account settings.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "content/components/form-layout/form-layout-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-01/form-layout-01.tsx",
          "content": "import { Button } from \"./shared/components/ui/button\";\nimport { Field, FieldLabel } from \"./shared/components/ui/field\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Separator } from \"./shared/components/ui/separator\";\n\nexport default function FormLayout01() {\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <div className=\"sm:mx-auto sm:max-w-2xl\">\n        <h3 className=\"text-balance font-semibold text-2xl text-foreground dark:text-foreground\">\n          Register to workspace\n        </h3>\n        <p className=\"mt-1 text-pretty text-muted-foreground text-sm dark:text-muted-foreground\">\n          Take a few moments to register for your company&apos;s workspace\n        </p>\n        <form action=\"#\" className=\"mt-8\" method=\"post\">\n          <div className=\"grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-6\">\n            <div className=\"col-span-full sm:col-span-3\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"first-name\">\n                  First name\n                  <span className=\"text-red-500\">*</span>\n                </FieldLabel>\n                <Input\n                  autoComplete=\"first-name\"\n                  id=\"first-name\"\n                  name=\"first-name\"\n                  placeholder=\"First name\"\n                  required\n                  type=\"text\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full sm:col-span-3\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"last-name\">\n                  Last name\n                  <span className=\"text-red-500\">*</span>\n                </FieldLabel>\n                <Input\n                  autoComplete=\"last-name\"\n                  id=\"last-name\"\n                  name=\"last-name\"\n                  placeholder=\"Last name\"\n                  required\n                  type=\"text\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"email\">\n                  Email\n                  <span className=\"text-red-500\">*</span>\n                </FieldLabel>\n                <Input\n                  autoComplete=\"email\"\n                  id=\"email\"\n                  name=\"email\"\n                  placeholder=\"Email\"\n                  required\n                  type=\"email\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"address\">Address</FieldLabel>\n                <Input\n                  autoComplete=\"street-address\"\n                  id=\"address\"\n                  name=\"address\"\n                  placeholder=\"Address\"\n                  type=\"text\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full sm:col-span-2\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"city\">City</FieldLabel>\n                <Input\n                  autoComplete=\"address-level2\"\n                  id=\"city\"\n                  name=\"city\"\n                  placeholder=\"City\"\n                  type=\"text\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full sm:col-span-2\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"state\">State</FieldLabel>\n                <Input\n                  autoComplete=\"address-level1\"\n                  id=\"state\"\n                  name=\"state\"\n                  placeholder=\"State\"\n                  type=\"text\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full sm:col-span-2\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"postal-code\">Postal code</FieldLabel>\n                <Input\n                  autoComplete=\"postal-code\"\n                  id=\"postal-code\"\n                  name=\"postal-code\"\n                  placeholder=\"Postal code\"\n                />\n              </Field>\n            </div>\n          </div>\n          <Separator className=\"my-6\" />\n          <div className=\"flex items-center justify-end space-x-4\">\n            <Button\n              className=\"whitespace-nowrap\"\n              type=\"button\"\n              variant=\"outline\"\n            >\n              Cancel\n            </Button>\n            <Button className=\"whitespace-nowrap\" type=\"submit\">\n              Submit\n            </Button>\n          </div>\n        </form>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/form-layout-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/form-layout-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/field.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-01/shared/components/ui/field.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useMemo } from 'react';\nimport { Label } from \"./label\";\nimport { Separator } from \"./separator\";\nimport { cn } from \"../../lib/utils\";\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n  return (\n    <fieldset\n      className={cn(\n        'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',\n        className\n      )}\n      data-slot=\"field-set\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLegend({\n  className,\n  variant = 'legend',\n  ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n  return (\n    <legend\n      className={cn(\n        'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',\n        className\n      )}\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',\n        className\n      )}\n      data-slot=\"field-group\"\n      {...props}\n    />\n  );\n}\n\nconst fieldVariants = cva(\n  'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',\n  {\n    variants: {\n      orientation: {\n        vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',\n        horizontal:\n          'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        responsive:\n          '@md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n      },\n    },\n    defaultVariants: {\n      orientation: 'vertical',\n    },\n  }\n);\n\nfunction Field({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      className={cn(fieldVariants({ orientation }), className)}\n      data-orientation={orientation}\n      data-slot=\"field\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-content flex flex-1 flex-col gap-0.5 leading-snug',\n        className\n      )}\n      data-slot=\"field-content\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      className={cn(\n        'group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 *:data-[slot=field]:p-2.5 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',\n        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex w-fit items-center gap-2 font-medium text-sm group-data-[disabled=true]/field:opacity-50',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      className={cn(\n        '[[data-variant=legend]+&]:-mt-1.5 text-left font-normal text-muted-foreground text-sm leading-normal group-has-data-horizontal/field:text-balance',\n        'nth-last-2:-mt-1 last:mt-0',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      data-slot=\"field-description\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <div\n      className={cn(\n        '-my-2 group-data-[variant=outline]/field-group:-mb-2 relative h-5 text-sm',\n        className\n      )}\n      data-content={!!children}\n      data-slot=\"field-separator\"\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<'div'> & {\n  errors?: Array<{ message?: string } | undefined>;\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children;\n    }\n\n    if (!errors?.length) {\n      return null;\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ];\n\n    if (uniqueErrors?.length === 1) {\n      return uniqueErrors[0]?.message;\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    );\n  }, [children, errors]);\n\n  if (!content) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn('font-normal text-destructive text-sm', className)}\n      data-slot=\"field-error\"\n      role=\"alert\"\n      {...props}\n    >\n      {content}\n    </div>\n  );\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-01/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-01/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-01/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "form-layout",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/form-layout-01/form-layout-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/form-layout/form-layout-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "form",
          "settings",
          "fields",
          "preferences",
          "表单",
          "设置",
          "输入",
          "账号"
        ],
        "contentGuidelines": [
          "Keep labels concise and place validation messages beside the relevant field.",
          "Replace sample handlers with your own save and validation logic."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/form-layout-01/form-layout-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/form-layout-01"
      }
    },
    {
      "name": "form-layout-02",
      "type": "registry:block",
      "title": "Form with Side Labels",
      "description": "Form with Side Labels. Structured product forms with labeled fields, preferences, and account settings.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/form-layout/form-layout-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/form-layout-02.tsx",
          "content": "import { Button } from \"./shared/components/ui/button\";\nimport { Field, FieldDescription, FieldLabel } from \"./shared/components/ui/field\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { RadioGroup, RadioGroupItem } from \"./shared/components/ui/radio-group\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport { Separator } from \"./shared/components/ui/separator\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\n\nexport default function FormLayout02() {\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <form>\n        <div className=\"grid grid-cols-1 gap-10 md:grid-cols-3\">\n          <div>\n            <h2 className=\"text-balance font-semibold text-foreground dark:text-foreground\">\n              Personal information\n            </h2>\n            <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6 dark:text-muted-foreground\">\n              Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\n            </p>\n          </div>\n          <div className=\"sm:max-w-3xl md:col-span-2\">\n            <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-6\">\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"first-name\">First name</FieldLabel>\n                  <Input\n                    autoComplete=\"given-name\"\n                    id=\"first-name\"\n                    name=\"first-name\"\n                    placeholder=\"Emma\"\n                    type=\"text\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"last-name\">Last name</FieldLabel>\n                  <Input\n                    autoComplete=\"family-name\"\n                    id=\"last-name\"\n                    name=\"last-name\"\n                    placeholder=\"Crown\"\n                    type=\"text\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n                  <Input\n                    autoComplete=\"email\"\n                    id=\"email\"\n                    name=\"email\"\n                    placeholder=\"emma@company.com\"\n                    type=\"email\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"birthyear\">Birth year</FieldLabel>\n                  <Input\n                    id=\"birthyear\"\n                    name=\"year\"\n                    placeholder=\"1990\"\n                    type=\"number\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"role\">Role</FieldLabel>\n                  <Input\n                    disabled\n                    id=\"role\"\n                    name=\"role\"\n                    placeholder=\"Senior Manager\"\n                    type=\"text\"\n                  />\n                  <FieldDescription>\n                    Roles can only be changed by system admin.\n                  </FieldDescription>\n                </Field>\n              </div>\n            </div>\n          </div>\n        </div>\n        <Separator className=\"my-8\" />\n        <div className=\"grid grid-cols-1 gap-10 md:grid-cols-3\">\n          <div>\n            <h2 className=\"text-balance font-semibold text-foreground dark:text-foreground\">\n              Workspace settings\n            </h2>\n            <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6 dark:text-muted-foreground\">\n              Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\n            </p>\n          </div>\n          <div className=\"sm:max-w-3xl md:col-span-2\">\n            <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-6\">\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"workspace-name\">\n                    Workspace name\n                  </FieldLabel>\n                  <Input\n                    id=\"workspace-name\"\n                    name=\"workspace-name\"\n                    placeholder=\"Test workspace\"\n                    type=\"text\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"visibility\">Visibility</FieldLabel>\n                  <Select\n                    defaultValue=\"private\"\n                    items={{ private: 'Private', public: 'Public' }}\n                    name=\"visibility\"\n                  >\n                    <SelectTrigger id=\"visibility\">\n                      <SelectValue placeholder=\"Select visibility\" />\n                    </SelectTrigger>\n                    <SelectContent>\n                      <SelectItem value=\"public\">Public</SelectItem>\n                      <SelectItem value=\"private\">Private</SelectItem>\n                    </SelectContent>\n                  </Select>\n                </Field>\n              </div>\n              <div className=\"col-span-full\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"workspace-description\">\n                    Workspace description\n                  </FieldLabel>\n                  <Textarea\n                    id=\"workspace-description\"\n                    name=\"workspace-description\"\n                    rows={4}\n                  />\n                  <FieldDescription>\n                    Note: description provided will not be displayed externally.\n                  </FieldDescription>\n                </Field>\n              </div>\n            </div>\n          </div>\n        </div>\n        <Separator className=\"my-8\" />\n        <div className=\"grid grid-cols-1 gap-10 md:grid-cols-3\">\n          <div>\n            <h2 className=\"text-balance font-semibold text-foreground dark:text-foreground\">\n              Notification settings\n            </h2>\n            <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6 dark:text-muted-foreground\">\n              Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\n            </p>\n          </div>\n          <div className=\"sm:max-w-3xl md:col-span-2\">\n            <fieldset>\n              <legend className=\"font-medium text-foreground text-sm dark:text-foreground\">\n                Newsletter\n              </legend>\n              <FieldDescription\n                className=\"mt-2 leading-6\"\n                id=\"newsletter-description\"\n              >\n                Change how often you want to receive updates from our\n                newsletter.\n              </FieldDescription>\n              <RadioGroup className=\"mt-6\" defaultValue=\"never\">\n                <div className=\"flex items-center gap-x-3\">\n                  <RadioGroupItem\n                    aria-describedby=\"newsletter-description\"\n                    id=\"every-week\"\n                    value=\"every-week\"\n                  />\n                  <FieldLabel className=\"font-normal\" htmlFor=\"every-week\">\n                    Every week\n                  </FieldLabel>\n                </div>\n                <div className=\"flex items-center gap-x-3\">\n                  <RadioGroupItem\n                    aria-describedby=\"newsletter-description\"\n                    id=\"every-month\"\n                    value=\"every-month\"\n                  />\n                  <FieldLabel className=\"font-normal\" htmlFor=\"every-month\">\n                    Every month\n                  </FieldLabel>\n                </div>\n                <div className=\"flex items-center gap-x-3\">\n                  <RadioGroupItem\n                    aria-describedby=\"newsletter-description\"\n                    id=\"never\"\n                    value=\"never\"\n                  />\n                  <FieldLabel className=\"font-normal\" htmlFor=\"never\">\n                    Never\n                  </FieldLabel>\n                </div>\n              </RadioGroup>\n            </fieldset>\n          </div>\n        </div>\n        <Separator className=\"my-8\" />\n        <div className=\"flex items-center justify-end space-x-4\">\n          <Button className=\"whitespace-nowrap\" type=\"button\" variant=\"outline\">\n            Go back\n          </Button>\n          <Button className=\"whitespace-nowrap\" type=\"submit\">\n            Save settings\n          </Button>\n        </div>\n      </form>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/form-layout-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/form-layout-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/field.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/components/ui/field.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useMemo } from 'react';\nimport { Label } from \"./label\";\nimport { Separator } from \"./separator\";\nimport { cn } from \"../../lib/utils\";\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n  return (\n    <fieldset\n      className={cn(\n        'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',\n        className\n      )}\n      data-slot=\"field-set\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLegend({\n  className,\n  variant = 'legend',\n  ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n  return (\n    <legend\n      className={cn(\n        'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',\n        className\n      )}\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',\n        className\n      )}\n      data-slot=\"field-group\"\n      {...props}\n    />\n  );\n}\n\nconst fieldVariants = cva(\n  'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',\n  {\n    variants: {\n      orientation: {\n        vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',\n        horizontal:\n          'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        responsive:\n          '@md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n      },\n    },\n    defaultVariants: {\n      orientation: 'vertical',\n    },\n  }\n);\n\nfunction Field({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      className={cn(fieldVariants({ orientation }), className)}\n      data-orientation={orientation}\n      data-slot=\"field\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-content flex flex-1 flex-col gap-0.5 leading-snug',\n        className\n      )}\n      data-slot=\"field-content\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      className={cn(\n        'group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 *:data-[slot=field]:p-2.5 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',\n        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex w-fit items-center gap-2 font-medium text-sm group-data-[disabled=true]/field:opacity-50',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      className={cn(\n        '[[data-variant=legend]+&]:-mt-1.5 text-left font-normal text-muted-foreground text-sm leading-normal group-has-data-horizontal/field:text-balance',\n        'nth-last-2:-mt-1 last:mt-0',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      data-slot=\"field-description\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <div\n      className={cn(\n        '-my-2 group-data-[variant=outline]/field-group:-mb-2 relative h-5 text-sm',\n        className\n      )}\n      data-content={!!children}\n      data-slot=\"field-separator\"\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<'div'> & {\n  errors?: Array<{ message?: string } | undefined>;\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children;\n    }\n\n    if (!errors?.length) {\n      return null;\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ];\n\n    if (uniqueErrors?.length === 1) {\n      return uniqueErrors[0]?.message;\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    );\n  }, [children, errors]);\n\n  if (!content) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn('font-normal text-destructive text-sm', className)}\n      data-slot=\"field-error\"\n      role=\"alert\"\n      {...props}\n    >\n      {content}\n    </div>\n  );\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/radio-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/components/ui/radio-group.tsx",
          "content": "'use client';\n\nimport { Radio as RadioPrimitive } from '@base-ui/react/radio';\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {\n  return (\n    <RadioGroupPrimitive\n      className={cn('grid w-full gap-2', className)}\n      data-slot=\"radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {\n  return (\n    <RadioPrimitive.Root\n      className={cn(\n        'group/radio-group-item peer after:-inset-x-3 after:-inset-y-2 relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"radio-group-item\"\n      {...props}\n    >\n      <RadioPrimitive.Indicator\n        className=\"flex size-4 items-center justify-center\"\n        data-slot=\"radio-group-indicator\"\n      >\n        <span className=\"-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2 size-2 rounded-full bg-primary-foreground\" />\n      </RadioPrimitive.Indicator>\n    </RadioPrimitive.Root>\n  );\n}\n\nexport { RadioGroup, RadioGroupItem };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "form-layout",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/form-layout-02/form-layout-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/form-layout/form-layout-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "form",
          "settings",
          "fields",
          "preferences",
          "表单",
          "设置",
          "输入",
          "账号"
        ],
        "contentGuidelines": [
          "Keep labels concise and place validation messages beside the relevant field.",
          "Replace sample handlers with your own save and validation logic."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/form-layout-02/form-layout-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/form-layout-02"
      }
    },
    {
      "name": "form-layout-03",
      "type": "registry:block",
      "title": "Form with Checkbox Settings",
      "description": "Form with Checkbox Settings. Structured product forms with labeled fields, preferences, and account settings.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/form-layout/form-layout-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/form-layout-03.tsx",
          "content": "import { Button } from \"./shared/components/ui/button\";\nimport { Checkbox } from \"./shared/components/ui/checkbox\";\nimport { Field, FieldDescription, FieldLabel } from \"./shared/components/ui/field\";\nimport { Input } from \"./shared/components/ui/input\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport { Separator } from \"./shared/components/ui/separator\";\nimport { Textarea } from \"./shared/components/ui/textarea\";\n\nexport default function FormLayout03() {\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <form>\n        <div className=\"grid grid-cols-1 gap-10 md:grid-cols-3\">\n          <div>\n            <h2 className=\"text-balance font-semibold text-foreground dark:text-foreground\">\n              Personal information\n            </h2>\n            <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6 dark:text-muted-foreground\">\n              Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\n            </p>\n          </div>\n          <div className=\"sm:max-w-3xl md:col-span-2\">\n            <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-6\">\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"first-name\">First name</FieldLabel>\n                  <Input\n                    autoComplete=\"given-name\"\n                    id=\"first-name\"\n                    name=\"first-name\"\n                    placeholder=\"Emma\"\n                    type=\"text\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"last-name\">Last name</FieldLabel>\n                  <Input\n                    autoComplete=\"family-name\"\n                    id=\"last-name\"\n                    name=\"last-name\"\n                    placeholder=\"Crown\"\n                    type=\"text\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n                  <Input\n                    autoComplete=\"email\"\n                    id=\"email\"\n                    name=\"email\"\n                    placeholder=\"emma@company.com\"\n                    type=\"email\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"birthyear\">Birth year</FieldLabel>\n                  <Input\n                    id=\"birthyear\"\n                    name=\"year\"\n                    placeholder=\"1990\"\n                    type=\"number\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"role\">Role</FieldLabel>\n                  <Input\n                    disabled\n                    id=\"role\"\n                    name=\"role\"\n                    placeholder=\"Senior Manager\"\n                    type=\"text\"\n                  />\n                  <FieldDescription>\n                    Roles can only be changed by system admin.\n                  </FieldDescription>\n                </Field>\n              </div>\n            </div>\n          </div>\n        </div>\n        <Separator className=\"my-8\" />\n        <div className=\"grid grid-cols-1 gap-10 md:grid-cols-3\">\n          <div>\n            <h2 className=\"text-balance font-semibold text-foreground dark:text-foreground\">\n              Workspace settings\n            </h2>\n            <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6 dark:text-muted-foreground\">\n              Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\n            </p>\n          </div>\n          <div className=\"sm:max-w-3xl md:col-span-2\">\n            <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-6\">\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"workspace-name\">\n                    Workspace name\n                  </FieldLabel>\n                  <Input\n                    id=\"workspace-name\"\n                    name=\"workspace-name\"\n                    placeholder=\"Test workspace\"\n                    type=\"text\"\n                  />\n                </Field>\n              </div>\n              <div className=\"col-span-full sm:col-span-3\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"visibility\">Visibility</FieldLabel>\n                  <Select\n                    defaultValue=\"private\"\n                    items={{ private: 'Private', public: 'Public' }}\n                    name=\"visibility\"\n                  >\n                    <SelectTrigger id=\"visibility\">\n                      <SelectValue placeholder=\"Select visibility\" />\n                    </SelectTrigger>\n                    <SelectContent>\n                      <SelectItem value=\"public\">Public</SelectItem>\n                      <SelectItem value=\"private\">Private</SelectItem>\n                    </SelectContent>\n                  </Select>\n                </Field>\n              </div>\n              <div className=\"col-span-full\">\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"workspace-description\">\n                    Workspace description\n                  </FieldLabel>\n                  <Textarea\n                    id=\"workspace-description\"\n                    name=\"workspace-description\"\n                    rows={4}\n                  />\n                  <FieldDescription>\n                    Note: description provided will not be displayed externally.\n                  </FieldDescription>\n                </Field>\n              </div>\n            </div>\n          </div>\n        </div>\n        <Separator className=\"my-8\" />\n        <div className=\"grid grid-cols-1 gap-10 md:grid-cols-3\">\n          <div>\n            <h2 className=\"text-balance font-semibold text-foreground dark:text-foreground\">\n              Notification settings\n            </h2>\n            <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6 dark:text-muted-foreground\">\n              Lorem ipsum dolor sit amet, consetetur sadipscing elitr.\n            </p>\n          </div>\n          <div className=\"sm:max-w-3xl md:col-span-2\">\n            <fieldset>\n              <legend className=\"font-medium text-foreground text-sm dark:text-foreground\">\n                Team\n              </legend>\n              <FieldDescription className=\"mt-1 leading-6\">\n                Configure the types of team alerts you want to receive.\n              </FieldDescription>\n              <div className=\"mt-2\">\n                <div className=\"flex items-center gap-x-3 py-1\">\n                  <Checkbox\n                    defaultChecked\n                    id=\"team-requests\"\n                    name=\"team-requests\"\n                  />\n                  <FieldLabel className=\"font-normal\" htmlFor=\"team-requests\">\n                    Team join requests\n                  </FieldLabel>\n                </div>\n                <div className=\"flex items-center gap-x-3 py-1\">\n                  <Checkbox\n                    id=\"team-activity-digest\"\n                    name=\"team-activity-digest\"\n                  />\n                  <FieldLabel\n                    className=\"font-normal\"\n                    htmlFor=\"team-activity-digest\"\n                  >\n                    Weekly team activity digest\n                  </FieldLabel>\n                </div>\n              </div>\n            </fieldset>\n            <fieldset className=\"mt-6\">\n              <legend className=\"font-medium text-foreground text-sm dark:text-foreground\">\n                Usage\n              </legend>\n              <FieldDescription className=\"mt-1 leading-6\">\n                Configure the types of usage alerts you want to receive.\n              </FieldDescription>\n              <div className=\"mt-2\">\n                <div className=\"flex items-center gap-x-3 py-1\">\n                  <Checkbox id=\"api-requests\" name=\"api-requests\" />\n                  <FieldLabel className=\"font-normal\" htmlFor=\"api-requests\">\n                    API requests\n                  </FieldLabel>\n                </div>\n                <div className=\"flex items-center gap-x-3 py-1\">\n                  <Checkbox\n                    id=\"workspace-execution\"\n                    name=\"workspace-execution\"\n                  />\n                  <FieldLabel\n                    className=\"font-normal\"\n                    htmlFor=\"workspace-execution\"\n                  >\n                    Workspace loading times\n                  </FieldLabel>\n                </div>\n                <div className=\"flex items-center gap-x-3 py-1\">\n                  <Checkbox\n                    defaultChecked\n                    id=\"query-caching\"\n                    name=\"query-caching\"\n                  />\n                  <FieldLabel className=\"font-normal\" htmlFor=\"query-caching\">\n                    Query caching\n                  </FieldLabel>\n                </div>\n                <div className=\"flex items-center gap-x-3 py-1\">\n                  <Checkbox defaultChecked id=\"storage\" name=\"storage\" />\n                  <FieldLabel className=\"font-normal\" htmlFor=\"storage\">\n                    Storage\n                  </FieldLabel>\n                </div>\n              </div>\n            </fieldset>\n          </div>\n        </div>\n        <Separator className=\"my-8\" />\n        <div className=\"flex items-center justify-end space-x-4\">\n          <Button className=\"whitespace-nowrap\" type=\"button\" variant=\"outline\">\n            Go back\n          </Button>\n          <Button className=\"whitespace-nowrap\" type=\"submit\">\n            Save settings\n          </Button>\n        </div>\n      </form>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/form-layout-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/form-layout-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/checkbox.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/components/ui/checkbox.tsx",
          "content": "'use client';\n\nimport { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { CheckIcon } from 'lucide-react';\nimport { cn } from \"../../lib/utils\";\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      className={cn(\n        'peer after:-inset-x-3 after:-inset-y-2 relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input outline-none transition-colors after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"checkbox\"\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n        data-slot=\"checkbox-indicator\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n\nexport { Checkbox };\n"
        },
        {
          "path": "components/ui/field.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/components/ui/field.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useMemo } from 'react';\nimport { Label } from \"./label\";\nimport { Separator } from \"./separator\";\nimport { cn } from \"../../lib/utils\";\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n  return (\n    <fieldset\n      className={cn(\n        'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',\n        className\n      )}\n      data-slot=\"field-set\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLegend({\n  className,\n  variant = 'legend',\n  ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n  return (\n    <legend\n      className={cn(\n        'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',\n        className\n      )}\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',\n        className\n      )}\n      data-slot=\"field-group\"\n      {...props}\n    />\n  );\n}\n\nconst fieldVariants = cva(\n  'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',\n  {\n    variants: {\n      orientation: {\n        vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',\n        horizontal:\n          'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        responsive:\n          '@md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n      },\n    },\n    defaultVariants: {\n      orientation: 'vertical',\n    },\n  }\n);\n\nfunction Field({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      className={cn(fieldVariants({ orientation }), className)}\n      data-orientation={orientation}\n      data-slot=\"field\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-content flex flex-1 flex-col gap-0.5 leading-snug',\n        className\n      )}\n      data-slot=\"field-content\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      className={cn(\n        'group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 *:data-[slot=field]:p-2.5 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',\n        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex w-fit items-center gap-2 font-medium text-sm group-data-[disabled=true]/field:opacity-50',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      className={cn(\n        '[[data-variant=legend]+&]:-mt-1.5 text-left font-normal text-muted-foreground text-sm leading-normal group-has-data-horizontal/field:text-balance',\n        'nth-last-2:-mt-1 last:mt-0',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      data-slot=\"field-description\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <div\n      className={cn(\n        '-my-2 group-data-[variant=outline]/field-group:-mb-2 relative h-5 text-sm',\n        className\n      )}\n      data-content={!!children}\n      data-slot=\"field-separator\"\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<'div'> & {\n  errors?: Array<{ message?: string } | undefined>;\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children;\n    }\n\n    if (!errors?.length) {\n      return null;\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ];\n\n    if (uniqueErrors?.length === 1) {\n      return uniqueErrors[0]?.message;\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    );\n  }, [children, errors]);\n\n  if (!content) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn('font-normal text-destructive text-sm', className)}\n      data-slot=\"field-error\"\n      role=\"alert\"\n      {...props}\n    >\n      {content}\n    </div>\n  );\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "form-layout",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/form-layout-03/form-layout-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/form-layout/form-layout-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "form",
          "settings",
          "fields",
          "preferences",
          "表单",
          "设置",
          "输入",
          "账号"
        ],
        "contentGuidelines": [
          "Keep labels concise and place validation messages beside the relevant field.",
          "Replace sample handlers with your own save and validation logic."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/form-layout-03/form-layout-03.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/form-layout-03"
      }
    },
    {
      "name": "form-layout-04",
      "type": "registry:block",
      "title": "Form with Package Selection",
      "description": "Form with Package Selection. Structured product forms with labeled fields, preferences, and account settings.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/form-layout/form-layout-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/form-layout-04.tsx",
          "content": "'use client';\n\nimport { useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Field, FieldDescription, FieldLabel } from \"./shared/components/ui/field\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { RadioGroup, RadioGroupItem } from \"./shared/components/ui/radio-group\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport { Separator } from \"./shared/components/ui/separator\";\n\nconst workspaces = [\n  {\n    id: 1,\n    title: 'Starter',\n    description: 'Up to 10,000 requests per day.',\n    users: 'Free',\n  },\n  {\n    id: 2,\n    title: 'Premium',\n    description: '500,000 requests per day¹',\n    users: '$900/month²',\n  },\n  {\n    id: 3,\n    title: 'Enterprise',\n    description: 'Based on your specific needs',\n    users: 'Custom',\n  },\n];\n\nexport default function FormLayout04() {\n  const [selectedWorkspace, setSelectedWorkspace] = useState(workspaces[0]);\n\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <div className=\"sm:mx-auto sm:max-w-2xl\">\n        <h3 className=\"text-balance font-semibold text-foreground text-lg\">\n          Apply for early access\n        </h3>\n        <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6\">\n          Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam\n          nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.\n        </p>\n        <form action=\"#\" className=\"mt-8\" method=\"post\">\n          <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-6\">\n            <div className=\"col-span-full sm:col-span-3\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"first-name\">\n                  First name<span className=\"text-red-500\">*</span>\n                </FieldLabel>\n                <Input\n                  autoComplete=\"given-name\"\n                  id=\"first-name\"\n                  name=\"first-name\"\n                  placeholder=\"Emma\"\n                  required\n                  type=\"text\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full sm:col-span-3\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"last-name\">Last name</FieldLabel>\n                <Input\n                  autoComplete=\"family-name\"\n                  id=\"last-name\"\n                  name=\"last-name\"\n                  placeholder=\"Crown\"\n                  type=\"text\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"email\">\n                  Work email<span className=\"text-red-500\">*</span>\n                </FieldLabel>\n                <Input\n                  autoComplete=\"email\"\n                  id=\"email\"\n                  name=\"email\"\n                  placeholder=\"emma@company.com\"\n                  required\n                  type=\"email\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full sm:col-span-3\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"company\">Company</FieldLabel>\n                <Input\n                  autoComplete=\"organization\"\n                  id=\"company\"\n                  name=\"company\"\n                  placeholder=\"Company, Inc.\"\n                  type=\"text\"\n                />\n              </Field>\n            </div>\n            <div className=\"col-span-full sm:col-span-3\">\n              <Field className=\"gap-2\">\n                <FieldLabel htmlFor=\"size\">Company size (employees)</FieldLabel>\n                <Select defaultValue=\"\">\n                  <SelectTrigger id=\"size\" name=\"size\">\n                    <SelectValue placeholder=\"Select company size\" />\n                  </SelectTrigger>\n                  <SelectContent>\n                    <SelectItem value=\"1-9\">1-9</SelectItem>\n                    <SelectItem value=\"10-50\">10-50</SelectItem>\n                    <SelectItem value=\"50-250\">50-250</SelectItem>\n                    <SelectItem value=\"250+\">250+</SelectItem>\n                  </SelectContent>\n                </Select>\n              </Field>\n            </div>\n            <Separator className=\"col-span-full my-4\" />\n            <div className=\"col-span-full\">\n              <FieldLabel className=\"mb-4 block font-semibold text-foreground\">\n                Select a workspace package\n              </FieldLabel>\n\n              <RadioGroup\n                className=\"grid grid-cols-1 gap-5 sm:grid-cols-3\"\n                defaultValue={selectedWorkspace.id.toString()}\n                onValueChange={(value) =>\n                  setSelectedWorkspace(\n                    workspaces.find(\n                      (workspace) => workspace.id.toString() === value\n                    ) || workspaces[0]\n                  )\n                }\n              >\n                {workspaces.map((item) => (\n                  <div\n                    className=\"relative flex flex-col gap-2 rounded-md border border-input p-4 shadow-xs outline-none has-data-checked:border-ring\"\n                    key={item.id.toString()}\n                  >\n                    <div className=\"flex justify-between\">\n                      <RadioGroupItem\n                        className=\"order-1 after:absolute after:inset-0\"\n                        id={item.id.toString()}\n                        value={item.id.toString()}\n                      />\n\n                      <FieldLabel\n                        className=\"block font-medium text-foreground text-sm\"\n                        htmlFor={item.id.toString()}\n                      >\n                        {item.title}\n                      </FieldLabel>\n                    </div>\n                    <div className=\"flex h-full flex-col justify-between\">\n                      <p className=\"mt-1 text-pretty text-muted-foreground text-sm\">\n                        {item.description}\n                      </p>\n                      <span className=\"mt-4 block font-medium text-foreground text-sm\">\n                        {item.users}\n                      </span>\n                    </div>\n                  </div>\n                ))}\n              </RadioGroup>\n\n              <FieldDescription className=\"mt-2!\">\n                <sup>1</sup> $0.5/10K requests after limit reach.\n              </FieldDescription>\n              <FieldDescription className=\"mt-1\">\n                <sup>2</sup> No credit card required for registration.\n              </FieldDescription>\n            </div>\n          </div>\n          <Separator className=\"my-6\" />\n          <div className=\"flex items-center justify-end space-x-4\">\n            <Button\n              className=\"whitespace-nowrap\"\n              type=\"button\"\n              variant=\"outline\"\n            >\n              Go back\n            </Button>\n            <Button className=\"whitespace-nowrap\" type=\"submit\">\n              Apply\n            </Button>\n          </div>\n        </form>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/form-layout-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/form-layout-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/field.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/shared/components/ui/field.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useMemo } from 'react';\nimport { Label } from \"./label\";\nimport { Separator } from \"./separator\";\nimport { cn } from \"../../lib/utils\";\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n  return (\n    <fieldset\n      className={cn(\n        'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',\n        className\n      )}\n      data-slot=\"field-set\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLegend({\n  className,\n  variant = 'legend',\n  ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n  return (\n    <legend\n      className={cn(\n        'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',\n        className\n      )}\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',\n        className\n      )}\n      data-slot=\"field-group\"\n      {...props}\n    />\n  );\n}\n\nconst fieldVariants = cva(\n  'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',\n  {\n    variants: {\n      orientation: {\n        vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',\n        horizontal:\n          'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        responsive:\n          '@md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n      },\n    },\n    defaultVariants: {\n      orientation: 'vertical',\n    },\n  }\n);\n\nfunction Field({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      className={cn(fieldVariants({ orientation }), className)}\n      data-orientation={orientation}\n      data-slot=\"field\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-content flex flex-1 flex-col gap-0.5 leading-snug',\n        className\n      )}\n      data-slot=\"field-content\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      className={cn(\n        'group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 *:data-[slot=field]:p-2.5 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',\n        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex w-fit items-center gap-2 font-medium text-sm group-data-[disabled=true]/field:opacity-50',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      className={cn(\n        '[[data-variant=legend]+&]:-mt-1.5 text-left font-normal text-muted-foreground text-sm leading-normal group-has-data-horizontal/field:text-balance',\n        'nth-last-2:-mt-1 last:mt-0',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      data-slot=\"field-description\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <div\n      className={cn(\n        '-my-2 group-data-[variant=outline]/field-group:-mb-2 relative h-5 text-sm',\n        className\n      )}\n      data-content={!!children}\n      data-slot=\"field-separator\"\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<'div'> & {\n  errors?: Array<{ message?: string } | undefined>;\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children;\n    }\n\n    if (!errors?.length) {\n      return null;\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ];\n\n    if (uniqueErrors?.length === 1) {\n      return uniqueErrors[0]?.message;\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    );\n  }, [children, errors]);\n\n  if (!content) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn('font-normal text-destructive text-sm', className)}\n      data-slot=\"field-error\"\n      role=\"alert\"\n      {...props}\n    >\n      {content}\n    </div>\n  );\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/radio-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/shared/components/ui/radio-group.tsx",
          "content": "'use client';\n\nimport { Radio as RadioPrimitive } from '@base-ui/react/radio';\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {\n  return (\n    <RadioGroupPrimitive\n      className={cn('grid w-full gap-2', className)}\n      data-slot=\"radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {\n  return (\n    <RadioPrimitive.Root\n      className={cn(\n        'group/radio-group-item peer after:-inset-x-3 after:-inset-y-2 relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"radio-group-item\"\n      {...props}\n    >\n      <RadioPrimitive.Indicator\n        className=\"flex size-4 items-center justify-center\"\n        data-slot=\"radio-group-indicator\"\n      >\n        <span className=\"-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2 size-2 rounded-full bg-primary-foreground\" />\n      </RadioPrimitive.Indicator>\n    </RadioPrimitive.Root>\n  );\n}\n\nexport { RadioGroup, RadioGroupItem };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "form-layout",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/form-layout-04/form-layout-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/form-layout/form-layout-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "form",
          "settings",
          "fields",
          "preferences",
          "表单",
          "设置",
          "输入",
          "账号"
        ],
        "contentGuidelines": [
          "Keep labels concise and place validation messages beside the relevant field.",
          "Replace sample handlers with your own save and validation logic."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/form-layout-04/form-layout-04.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/form-layout-04"
      }
    },
    {
      "name": "form-layout-05",
      "type": "registry:block",
      "title": "Form with Plan Selection",
      "description": "Form with Plan Selection. Structured product forms with labeled fields, preferences, and account settings.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/form-layout/form-layout-05.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/form-layout-05.tsx",
          "content": "'use client';\n\nimport { Check, CircleCheck, ExternalLink } from 'lucide-react';\nimport { useState } from 'react';\nimport { Badge } from \"./shared/components/ui/badge\";\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { Field, FieldDescription, FieldLabel } from \"./shared/components/ui/field\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { RadioGroup, RadioGroupItem } from \"./shared/components/ui/radio-group\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport { Separator } from \"./shared/components/ui/separator\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst highlights = [\n  {\n    id: 1,\n    feature: 'Used by top design teams worldwide',\n  },\n  {\n    id: 2,\n    feature: 'Seamless integration with design tools',\n  },\n  {\n    id: 3,\n    feature: 'Real-time collaboration features',\n  },\n];\n\nconst plans = [\n  {\n    name: 'Creator',\n    features: [\n      { feature: 'Up to 3 design projects' },\n      { feature: 'Basic collaboration tools' },\n      { feature: '5GB cloud storage' },\n      { feature: 'Community forum support' },\n    ],\n    price: '$15',\n    href: '#',\n    isRecommended: false,\n  },\n  {\n    name: 'Team',\n    features: [\n      { feature: 'Unlimited design projects' },\n      { feature: 'Advanced collaboration suite' },\n      { feature: '50GB cloud storage' },\n      { feature: 'Priority email support' },\n    ],\n    price: '$49',\n    href: '#',\n    isRecommended: true,\n  },\n  {\n    name: 'Agency',\n    features: [\n      { feature: 'Unlimited projects and team members' },\n      { feature: 'Client portal access' },\n      { feature: '250GB cloud storage' },\n      { feature: 'White-labeling options' },\n      { feature: 'Dedicated account manager' },\n    ],\n    price: '$99',\n    href: '#',\n    isRecommended: false,\n  },\n];\n\nexport default function FormLayout05() {\n  const [selected, setSelected] = useState(plans[0]);\n\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <form className=\"sm:mx-auto sm:max-w-7xl\">\n        <h3 className=\"text-balance font-semibold text-foreground text-xl\">\n          Create new design workspace\n        </h3>\n        <div className=\"grid grid-cols-1 gap-10 lg:grid-cols-12 lg:gap-12\">\n          <div className=\"mt-6 lg:col-span-7\">\n            <div className=\"space-y-4 md:space-y-6\">\n              <div className=\"md:flex md:items-center md:space-x-4\">\n                <div className=\"md:w-1/4\">\n                  <Field className=\"gap-2\">\n                    <FieldLabel htmlFor=\"organization\">Organization</FieldLabel>\n                    <Select\n                      defaultValue=\"1\"\n                      items={{\n                        '1': 'Acme, Inc.',\n                        '2': 'Hero Labs',\n                        '3': 'Rose Holding',\n                      }}\n                    >\n                      <SelectTrigger\n                        className=\"w-full\"\n                        id=\"organization\"\n                        name=\"organization\"\n                      >\n                        <SelectValue placeholder=\"Select organization\" />\n                      </SelectTrigger>\n                      <SelectContent>\n                        <SelectItem value=\"1\">Acme, Inc.</SelectItem>\n                        <SelectItem value=\"2\">Hero Labs</SelectItem>\n                        <SelectItem value=\"3\">Rose Holding</SelectItem>\n                      </SelectContent>\n                    </Select>\n                  </Field>\n                </div>\n                <div className=\"mt-4 md:mt-0 md:w-3/4\">\n                  <Field className=\"gap-2\">\n                    <FieldLabel htmlFor=\"workspace\">Workspace name</FieldLabel>\n                    <Input id=\"workspace\" name=\"workspace\" />\n                  </Field>\n                </div>\n              </div>\n              <div>\n                <Field className=\"gap-2\">\n                  <FieldLabel htmlFor=\"region\">Region</FieldLabel>\n                  <Select\n                    defaultValue=\"iad1\"\n                    items={{\n                      fra1: 'eu-central-1 (Frankfurt, Germany)',\n                      iad1: 'us-east-1 (Washington, D.C., USA)',\n                      lhr1: 'eu-west-2 (London, United Kingdom)',\n                      sfo1: 'us-west-1 (San Francisco, USA)',\n                      sin1: 'ap-southeast-1 (Singapore)',\n                    }}\n                  >\n                    <SelectTrigger id=\"region\" name=\"region\">\n                      <SelectValue placeholder=\"Select region\" />\n                    </SelectTrigger>\n                    <SelectContent>\n                      <SelectItem value=\"fra1\">\n                        eu-central-1 (Frankfurt, Germany)\n                      </SelectItem>\n                      <SelectItem value=\"iad1\">\n                        us-east-1 (Washington, D.C., USA)\n                      </SelectItem>\n                      <SelectItem value=\"lhr1\">\n                        eu-west-2 (London, United Kingdom)\n                      </SelectItem>\n                      <SelectItem value=\"sfo1\">\n                        us-west-1 (San Francisco, USA)\n                      </SelectItem>\n                      <SelectItem value=\"sin1\">\n                        ap-southeast-1 (Singapore)\n                      </SelectItem>\n                    </SelectContent>\n                  </Select>\n                  <FieldDescription>\n                    For best performance, choose a region closest to your\n                    operations\n                  </FieldDescription>\n                </Field>\n              </div>\n            </div>\n            <h4 className=\"mt-14 text-balance font-medium\">\n              Plan type<span className=\"text-red-500\">*</span>\n            </h4>\n            <RadioGroup\n              className=\"mt-4 space-y-4\"\n              onValueChange={(value) =>\n                setSelected(\n                  plans.find((plan) => plan.name === value) || plans[0]\n                )\n              }\n              value={selected.name}\n            >\n              {plans.map((plan) => (\n                <label\n                  className={cn(\n                    'relative block cursor-pointer rounded-md border bg-background transition',\n                    selected.name === plan.name\n                      ? 'border-primary/20 ring-2 ring-primary/20'\n                      : 'border-border'\n                  )}\n                  htmlFor={plan.name}\n                  key={plan.name}\n                >\n                  <div className=\"flex items-start space-x-4 px-6 py-4\">\n                    <div className=\"mt-1 flex h-4 w-4 shrink-0 items-center justify-center\">\n                      <RadioGroupItem id={plan.name} value={plan.name} />\n                    </div>\n                    <div className=\"w-full\">\n                      <p className=\"text-pretty leading-6\">\n                        <span className=\"font-semibold text-foreground\">\n                          {plan.name}\n                        </span>\n                        {plan.isRecommended && (\n                          <Badge className=\"ml-2\" variant=\"secondary\">\n                            recommended\n                          </Badge>\n                        )}\n                      </p>\n                      <ul className=\"mt-2 space-y-1\">\n                        {plan.features.map((feature, index) => (\n                          <li\n                            className=\"flex items-center gap-2 text-sm\"\n                            key={index}\n                          >\n                            <Check\n                              aria-hidden={true}\n                              className=\"h-4 w-4 text-muted-foreground\"\n                            />\n                            {feature.feature}\n                          </li>\n                        ))}\n                      </ul>\n                    </div>\n                  </div>\n                  <div className=\"flex items-center justify-between rounded-b-md border-border border-t bg-muted px-6 py-3\">\n                    <a\n                      className=\"inline-flex items-center gap-1 text-primary text-sm hover:underline hover:underline-offset-4\"\n                      href={plan.href}\n                    >\n                      Learn more\n                      <ExternalLink aria-hidden={true} className=\"h-4 w-4\" />\n                    </a>\n                    <div>\n                      <span className=\"font-semibold text-foreground text-lg\">\n                        {plan.price}\n                      </span>\n                      <span className=\"text-muted-foreground text-sm\">/mo</span>\n                    </div>\n                  </div>\n                </label>\n              ))}\n            </RadioGroup>\n          </div>\n          <div className=\"lg:col-span-5\">\n            <Card className=\"bg-muted shadow-none\">\n              <CardContent>\n                <h4 className=\"text-balance font-semibold text-foreground text-sm\">\n                  Choose the right plan for your design team\n                </h4>\n                <p className=\"mt-2 text-pretty text-muted-foreground text-sm leading-6\">\n                  Our flexible plans are designed to scale with your team&apos;s\n                  needs. All plans include core design collaboration features\n                  with varying levels of storage and support.\n                </p>\n                <ul className=\"mt-4 space-y-1\">\n                  {highlights.map((item) => (\n                    <li\n                      className=\"flex items-center space-x-2 py-1.5 text-foreground\"\n                      key={item.id}\n                    >\n                      <CircleCheck className=\"h-5 w-5 text-primary\" />\n                      <span className=\"truncate text-sm\">{item.feature}</span>\n                    </li>\n                  ))}\n                </ul>\n                <a\n                  className=\"mt-4 inline-flex items-center gap-1 text-primary text-sm\"\n                  href=\"#\"\n                >\n                  Learn more\n                  <ExternalLink aria-hidden={true} className=\"h-4 w-4\" />\n                </a>\n              </CardContent>\n            </Card>\n          </div>\n        </div>\n        <Separator className=\"my-10\" />\n        <div className=\"flex items-center justify-end space-x-4\">\n          <Button type=\"button\" variant=\"ghost\">\n            Cancel\n          </Button>\n          <Button type=\"submit\">Update</Button>\n        </div>\n      </form>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/form-layout-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/form-layout-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/field.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/field.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useMemo } from 'react';\nimport { Label } from \"./label\";\nimport { Separator } from \"./separator\";\nimport { cn } from \"../../lib/utils\";\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {\n  return (\n    <fieldset\n      className={cn(\n        'flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',\n        className\n      )}\n      data-slot=\"field-set\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLegend({\n  className,\n  variant = 'legend',\n  ...props\n}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {\n  return (\n    <legend\n      className={cn(\n        'mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base',\n        className\n      )}\n      data-slot=\"field-legend\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',\n        className\n      )}\n      data-slot=\"field-group\"\n      {...props}\n    />\n  );\n}\n\nconst fieldVariants = cva(\n  'group/field flex w-full gap-2 data-[invalid=true]:text-destructive',\n  {\n    variants: {\n      orientation: {\n        vertical: 'flex-col *:w-full [&>.sr-only]:w-auto',\n        horizontal:\n          'flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n        responsive:\n          '@md/field-group:flex-row flex-col @md/field-group:items-center *:w-full @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',\n      },\n    },\n    defaultVariants: {\n      orientation: 'vertical',\n    },\n  }\n);\n\nfunction Field({\n  className,\n  orientation = 'vertical',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {\n  return (\n    <div\n      className={cn(fieldVariants({ orientation }), className)}\n      data-orientation={orientation}\n      data-slot=\"field\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/field-content flex flex-1 flex-col gap-0.5 leading-snug',\n        className\n      )}\n      data-slot=\"field-content\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof Label>) {\n  return (\n    <Label\n      className={cn(\n        'group/field-label peer/field-label flex w-fit gap-2 leading-snug has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 *:data-[slot=field]:p-2.5 group-data-[disabled=true]/field:opacity-50 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10',\n        'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex w-fit items-center gap-2 font-medium text-sm group-data-[disabled=true]/field:opacity-50',\n        className\n      )}\n      data-slot=\"field-label\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {\n  return (\n    <p\n      className={cn(\n        '[[data-variant=legend]+&]:-mt-1.5 text-left font-normal text-muted-foreground text-sm leading-normal group-has-data-horizontal/field:text-balance',\n        'nth-last-2:-mt-1 last:mt-0',\n        '[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',\n        className\n      )}\n      data-slot=\"field-description\"\n      {...props}\n    />\n  );\n}\n\nfunction FieldSeparator({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<'div'> & {\n  children?: React.ReactNode;\n}) {\n  return (\n    <div\n      className={cn(\n        '-my-2 group-data-[variant=outline]/field-group:-mb-2 relative h-5 text-sm',\n        className\n      )}\n      data-content={!!children}\n      data-slot=\"field-separator\"\n      {...props}\n    >\n      <Separator className=\"absolute inset-0 top-1/2\" />\n      {children && (\n        <span\n          className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n          data-slot=\"field-separator-content\"\n        >\n          {children}\n        </span>\n      )}\n    </div>\n  );\n}\n\nfunction FieldError({\n  className,\n  children,\n  errors,\n  ...props\n}: React.ComponentProps<'div'> & {\n  errors?: Array<{ message?: string } | undefined>;\n}) {\n  const content = useMemo(() => {\n    if (children) {\n      return children;\n    }\n\n    if (!errors?.length) {\n      return null;\n    }\n\n    const uniqueErrors = [\n      ...new Map(errors.map((error) => [error?.message, error])).values(),\n    ];\n\n    if (uniqueErrors?.length === 1) {\n      return uniqueErrors[0]?.message;\n    }\n\n    return (\n      <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n        {uniqueErrors.map(\n          (error, index) =>\n            error?.message && <li key={index}>{error.message}</li>\n        )}\n      </ul>\n    );\n  }, [children, errors]);\n\n  if (!content) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn('font-normal text-destructive text-sm', className)}\n      data-slot=\"field-error\"\n      role=\"alert\"\n      {...props}\n    >\n      {content}\n    </div>\n  );\n}\n\nexport {\n  Field,\n  FieldLabel,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLegend,\n  FieldSeparator,\n  FieldSet,\n  FieldContent,\n  FieldTitle,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/radio-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/radio-group.tsx",
          "content": "'use client';\n\nimport { Radio as RadioPrimitive } from '@base-ui/react/radio';\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {\n  return (\n    <RadioGroupPrimitive\n      className={cn('grid w-full gap-2', className)}\n      data-slot=\"radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {\n  return (\n    <RadioPrimitive.Root\n      className={cn(\n        'group/radio-group-item peer after:-inset-x-3 after:-inset-y-2 relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"radio-group-item\"\n      {...props}\n    >\n      <RadioPrimitive.Indicator\n        className=\"flex size-4 items-center justify-center\"\n        data-slot=\"radio-group-indicator\"\n      >\n        <span className=\"-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2 size-2 rounded-full bg-primary-foreground\" />\n      </RadioPrimitive.Indicator>\n    </RadioPrimitive.Root>\n  );\n}\n\nexport { RadioGroup, RadioGroupItem };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/form-layout-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "form-layout",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/form-layout-05/form-layout-05.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/form-layout/form-layout-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "form",
          "settings",
          "fields",
          "preferences",
          "表单",
          "设置",
          "输入",
          "账号"
        ],
        "contentGuidelines": [
          "Keep labels concise and place validation messages beside the relevant field.",
          "Replace sample handlers with your own save and validation logic."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/form-layout-05/form-layout-05.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/form-layout-05"
      }
    },
    {
      "name": "grid-list-01",
      "type": "registry:block",
      "title": "Grid List with Dropdown",
      "description": "Grid List with Dropdown. Responsive resource grids and card lists for projects, workspaces, and collections.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/grid-list/grid-list-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-01/grid-list-01.tsx",
          "content": "import { MoreVertical } from 'lucide-react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\n\nconst bookCollections = [\n  { name: 'Science Fiction', initials: 'SF', href: '#', books: 37 },\n  { name: 'Mystery Thrillers', initials: 'MT', href: '#', books: 29 },\n  { name: 'Historical Fiction', initials: 'HF', href: '#', books: 23 },\n];\n\nexport default function GridList01() {\n  return (\n    <div className=\"flex items-center justify-center p-8\">\n      <div>\n        <h2 className=\"text-balance font-medium text-muted-foreground text-sm\">\n          Favorite Book Collections\n        </h2>\n        <ul\n          className=\"mt-3 grid grid-cols-1 gap-5 sm:gap-6 lg:grid-cols-3\"\n          role=\"list\"\n        >\n          {bookCollections.map((collection) => (\n            <li className=\"col-span-1\" key={collection.name}>\n              <Card className=\"flex w-full flex-row gap-0 overflow-hidden rounded-md py-0 shadow-2xs\">\n                <div className=\"flex w-16 shrink-0 items-center justify-center bg-primary font-medium text-primary-foreground text-sm\">\n                  {collection.initials}\n                </div>\n                <CardContent className=\"flex flex-1 items-center justify-between truncate bg-card p-0\">\n                  <div className=\"flex-1 truncate px-4 py-2 text-sm\">\n                    <a\n                      className=\"font-medium text-foreground hover:text-muted-foreground\"\n                      href={collection.href}\n                    >\n                      {collection.name}\n                    </a>\n                    <p className=\"text-pretty text-muted-foreground\">\n                      {collection.books} Books\n                    </p>\n                  </div>\n                  <div className=\"shrink-0 pr-2\">\n                    <DropdownMenu>\n                      <DropdownMenuTrigger\n                        render={\n                          <Button\n                            className=\"h-8 w-8 rounded-full\"\n                            size=\"icon\"\n                            type=\"button\"\n                            variant=\"ghost\"\n                          />\n                        }\n                      >\n                        <span className=\"sr-only\">Open options</span>\n                        <MoreVertical className=\"h-5 w-5\" />\n                      </DropdownMenuTrigger>\n                      <DropdownMenuContent align=\"end\">\n                        <DropdownMenuItem>View collection</DropdownMenuItem>\n                        <DropdownMenuItem>Edit collection</DropdownMenuItem>\n                        <DropdownMenuSeparator />\n                        <DropdownMenuItem>Share collection</DropdownMenuItem>\n                        <DropdownMenuItem className=\"text-destructive hover:bg-destructive/10 hover:text-destructive focus:bg-destructive/10 focus:text-destructive\">\n                          Delete collection\n                        </DropdownMenuItem>\n                      </DropdownMenuContent>\n                    </DropdownMenu>\n                  </div>\n                </CardContent>\n              </Card>\n            </li>\n          ))}\n        </ul>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/grid-list-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/grid-list-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-01/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-01/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "grid-list",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/grid-list-01/grid-list-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/grid-list/grid-list-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "grid",
          "list",
          "cards",
          "projects",
          "网格",
          "列表",
          "卡片",
          "项目"
        ],
        "contentGuidelines": [
          "Use a consistent number of metadata fields per item.",
          "Test long resource names and the empty collection state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/grid-list-01/grid-list-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/grid-list-01"
      }
    },
    {
      "name": "grid-list-02",
      "type": "registry:block",
      "title": "Grid List with Links",
      "description": "Grid List with Links. Responsive resource grids and card lists for projects, workspaces, and collections.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "content/components/grid-list/grid-list-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-02/grid-list-02.tsx",
          "content": "import { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\n\nconst people = [\n  {\n    name: 'Timur Ercan',\n    email: 'timur@documenso.com',\n    role: 'Co-Founder / CEO',\n    imageUrl: 'https://blocks.so/avatar-02.png',\n  },\n  {\n    name: 'Lucas Smith',\n    email: 'lucas@documenso.com',\n    role: 'Co-Founder / CTO',\n    imageUrl: 'https://blocks.so/avatar-03.png',\n  },\n  {\n    name: 'Ephraim Duncan',\n    email: 'ephraim@documenso.com',\n    role: 'Software Engineer',\n    imageUrl: 'https://blocks.so/avatar-01.png',\n  },\n  {\n    name: 'Catalin Pit',\n    email: 'catalin@documenso.com',\n    role: 'Software Engineer',\n    imageUrl: 'https://blocks.so/avatar-04.png',\n  },\n];\n\nexport default function GridList02() {\n  return (\n    <div className=\"flex items-center justify-center p-8\">\n      <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n        {people.map((person) => (\n          <Card\n            className=\"relative border py-0 shadow-2xs transition-[border-color,box-shadow] duration-100 ease-out focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 hover:border-muted-foreground hover:shadow-sm\"\n            key={person.email}\n          >\n            <CardContent className=\"flex items-center space-x-4 p-4\">\n              <Avatar className=\"h-10 w-10\">\n                <AvatarImage alt={person.name} src={person.imageUrl} />\n                <AvatarFallback>{person.name.charAt(0)}</AvatarFallback>\n              </Avatar>\n              <div className=\"min-w-0 flex-1\">\n                <a className=\"focus:outline-none\" href=\"#\">\n                  <span aria-hidden=\"true\" className=\"absolute inset-0\" />\n                  <p className=\"text-pretty font-medium text-foreground text-sm\">\n                    {person.name}\n                  </p>\n                  <p className=\"truncate text-pretty text-muted-foreground text-sm\">\n                    {person.role}\n                  </p>\n                </a>\n              </div>\n            </CardContent>\n          </Card>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/grid-list-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/grid-list-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-02/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-02/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "grid-list",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/grid-list-02/grid-list-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/grid-list/grid-list-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "grid",
          "list",
          "cards",
          "projects",
          "网格",
          "列表",
          "卡片",
          "项目"
        ],
        "contentGuidelines": [
          "Use a consistent number of metadata fields per item.",
          "Test long resource names and the empty collection state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/grid-list-02/grid-list-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/grid-list-02"
      }
    },
    {
      "name": "grid-list-03",
      "type": "registry:block",
      "title": "Grid List with Icons",
      "description": "Grid List with Icons. Responsive resource grids and card lists for projects, workspaces, and collections.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/grid-list/grid-list-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-03/grid-list-03.tsx",
          "content": "import {\n  ArrowRight,\n  ArrowUpRight,\n  CheckCircle,\n  ContactRound,\n  Hand,\n  Server,\n  UserCircle,\n} from 'lucide-react';\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst actions = [\n  {\n    title: 'Getting Started',\n    description:\n      'Everything you need to know to get started and get to work in ChatCloud.',\n    href: '#',\n    icon: ArrowRight,\n    iconForeground: 'text-green-700',\n    iconBackground: 'bg-green-50 dark:bg-green-950/30',\n    ringColorClass: 'ring-green-700/30',\n  },\n  {\n    title: 'Admin Settings',\n    description:\n      'Learn how to manage your current workspace or your enterprise space.',\n    href: '#',\n    icon: UserCircle,\n    iconForeground: 'text-red-700',\n    iconBackground: 'bg-red-50 dark:bg-red-950/30',\n    ringColorClass: 'ring-red-700/30',\n  },\n  {\n    title: 'Server Setup',\n    description:\n      'Connect, simplify, and automate. Discover the power of apps and tools.',\n    href: '#',\n    icon: Server,\n    iconForeground: 'text-blue-700',\n    iconBackground: 'bg-blue-50 dark:bg-blue-950/30',\n    ringColorClass: 'ring-blue-700/30',\n  },\n  {\n    title: 'Login and Verification',\n    description:\n      'Read on to learn how to sign in with your email address, or your Apple or Google.',\n    href: '#',\n    icon: CheckCircle,\n    iconForeground: 'text-sky-700',\n    iconBackground: 'bg-sky-50 dark:bg-sky-950/30',\n    ringColorClass: 'ring-sky-700/30',\n  },\n  {\n    title: 'Account Setup',\n    description:\n      'Adjust your profile and preferences to make ChatCloud work just for you.',\n    href: '#',\n    icon: ContactRound,\n    iconForeground: 'text-pink-700',\n    iconBackground: 'bg-pink-50 dark:bg-pink-950/30',\n    ringColorClass: 'ring-pink-700/30',\n  },\n  {\n    title: 'Trust & Safety',\n    description:\n      'Trust on our current database and learn how we distribute your data.',\n    href: '#',\n    icon: Hand,\n    iconForeground: 'text-orange-700',\n    iconBackground: 'bg-orange-50 dark:bg-orange-950/30',\n    ringColorClass: 'ring-orange-700/30',\n  },\n];\n\nexport default function GridList03() {\n  return (\n    <div className=\"flex items-center justify-center p-8\">\n      <div className=\"grid grid-cols-1 space-y-0.5 overflow-hidden rounded-2xl bg-muted p-0.5 shadow-sm sm:grid-cols-2 sm:gap-0.5 sm:space-y-0 lg:grid-cols-3\">\n        {actions.map((action) => (\n          <Card\n            className={cn(\n              'group relative rounded-xl border-0 bg-card p-0 shadow-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-inset'\n            )}\n            key={action.title}\n          >\n            <CardContent className=\"p-6\">\n              <div>\n                <span\n                  className={cn(\n                    action.iconBackground,\n                    action.iconForeground,\n                    'inline-flex rounded-lg p-3 ring-2 ring-inset',\n                    action.ringColorClass\n                  )}\n                >\n                  <action.icon aria-hidden=\"true\" className=\"h-6 w-6\" />\n                </span>\n              </div>\n              <div className=\"mt-4\">\n                <h3 className=\"text-balance font-semibold text-base text-foreground\">\n                  <a className=\"focus:outline-none\" href={action.href}>\n                    <span aria-hidden=\"true\" className=\"absolute inset-0\" />\n                    {action.title}\n                  </a>\n                </h3>\n                <p className=\"mt-2 text-pretty text-muted-foreground text-sm\">\n                  {action.description}\n                </p>\n              </div>\n              <span\n                aria-hidden=\"true\"\n                className=\"pointer-events-none absolute top-6 right-6 text-muted-foreground/50 group-hover:text-muted-foreground/60\"\n              >\n                <ArrowUpRight className=\"h-6 w-6\" />\n              </span>\n            </CardContent>\n          </Card>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "licenses/grid-list-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/grid-list-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-03/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/grid-list-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "grid-list",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/grid-list-03/grid-list-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/grid-list/grid-list-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "grid",
          "list",
          "cards",
          "projects",
          "网格",
          "列表",
          "卡片",
          "项目"
        ],
        "contentGuidelines": [
          "Use a consistent number of metadata fields per item.",
          "Test long resource names and the empty collection state."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/grid-list-03/grid-list-03.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/grid-list-03"
      }
    },
    {
      "name": "hero-01",
      "type": "registry:block",
      "title": "Product Hero with Preview",
      "description": "A product landing hero with an editorial headline, two calls to action, and a media workspace preview.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/marketing/hero-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/hero-01/hero-01.tsx",
          "content": "import {\n  ArrowDown,\n  ArrowRight,\n  Check,\n  CheckCheck,\n  FileVideo,\n  Film,\n  Play,\n  Sparkles,\n} from \"lucide-react\";\n\nexport type Hero01Props = {\n  eyebrow?: string;\n  title?: string;\n  description?: string;\n  primaryLabel?: string;\n  primaryHref?: string;\n  secondaryLabel?: string;\n  secondaryHref?: string;\n};\n\n/** Split hero with an entirely code-rendered, asset-free product illustration. */\nexport default function Hero01({\n  eyebrow = \"A little less busywork. A lot more creating.\",\n  title = \"Your files. Ready for what’s next.\",\n  description = \"One calm workspace to organize, prepare, and export your media. Bring the files. Keep your focus on the good part.\",\n  primaryLabel = \"Explore the workspace\",\n  primaryHref = \"/preview/recipes/tool-workspace\",\n  secondaryLabel = \"See how it works\",\n  secondaryHref = \"/preview/recipes/tool-landing#features\",\n}: Hero01Props) {\n  return (\n    <section className=\"w-full overflow-hidden bg-white text-zinc-950 dark:bg-zinc-950 dark:text-white\">\n      <div className=\"mx-auto grid max-w-6xl items-center gap-14 px-6 py-20 md:py-28 lg:grid-cols-[1fr_1.08fr] lg:gap-16 lg:px-8\">\n        <div>\n          <p className=\"mb-6 inline-flex items-center gap-2 text-xs font-medium tracking-wide text-violet-600 dark:text-violet-400\">\n            <span className=\"size-1.5 rounded-full bg-violet-500\" />\n            {eyebrow}\n          </p>\n          <h1 className=\"max-w-[12ch] text-balance text-5xl font-semibold leading-[1.08] tracking-[-0.055em] sm:text-6xl lg:text-[4.25rem]\">\n            {title}\n          </h1>\n          <p className=\"mt-6 max-w-md text-pretty text-base leading-7 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n          <div className=\"mt-8 flex flex-wrap items-center gap-x-6 gap-y-4\">\n            <a\n              className=\"inline-flex min-h-11 items-center gap-3 rounded-lg bg-violet-600 px-5 py-3 text-sm font-medium text-white shadow-sm shadow-violet-600/15 transition-colors hover:bg-violet-700 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-violet-500\"\n              href={primaryHref}\n            >\n              {primaryLabel}\n              <ArrowRight aria-hidden=\"true\" className=\"size-4\" />\n            </a>\n            <a\n              className=\"inline-flex min-h-11 items-center gap-2 rounded text-sm font-medium text-zinc-600 hover:text-zinc-950 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-zinc-300 dark:hover:text-white\"\n              href={secondaryHref}\n            >\n              {secondaryLabel}\n              <ArrowDown aria-hidden=\"true\" className=\"size-3.5\" />\n            </a>\n          </div>\n          <p className=\"mt-7 flex items-center gap-2 text-xs text-zinc-400\">\n            <CheckCheck aria-hidden=\"true\" className=\"size-4 text-zinc-400\" />\n            Interactive demo · No account needed\n          </p>\n        </div>\n        <div className=\"relative min-w-0 scroll-mt-8\" id=\"product-preview\">\n          <div\n            aria-hidden=\"true\"\n            className=\"absolute -inset-8 rounded-[3rem] bg-violet-50/70 dark:bg-violet-950/20\"\n          />\n          <div className=\"relative overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-[0_20px_60px_-25px_rgba(30,41,59,0.2)] dark:border-zinc-700 dark:bg-zinc-900\">\n            <div className=\"flex items-center justify-between border-b border-zinc-100 px-4 py-3 dark:border-zinc-800\">\n              <div aria-hidden=\"true\" className=\"flex gap-1.5\">\n                <span className=\"size-2 rounded-full bg-zinc-200 dark:bg-zinc-700\" />\n                <span className=\"size-2 rounded-full bg-zinc-200 dark:bg-zinc-700\" />\n                <span className=\"size-2 rounded-full bg-zinc-200 dark:bg-zinc-700\" />\n              </div>\n              <span className=\"font-mono text-[10px] text-zinc-400\">\n                frame / workspace\n              </span>\n              <span className=\"rounded border border-zinc-200 px-1.5 py-0.5 text-[9px] font-medium tracking-wider text-zinc-400 dark:border-zinc-700\">\n                DEMO\n              </span>\n            </div>\n            <div className=\"p-5 sm:p-7\">\n              <div className=\"mb-5 flex items-start justify-between gap-3\">\n                <div>\n                  <p className=\"text-sm font-semibold\">The next big idea</p>\n                  <p className=\"mt-1 text-[11px] text-zinc-400\">\n                    A place for work in progress.\n                  </p>\n                </div>\n                <span className=\"rounded-full bg-violet-50 px-2.5 py-1 text-[10px] font-medium text-violet-600 dark:bg-violet-500/10 dark:text-violet-300\">\n                  Personal project\n                </span>\n              </div>\n              <div className=\"relative grid aspect-[1.65] place-items-center overflow-hidden rounded-lg bg-zinc-950\">\n                <div\n                  aria-hidden=\"true\"\n                  className=\"absolute inset-0 bg-[radial-gradient(ellipse_at_25%_90%,#7c3aed_0%,transparent_50%),radial-gradient(ellipse_at_90%_10%,#71717a_0%,transparent_50%)] opacity-75\"\n                />\n                <div\n                  aria-hidden=\"true\"\n                  className=\"absolute size-32 rotate-[-25deg] rounded-[1.5rem] border border-white/30 bg-white/10 shadow-xl backdrop-blur-md sm:size-40\"\n                />\n                <div\n                  aria-hidden=\"true\"\n                  className=\"absolute size-32 translate-x-8 translate-y-5 rotate-[15deg] rounded-[1.5rem] border border-white/30 bg-white/10 shadow-xl backdrop-blur-md sm:size-40\"\n                />\n                <span className=\"relative grid size-12 place-items-center rounded-full border border-white/30 bg-white/15 text-white backdrop-blur\">\n                  <Play\n                    aria-hidden=\"true\"\n                    className=\"ml-0.5 size-4 fill-current\"\n                  />\n                </span>\n                <div className=\"absolute inset-x-3 bottom-3 flex items-center justify-between rounded bg-black/20 px-2 py-1.5 font-mono text-[9px] text-white/80\">\n                  <span>launch-film.mp4</span>\n                  <span>00:42 / 01:28</span>\n                </div>\n              </div>\n              <div className=\"mt-4 flex items-center gap-3 rounded-lg border border-zinc-200 p-3 dark:border-zinc-700\">\n                <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-violet-50 text-violet-500 dark:bg-violet-500/10\">\n                  <FileVideo aria-hidden=\"true\" className=\"size-4\" />\n                </span>\n                <div className=\"min-w-0 flex-1\">\n                  <p className=\"truncate text-[11px] font-medium\">\n                    launch-film.mp4\n                  </p>\n                  <p className=\"mt-1 text-[10px] text-zinc-400\">\n                    MP4 · 1080p · Ready to export\n                  </p>\n                </div>\n                <span className=\"grid size-5 shrink-0 place-items-center rounded-full bg-emerald-50 text-emerald-600 dark:bg-emerald-500/10\">\n                  <Check aria-hidden=\"true\" className=\"size-3\" />\n                </span>\n              </div>\n              <div className=\"mt-3 grid grid-cols-3 gap-2 text-center text-[10px] text-zinc-500 dark:text-zinc-400\">\n                <span className=\"flex items-center justify-center gap-1.5 rounded border border-zinc-100 py-2 dark:border-zinc-800\">\n                  <Film aria-hidden=\"true\" className=\"size-3\" />\n                  Prepare\n                </span>\n                <span className=\"flex items-center justify-center gap-1.5 rounded border border-zinc-100 py-2 dark:border-zinc-800\">\n                  <Sparkles aria-hidden=\"true\" className=\"size-3\" />\n                  Organize\n                </span>\n                <span className=\"flex items-center justify-center gap-1.5 rounded border border-zinc-100 py-2 dark:border-zinc-800\">\n                  <CheckCheck aria-hidden=\"true\" className=\"size-3\" />\n                  Export\n                </span>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "licenses/hero-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/hero-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        }
      ],
      "categories": [
        "marketing",
        "marketing",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/hero-01/hero-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/marketing/hero-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "marketing",
        "featured": true,
        "tags": [
          "hero",
          "landing",
          "product",
          "video",
          "preview",
          "split",
          "首屏",
          "官网",
          "视频",
          "预览",
          "长标题"
        ],
        "contentGuidelines": [
          "Aim for two to three headline lines; test longer Chinese copy on mobile.",
          "Keep the introduction to two short sentences and use at most two calls to action."
        ],
        "recommendedWith": [
          "navigation-01",
          "logo-cloud-01",
          "features-01"
        ],
        "entry": "@components/uiblocks/hero-01/hero-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/hero-01"
      }
    },
    {
      "name": "login-01",
      "type": "registry:block",
      "title": "Login with Email and Google",
      "description": "Login with Email and Google. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/login-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-01/login-01.tsx",
          "content": "import type { JSX, SVGProps } from 'react';\nimport { Button, buttonVariants } from \"./shared/components/ui/button\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport { Separator } from \"./shared/components/ui/separator\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst GoogleIcon = (\n  props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>\n) => (\n  <svg fill=\"currentColor\" viewBox=\"0 0 24 24\" {...props}>\n    <path d=\"M3.06364 7.50914C4.70909 4.24092 8.09084 2 12 2C14.6954 2 16.959 2.99095 18.6909 4.60455L15.8227 7.47274C14.7864 6.48185 13.4681 5.97727 12 5.97727C9.39542 5.97727 7.19084 7.73637 6.40455 10.1C6.2045 10.7 6.09086 11.3409 6.09086 12C6.09086 12.6591 6.2045 13.3 6.40455 13.9C7.19084 16.2636 9.39542 18.0227 12 18.0227C13.3454 18.0227 14.4909 17.6682 15.3864 17.0682C16.4454 16.3591 17.15 15.3 17.3818 14.05H12V10.1818H21.4181C21.5364 10.8363 21.6 11.5182 21.6 12.2273C21.6 15.2727 20.5091 17.8363 18.6181 19.5773C16.9636 21.1046 14.7 22 12 22C8.09084 22 4.70909 19.7591 3.06364 16.4909C2.38638 15.1409 2 13.6136 2 12C2 10.3864 2.38638 8.85911 3.06364 7.50914Z\" />\n  </svg>\n);\n\nexport default function Login01() {\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <div className=\"flex flex-1 flex-col justify-center px-4 py-10 lg:px-6\">\n        <div className=\"sm:mx-auto sm:w-full sm:max-w-sm\">\n          <h2 className=\"text-balance text-center font-semibold text-foreground text-xl\">\n            Log in or create account\n          </h2>\n          <form action=\"#\" className=\"mt-6\" method=\"post\">\n            <Label className=\"font-medium text-foreground\" htmlFor=\"email\">\n              Email\n            </Label>\n            <Input\n              autoComplete=\"email\"\n              className=\"mt-2\"\n              id=\"email\"\n              name=\"email\"\n              placeholder=\"john@company.com\"\n              type=\"email\"\n            />\n            <Button className=\"mt-4 w-full\" type=\"submit\">\n              Sign in\n            </Button>\n          </form>\n\n          <div className=\"relative my-6\">\n            <div className=\"absolute inset-0 flex items-center\">\n              <Separator className=\"w-full\" />\n            </div>\n            <div className=\"relative flex justify-center text-xs uppercase\">\n              <span className=\"bg-background px-2 text-muted-foreground\">\n                or with\n              </span>\n            </div>\n          </div>\n\n          <a\n            className={cn(\n              buttonVariants({ variant: 'outline' }),\n              'inline-flex w-full items-center justify-center space-x-2'\n            )}\n            href=\"#\"\n          >\n            <GoogleIcon aria-hidden={true} className=\"size-5\" />\n            <span className=\"font-medium text-sm\">Sign in with Google</span>\n          </a>\n\n          <p className=\"mt-4 text-pretty text-muted-foreground text-xs\">\n            By signing in, you agree to our{' '}\n            <a className=\"underline underline-offset-4\" href=\"#\">\n              terms of service\n            </a>{' '}\n            and{' '}\n            <a className=\"underline underline-offset-4\" href=\"#\">\n              privacy policy\n            </a>\n            .\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-01/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-01/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-01/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-01/login-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-01/login-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-01"
      }
    },
    {
      "name": "login-02",
      "type": "registry:block",
      "title": "Login with Email, Password and Google",
      "description": "Login with Email, Password and Google. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/login-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-02/login-02.tsx",
          "content": "import type { JSX, SVGProps } from 'react';\nimport { Button, buttonVariants } from \"./shared/components/ui/button\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport { Separator } from \"./shared/components/ui/separator\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst GoogleIcon = (\n  props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>\n) => (\n  <svg fill=\"currentColor\" viewBox=\"0 0 24 24\" {...props}>\n    <path d=\"M3.06364 7.50914C4.70909 4.24092 8.09084 2 12 2C14.6954 2 16.959 2.99095 18.6909 4.60455L15.8227 7.47274C14.7864 6.48185 13.4681 5.97727 12 5.97727C9.39542 5.97727 7.19084 7.73637 6.40455 10.1C6.2045 10.7 6.09086 11.3409 6.09086 12C6.09086 12.6591 6.2045 13.3 6.40455 13.9C7.19084 16.2636 9.39542 18.0227 12 18.0227C13.3454 18.0227 14.4909 17.6682 15.3864 17.0682C16.4454 16.3591 17.15 15.3 17.3818 14.05H12V10.1818H21.4181C21.5364 10.8363 21.6 11.5182 21.6 12.2273C21.6 15.2727 20.5091 17.8363 18.6181 19.5773C16.9636 21.1046 14.7 22 12 22C8.09084 22 4.70909 19.7591 3.06364 16.4909C2.38638 15.1409 2 13.6136 2 12C2 10.3864 2.38638 8.85911 3.06364 7.50914Z\" />\n  </svg>\n);\n\nexport default function Login02() {\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <div className=\"flex flex-1 flex-col justify-center px-4 py-10 lg:px-6\">\n        <div className=\"sm:mx-auto sm:w-full sm:max-w-sm\">\n          <h2 className=\"text-balance text-center font-semibold text-foreground text-xl\">\n            Log in or create account\n          </h2>\n          <form action=\"#\" className=\"mt-6 space-y-4\" method=\"post\">\n            <div>\n              <Label\n                className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                htmlFor=\"email-login-02\"\n              >\n                Email\n              </Label>\n              <Input\n                autoComplete=\"email\"\n                className=\"mt-2\"\n                id=\"email-login-02\"\n                name=\"email-login-02\"\n                placeholder=\"ephraim@blocks.so\"\n                type=\"email\"\n              />\n            </div>\n            <div>\n              <Label\n                className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                htmlFor=\"password-login-02\"\n              >\n                Password\n              </Label>\n              <Input\n                autoComplete=\"password\"\n                className=\"mt-2\"\n                id=\"password-login-02\"\n                name=\"password-login-02\"\n                placeholder=\"**************\"\n                type=\"password\"\n              />\n            </div>\n            <Button className=\"mt-4 w-full py-2 font-medium\" type=\"submit\">\n              Sign in\n            </Button>\n          </form>\n\n          <div className=\"relative my-6\">\n            <div className=\"absolute inset-0 flex items-center\">\n              <Separator className=\"w-full\" />\n            </div>\n            <div className=\"relative flex justify-center text-xs uppercase\">\n              <span className=\"bg-background px-2 text-muted-foreground\">\n                or with\n              </span>\n            </div>\n          </div>\n\n          <a\n            className={cn(\n              buttonVariants({ variant: 'outline' }),\n              'flex w-full items-center justify-center space-x-2 py-2'\n            )}\n            href=\"#\"\n          >\n            <GoogleIcon aria-hidden={true} className=\"size-5\" />\n            <span className=\"font-medium text-sm\">Sign in with Google</span>\n          </a>\n\n          <p className=\"mt-4 text-pretty text-muted-foreground text-xs dark:text-muted-foreground\">\n            By signing in, you agree to our{' '}\n            <a className=\"underline underline-offset-4\" href=\"#\">\n              terms of service\n            </a>{' '}\n            and{' '}\n            <a className=\"underline underline-offset-4\" href=\"#\">\n              privacy policy\n            </a>\n            .\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-02/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-02/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-02/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-02/login-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-02/login-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-02"
      }
    },
    {
      "name": "login-03",
      "type": "registry:block",
      "title": "Login with Email and Password",
      "description": "Login with Email and Password. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/login-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-03/login-03.tsx",
          "content": "'use client';\n\nimport { Button } from \"./shared/components/ui/button\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\nexport default function Login03() {\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <div className=\"flex flex-1 flex-col justify-center px-4 py-10 lg:px-6\">\n        <div className=\"sm:mx-auto sm:w-full sm:max-w-sm\">\n          <h3 className=\"text-balance text-center font-semibold text-foreground text-lg dark:text-foreground\">\n            Welcome Back\n          </h3>\n          <p className=\"text-pretty text-center text-muted-foreground text-sm dark:text-muted-foreground\">\n            Enter your credentials to access your account.\n          </p>\n          <form action=\"#\" className=\"mt-6 space-y-4\" method=\"post\">\n            <div>\n              <Label\n                className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                htmlFor=\"email-login-03\"\n              >\n                Email\n              </Label>\n              <Input\n                autoComplete=\"email\"\n                className=\"mt-2\"\n                id=\"email-login-03\"\n                name=\"email-login-03\"\n                placeholder=\"ephraim@blocks.so\"\n                type=\"email\"\n              />\n            </div>\n            <div>\n              <Label\n                className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                htmlFor=\"password-login-03\"\n              >\n                Password\n              </Label>\n              <Input\n                autoComplete=\"password\"\n                className=\"mt-2\"\n                id=\"password-login-03\"\n                name=\"password-login-03\"\n                placeholder=\"**************\"\n                type=\"password\"\n              />\n            </div>\n            <Button className=\"mt-4 w-full py-2 font-medium\" type=\"submit\">\n              Sign in\n            </Button>\n          </form>\n          <p className=\"mt-6 text-pretty text-muted-foreground text-sm dark:text-muted-foreground\">\n            Forgot your password?{' '}\n            <a\n              className=\"font-medium text-primary hover:text-primary/90 dark:text-primary dark:hover:text-primary/90\"\n              href=\"#\"\n            >\n              Reset password\n            </a>\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-03/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-03/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-03/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-03/login-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-03/login-03.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-03"
      }
    },
    {
      "name": "login-04",
      "type": "registry:block",
      "title": "Login with Social Buttons",
      "description": "Login with Social Buttons. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/login-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-04/login-04.tsx",
          "content": "import type { JSX, SVGProps } from 'react';\nimport { Button, buttonVariants } from \"./shared/components/ui/button\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport { Separator } from \"./shared/components/ui/separator\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst GitHubIcon = (\n  props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>\n) => (\n  <svg fill=\"currentColor\" viewBox=\"0 0 24 24\" {...props}>\n    <path d=\"M12.001 2C6.47598 2 2.00098 6.475 2.00098 12C2.00098 16.425 4.86348 20.1625 8.83848 21.4875C9.33848 21.575 9.52598 21.275 9.52598 21.0125C9.52598 20.775 9.51348 19.9875 9.51348 19.15C7.00098 19.6125 6.35098 18.5375 6.15098 17.975C6.03848 17.6875 5.55098 16.8 5.12598 16.5625C4.77598 16.375 4.27598 15.9125 5.11348 15.9C5.90098 15.8875 6.46348 16.625 6.65098 16.925C7.55098 18.4375 8.98848 18.0125 9.56348 17.75C9.65098 17.1 9.91348 16.6625 10.201 16.4125C7.97598 16.1625 5.65098 15.3 5.65098 11.475C5.65098 10.3875 6.03848 9.4875 6.67598 8.7875C6.57598 8.5375 6.22598 7.5125 6.77598 6.1375C6.77598 6.1375 7.61348 5.875 9.52598 7.1625C10.326 6.9375 11.176 6.825 12.026 6.825C12.876 6.825 13.726 6.9375 14.526 7.1625C16.4385 5.8625 17.276 6.1375 17.276 6.1375C17.826 7.5125 17.476 8.5375 17.376 8.7875C18.0135 9.4875 18.401 10.375 18.401 11.475C18.401 15.3125 16.0635 16.1625 13.8385 16.4125C14.201 16.725 14.5135 17.325 14.5135 18.2625C14.5135 19.6 14.501 20.675 14.501 21.0125C14.501 21.275 14.6885 21.5875 15.1885 21.4875C19.259 20.1133 21.9999 16.2963 22.001 12C22.001 6.475 17.526 2 12.001 2Z\" />\n  </svg>\n);\n\nconst GoogleIcon = (\n  props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>\n) => (\n  <svg fill=\"currentColor\" viewBox=\"0 0 24 24\" {...props}>\n    <path d=\"M3.06364 7.50914C4.70909 4.24092 8.09084 2 12 2C14.6954 2 16.959 2.99095 18.6909 4.60455L15.8227 7.47274C14.7864 6.48185 13.4681 5.97727 12 5.97727C9.39542 5.97727 7.19084 7.73637 6.40455 10.1C6.2045 10.7 6.09086 11.3409 6.09086 12C6.09086 12.6591 6.2045 13.3 6.40455 13.9C7.19084 16.2636 9.39542 18.0227 12 18.0227C13.3454 18.0227 14.4909 17.6682 15.3864 17.0682C16.4454 16.3591 17.15 15.3 17.3818 14.05H12V10.1818H21.4181C21.5364 10.8363 21.6 11.5182 21.6 12.2273C21.6 15.2727 20.5091 17.8363 18.6181 19.5773C16.9636 21.1046 14.7 22 12 22C8.09084 22 4.70909 19.7591 3.06364 16.4909C2.38638 15.1409 2 13.6136 2 12C2 10.3864 2.38638 8.85911 3.06364 7.50914Z\" />\n  </svg>\n);\n\nconst Logo = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (\n  <svg\n    fill=\"currentColor\"\n    height=\"48\"\n    viewBox=\"0 0 40 48\"\n    width=\"40\"\n    {...props}\n  >\n    <clipPath id=\"a\">\n      <path d=\"m0 0h40v48h-40z\" />\n    </clipPath>\n    <g clipPath=\"url(#a)\">\n      <path d=\"m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z\" />\n      <path d=\"m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z\" />\n      <path d=\"m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z\" />\n      <path d=\"m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z\" />\n      <path d=\"m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z\" />\n      <path d=\"m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z\" />\n    </g>\n  </svg>\n);\n\nexport default function Login04() {\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <div className=\"flex flex-1 flex-col justify-center px-4 py-10 lg:px-6\">\n        <div className=\"sm:mx-auto sm:w-full sm:max-w-md\">\n          <div className=\"flex items-center space-x-1.5\">\n            <Logo\n              aria-hidden={true}\n              className=\"h-7 w-7 text-foreground dark:text-foreground\"\n            />\n            <p className=\"text-pretty font-medium text-foreground text-lg dark:text-foreground\">\n              Acme\n            </p>\n          </div>\n          <h3 className=\"mt-6 text-balance font-semibold text-foreground text-lg dark:text-foreground\">\n            Sign in to your account\n          </h3>\n          <p className=\"mt-2 text-pretty text-muted-foreground text-sm dark:text-muted-foreground\">\n            Don&apos;t have an account?{' '}\n            <a\n              className=\"font-medium text-primary hover:text-primary/90 dark:text-primary hover:dark:text-primary/90\"\n              href=\"#\"\n            >\n              Sign up\n            </a>\n          </p>\n          <div className=\"mt-8 flex flex-col items-center space-y-2 sm:flex-row sm:space-x-4 sm:space-y-0\">\n            <a\n              className={cn(\n                buttonVariants({ variant: 'outline' }),\n                'flex-1 items-center justify-center space-x-2 py-2'\n              )}\n              href=\"#\"\n            >\n              <GitHubIcon aria-hidden={true} className=\"size-5\" />\n              <span className=\"font-medium text-sm\">Login with GitHub</span>\n            </a>\n            <a\n              className={cn(\n                buttonVariants({ variant: 'outline' }),\n                'mt-2 flex-1 items-center justify-center space-x-2 py-2 sm:mt-0'\n              )}\n              href=\"#\"\n            >\n              <GoogleIcon aria-hidden={true} className=\"size-4\" />\n              <span className=\"font-medium text-sm\">Login with Google</span>\n            </a>\n          </div>\n\n          <div className=\"relative my-6\">\n            <div className=\"absolute inset-0 flex items-center\">\n              <Separator className=\"w-full\" />\n            </div>\n            <div className=\"relative flex justify-center text-xs uppercase\">\n              <span className=\"bg-background px-2 text-muted-foreground\">\n                or\n              </span>\n            </div>\n          </div>\n\n          <form action=\"#\" className=\"mt-6 space-y-4\" method=\"post\">\n            <div>\n              <Label\n                className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                htmlFor=\"email-login-04\"\n              >\n                Email\n              </Label>\n              <Input\n                autoComplete=\"email\"\n                className=\"mt-2\"\n                id=\"email-login-04\"\n                name=\"email-login-04\"\n                placeholder=\"ephraim@blocks.so\"\n                type=\"email\"\n              />\n            </div>\n            <div>\n              <Label\n                className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                htmlFor=\"password-login-04\"\n              >\n                Password\n              </Label>\n              <Input\n                autoComplete=\"password\"\n                className=\"mt-2\"\n                id=\"password-login-04\"\n                name=\"password-login-04\"\n                placeholder=\"********\"\n                type=\"password\"\n              />\n            </div>\n            <Button className=\"mt-4 w-full py-2 font-medium\" type=\"submit\">\n              Sign in\n            </Button>\n          </form>\n          <p className=\"mt-6 text-pretty text-muted-foreground text-sm dark:text-muted-foreground\">\n            Forgot your password?{' '}\n            <a\n              className=\"font-medium text-primary hover:text-primary/90 dark:text-primary hover:dark:text-primary/90\"\n              href=\"#\"\n            >\n              Reset password\n            </a>\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-04/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-04/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-04/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-04/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-04/login-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-04/login-04.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-04"
      }
    },
    {
      "name": "login-05",
      "type": "registry:block",
      "title": "Login Card Signup Form",
      "description": "Login Card Signup Form. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/login-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-05.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-05/login-05.tsx",
          "content": "import type { JSX, SVGProps } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { Checkbox } from \"./shared/components/ui/checkbox\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\nconst Logo = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (\n  <svg\n    fill=\"currentColor\"\n    height=\"48\"\n    viewBox=\"0 0 40 48\"\n    width=\"40\"\n    {...props}\n  >\n    <clipPath id=\"a\">\n      <path d=\"m0 0h40v48h-40z\" />\n    </clipPath>\n    <g clipPath=\"url(#a)\">\n      <path d=\"m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z\" />\n      <path d=\"m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z\" />\n      <path d=\"m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z\" />\n      <path d=\"m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z\" />\n      <path d=\"m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z\" />\n      <path d=\"m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z\" />\n    </g>\n  </svg>\n);\n\nexport default function Login05() {\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <div className=\"flex flex-1 flex-col justify-center px-4 py-10 lg:px-6\">\n        <div className=\"sm:mx-auto sm:w-full sm:max-w-md\">\n          <Logo\n            aria-hidden={true}\n            className=\"mx-auto h-10 w-10 text-foreground dark:text-foreground\"\n          />\n          <h3 className=\"mt-2 text-balance text-center font-bold text-foreground text-lg dark:text-foreground\">\n            Create new account for workspace\n          </h3>\n        </div>\n\n        <Card className=\"mt-4 shadow-2xs sm:mx-auto sm:w-full sm:max-w-md\">\n          <CardContent>\n            <form action=\"#\" className=\"space-y-4\" method=\"post\">\n              <div>\n                <Label\n                  className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                  htmlFor=\"name-login-05\"\n                >\n                  Name\n                </Label>\n                <Input\n                  autoComplete=\"name-login-05\"\n                  className=\"mt-2\"\n                  id=\"name-login-05\"\n                  name=\"name-login-05\"\n                  placeholder=\"Name\"\n                  type=\"text\"\n                />\n              </div>\n\n              <div>\n                <Label\n                  className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                  htmlFor=\"email-login-05\"\n                >\n                  Email\n                </Label>\n                <Input\n                  autoComplete=\"email-login-05\"\n                  className=\"mt-2\"\n                  id=\"email-login-05\"\n                  name=\"email-login-05\"\n                  placeholder=\"ephraim@blocks.so\"\n                  type=\"email\"\n                />\n              </div>\n\n              <div>\n                <Label\n                  className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                  htmlFor=\"password-login-05\"\n                >\n                  Password\n                </Label>\n                <Input\n                  autoComplete=\"password-login-05\"\n                  className=\"mt-2\"\n                  id=\"password-login-05   \"\n                  name=\"password-login-05\"\n                  placeholder=\"Password\"\n                  type=\"password\"\n                />\n              </div>\n\n              <div>\n                <Label\n                  className=\"font-medium text-foreground text-sm dark:text-foreground\"\n                  htmlFor=\"confirm-password-login-05\"\n                >\n                  Confirm password\n                </Label>\n                <Input\n                  autoComplete=\"confirm-password-login-05\"\n                  className=\"mt-2\"\n                  id=\"confirm-password-login-05\"\n                  name=\"confirm-password-login-05\"\n                  placeholder=\"Password\"\n                  type=\"password\"\n                />\n              </div>\n\n              <div className=\"mt-2 flex items-start\">\n                <div className=\"flex h-6 items-center\">\n                  <Checkbox\n                    className=\"size-4\"\n                    id=\"newsletter-login-05\"\n                    name=\"newsletter-login-05\"\n                  />\n                </div>\n                <Label\n                  className=\"ml-3 text-muted-foreground text-sm leading-6 dark:text-muted-foreground\"\n                  htmlFor=\"newsletter-login-05\"\n                >\n                  Sign up to our newsletter\n                </Label>\n              </div>\n\n              <Button className=\"mt-4 w-full py-2 font-medium\" type=\"submit\">\n                Create account\n              </Button>\n\n              <p className=\"text-pretty text-center text-muted-foreground text-xs dark:text-muted-foreground\">\n                By signing in, you agree to our{' '}\n                <a\n                  className=\"text-primary capitalize hover:text-primary/90 dark:text-primary hover:dark:text-primary/90\"\n                  href=\"#\"\n                >\n                  Terms of use\n                </a>{' '}\n                and{' '}\n                <a\n                  className=\"text-primary capitalize hover:text-primary/90 dark:text-primary hover:dark:text-primary/90\"\n                  href=\"#\"\n                >\n                  Privacy policy\n                </a>\n              </p>\n            </form>\n          </CardContent>\n        </Card>\n\n        <p className=\"mt-6 text-pretty text-center text-muted-foreground text-sm dark:text-muted-foreground\">\n          Already have an account?{' '}\n          <a\n            className=\"font-medium text-primary hover:text-primary/90 dark:text-primary hover:dark:text-primary/90\"\n            href=\"#\"\n          >\n            Sign in\n          </a>\n        </p>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-05/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-05/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/checkbox.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-05/shared/components/ui/checkbox.tsx",
          "content": "'use client';\n\nimport { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { CheckIcon } from 'lucide-react';\nimport { cn } from \"../../lib/utils\";\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      className={cn(\n        'peer after:-inset-x-3 after:-inset-y-2 relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input outline-none transition-colors after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"checkbox\"\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n        data-slot=\"checkbox-indicator\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n\nexport { Checkbox };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-05/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-05/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-05/login-05.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-05/login-05.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-05"
      }
    },
    {
      "name": "login-06",
      "type": "registry:block",
      "title": "Login with Magic Link and SSO",
      "description": "Login with Magic Link and SSO. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/login-06/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-06/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-06.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-06/login-06.tsx",
          "content": "import type { JSX, SVGProps } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Separator } from \"./shared/components/ui/separator\";\n\nconst Logo = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (\n  <svg\n    fill=\"currentColor\"\n    height=\"48\"\n    viewBox=\"0 0 40 48\"\n    width=\"40\"\n    {...props}\n  >\n    <clipPath id=\"a\">\n      <path d=\"m0 0h40v48h-40z\" />\n    </clipPath>\n    <g clipPath=\"url(#a)\">\n      <path d=\"m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z\" />\n      <path d=\"m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z\" />\n      <path d=\"m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z\" />\n      <path d=\"m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z\" />\n      <path d=\"m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z\" />\n      <path d=\"m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z\" />\n    </g>\n  </svg>\n);\n\nexport default function Login06() {\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <Card className=\"w-full max-w-sm rounded-4xl px-6 py-10 pt-14 shadow-2xs\">\n        <CardContent className=\"\">\n          <div className=\"flex flex-col items-center space-y-8\">\n            <Logo />\n\n            <div className=\"space-y-2 text-center\">\n              <h1 className=\"text-balance font-semibold text-3xl text-foreground\">\n                Welcome back!\n              </h1>\n              <p className=\"text-pretty text-muted-foreground text-sm\">\n                First time here?{' '}\n                <a className=\"text-foreground hover:underline\" href=\"#\">\n                  Sign up for free\n                </a>\n              </p>\n            </div>\n\n            <div className=\"w-full space-y-4\">\n              <Input\n                className=\"w-full rounded-xl\"\n                placeholder=\"Your email\"\n                type=\"email\"\n              />\n              <div className=\"flex flex-col gap-2\">\n                <Button className=\"w-full rounded-xl\" size=\"lg\">\n                  Send me the magic link\n                </Button>\n                <Button\n                  className=\"w-full text-muted-foreground text-sm\"\n                  variant=\"link\"\n                >\n                  Sign in using password\n                </Button>\n              </div>\n\n              <div className=\"flex items-center gap-4 py-2\">\n                <Separator className=\"flex-1\" />\n                <span className=\"text-muted-foreground text-sm\">OR</span>\n                <Separator className=\"flex-1\" />\n              </div>\n\n              <Button className=\"w-full rounded-xl\" size=\"lg\" variant=\"outline\">\n                Single sign-on (SSO)\n              </Button>\n            </div>\n\n            <p className=\"w-11/12 text-pretty text-center text-muted-foreground text-xs\">\n              You acknowledge that you read, and agree, to our{' '}\n              <a className=\"underline hover:text-foreground\" href=\"#\">\n                Terms of Service\n              </a>{' '}\n              and our{' '}\n              <a className=\"underline hover:text-foreground\" href=\"#\">\n                Privacy Policy\n              </a>\n              .\n            </p>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-06/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-06/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-06/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-06/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-06/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-06/login-06.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-06 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-06/login-06.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-06"
      }
    },
    {
      "name": "login-07",
      "type": "registry:block",
      "title": "Login Full Page with Icons",
      "description": "Login Full Page with Icons. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/login-07/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-07/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-07.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-07/login-07.tsx",
          "content": "'use client';\n\nimport { ArrowRight, Eye, EyeOff, Lock, Mail } from 'lucide-react';\nimport { type JSX, type SVGProps, useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Checkbox } from \"./shared/components/ui/checkbox\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport { Separator } from \"./shared/components/ui/separator\";\n\nconst GoogleIcon = (\n  props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>\n) => (\n  <svg fill=\"currentColor\" viewBox=\"0 0 24 24\" {...props}>\n    <path d=\"M3.06364 7.50914C4.70909 4.24092 8.09084 2 12 2C14.6954 2 16.959 2.99095 18.6909 4.60455L15.8227 7.47274C14.7864 6.48185 13.4681 5.97727 12 5.97727C9.39542 5.97727 7.19084 7.73637 6.40455 10.1C6.2045 10.7 6.09086 11.3409 6.09086 12C6.09086 12.6591 6.2045 13.3 6.40455 13.9C7.19084 16.2636 9.39542 18.0227 12 18.0227C13.3454 18.0227 14.4909 17.6682 15.3864 17.0682C16.4454 16.3591 17.15 15.3 17.3818 14.05H12V10.1818H21.4181C21.5364 10.8363 21.6 11.5182 21.6 12.2273C21.6 15.2727 20.5091 17.8363 18.6181 19.5773C16.9636 21.1046 14.7 22 12 22C8.09084 22 4.70909 19.7591 3.06364 16.4909C2.38638 15.1409 2 13.6136 2 12C2 10.3864 2.38638 8.85911 3.06364 7.50914Z\" />\n  </svg>\n);\n\nconst Logo = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (\n  <svg\n    fill=\"currentColor\"\n    height=\"48\"\n    viewBox=\"0 0 40 48\"\n    width=\"40\"\n    {...props}\n  >\n    <clipPath id=\"a\">\n      <path d=\"m0 0h40v48h-40z\" />\n    </clipPath>\n    <g clipPath=\"url(#a)\">\n      <path d=\"m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z\" />\n      <path d=\"m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z\" />\n      <path d=\"m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z\" />\n      <path d=\"m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z\" />\n      <path d=\"m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z\" />\n      <path d=\"m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z\" />\n    </g>\n  </svg>\n);\n\nexport default function Login07() {\n  const [isVisible, setIsVisible] = useState<boolean>(false);\n\n  const toggleVisibility = () => setIsVisible((prevState) => !prevState);\n\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <div className=\"mx-auto w-full max-w-xs space-y-6\">\n        <div className=\"space-y-2 text-center\">\n          <Logo className=\"mx-auto h-16 w-16\" />\n          <h1 className=\"text-balance font-semibold text-3xl\">Welcome back</h1>\n          <p className=\"text-pretty text-muted-foreground\">\n            Sign in to access to your dashboard, settings and projects.\n          </p>\n        </div>\n\n        <div className=\"space-y-5\">\n          <Button className=\"w-full justify-center gap-2\" variant=\"outline\">\n            <GoogleIcon className=\"h-4 w-4\" />\n            Sign in with Google\n          </Button>\n\n          <div className=\"flex items-center gap-2\">\n            <Separator className=\"flex-1\" />\n            <span className=\"text-muted-foreground text-sm\">\n              or sign in with email\n            </span>\n            <Separator className=\"flex-1\" />\n          </div>\n\n          <div className=\"space-y-6\">\n            <div>\n              <Label htmlFor=\"email\">Email</Label>\n              <div className=\"relative mt-2.5\">\n                <Input\n                  className=\"peer ps-9\"\n                  id=\"email\"\n                  placeholder=\"ephraim@blocks.so\"\n                  type=\"email\"\n                />\n                <div className=\"pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground/80 peer-disabled:opacity-50\">\n                  <Mail aria-hidden=\"true\" size={16} />\n                </div>\n              </div>\n            </div>\n\n            <div>\n              <div className=\"flex items-center justify-between\">\n                <Label htmlFor=\"password\">Password</Label>\n                <a className=\"text-primary text-sm hover:underline\" href=\"#\">\n                  Forgot Password?\n                </a>\n              </div>\n              <div className=\"relative mt-2.5\">\n                <Input\n                  className=\"ps-9 pe-9\"\n                  id=\"password\"\n                  placeholder=\"Enter your password\"\n                  type={isVisible ? 'text' : 'password'}\n                />\n                <div className=\"pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground/80 peer-disabled:opacity-50\">\n                  <Lock aria-hidden=\"true\" size={16} />\n                </div>\n                <button\n                  aria-controls=\"password\"\n                  aria-label={isVisible ? 'Hide password' : 'Show password'}\n                  aria-pressed={isVisible}\n                  className=\"absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\"\n                  onClick={toggleVisibility}\n                  type=\"button\"\n                >\n                  {isVisible ? (\n                    <EyeOff aria-hidden=\"true\" size={16} />\n                  ) : (\n                    <Eye aria-hidden=\"true\" size={16} />\n                  )}\n                </button>\n              </div>\n            </div>\n\n            <div className=\"flex items-center gap-2 pt-1\">\n              <Checkbox id=\"remember-me\" />\n              <Label htmlFor=\"remember-me\">Remember for 30 days</Label>\n            </div>\n          </div>\n\n          <Button className=\"w-full\">\n            Sign in\n            <ArrowRight className=\"h-4 w-4\" />\n          </Button>\n\n          <div className=\"text-center text-sm\">\n            No account?{' '}\n            <a className=\"font-medium text-primary hover:underline\" href=\"#\">\n              Create an account\n            </a>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-07/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/checkbox.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-07/shared/components/ui/checkbox.tsx",
          "content": "'use client';\n\nimport { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { CheckIcon } from 'lucide-react';\nimport { cn } from \"../../lib/utils\";\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      className={cn(\n        'peer after:-inset-x-3 after:-inset-y-2 relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input outline-none transition-colors after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"checkbox\"\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n        data-slot=\"checkbox-indicator\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n\nexport { Checkbox };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-07/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-07/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-07/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-07/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-07/login-07.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-07 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-07/login-07.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-07"
      }
    },
    {
      "name": "login-08",
      "type": "registry:block",
      "title": "Login with SSO",
      "description": "Login with SSO. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/login-08/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-08/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.2.0):\n- Replaced Next.js Link with standard anchors for framework-independent authentication UI.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-08.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-08/login-08.tsx",
          "content": "\"use client\";\n\nimport { EyeIcon, EyeOffIcon, Key } from \"lucide-react\";\nimport { type JSX, type SVGProps, useState } from \"react\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardFooter,\n  CardHeader,\n} from \"./shared/components/ui/card\";\nimport { Checkbox } from \"./shared/components/ui/checkbox\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\nconst Logo = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (\n  <svg\n    fill=\"currentColor\"\n    height=\"48\"\n    viewBox=\"0 0 40 48\"\n    width=\"40\"\n    {...props}\n  >\n    <clipPath id=\"a\">\n      <path d=\"m0 0h40v48h-40z\" />\n    </clipPath>\n    <g clipPath=\"url(#a)\">\n      <path d=\"m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z\" />\n      <path d=\"m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z\" />\n      <path d=\"m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z\" />\n      <path d=\"m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z\" />\n      <path d=\"m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z\" />\n      <path d=\"m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z\" />\n    </g>\n  </svg>\n);\n\nexport default function SignIn() {\n  const [isPasswordVisible, setIsPasswordVisible] = useState<boolean>(false);\n\n  const togglePasswordVisibility = () => setIsPasswordVisible((prev) => !prev);\n\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <Card className=\"mx-4 w-full max-w-md pb-0 shadow-2xs\">\n        <CardHeader className=\"mt-4 mb-2 space-y-1 text-center\">\n          <div className=\"flex justify-center\">\n            <Logo />\n          </div>\n          <div>\n            <h2 className=\"text-balance font-semibold text-2xl\">\n              Sign in to Acme\n            </h2>\n            <p className=\"text-pretty text-muted-foreground text-sm\">\n              Welcome back! Please enter your details.\n            </p>\n          </div>\n        </CardHeader>\n        <CardContent className=\"space-y-6\">\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"email\">Email address</Label>\n            <Input id=\"email\" placeholder=\"ephraim@blocks.so\" type=\"email\" />\n          </div>\n          <div className=\"space-y-0\">\n            <div className=\"mb-2 flex items-center justify-between\">\n              <Label htmlFor=\"password\">Password</Label>\n              <a className=\"text-primary text-sm hover:underline\" href=\"#\">\n                Reset password\n              </a>\n            </div>\n            <div className=\"relative\">\n              <Input\n                className=\"pe-9\"\n                id=\"password\"\n                placeholder=\"Enter your password\"\n                type={isPasswordVisible ? \"text\" : \"password\"}\n              />\n              <button\n                aria-controls=\"password\"\n                aria-label={\n                  isPasswordVisible ? \"Hide password\" : \"Show password\"\n                }\n                aria-pressed={isPasswordVisible}\n                className=\"absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\"\n                onClick={togglePasswordVisibility}\n                type=\"button\"\n              >\n                {isPasswordVisible ? (\n                  <EyeOffIcon aria-hidden=\"true\" size={16} />\n                ) : (\n                  <EyeIcon aria-hidden=\"true\" size={16} />\n                )}\n              </button>\n            </div>\n          </div>\n          <div className=\"flex items-center space-x-2\">\n            <Checkbox defaultChecked id=\"remember\" />\n            <Label className=\"font-normal text-sm\" htmlFor=\"remember\">\n              Remember me\n            </Label>\n          </div>\n\n          <div className=\"space-y-2\">\n            <Button className=\"w-full\" type=\"submit\">\n              Sign In\n            </Button>\n            <Button className=\"w-full\" type=\"button\" variant=\"outline\">\n              <Key className=\"mr-2 h-4 w-4\" />\n              Single sign-on (SSO)\n            </Button>\n          </div>\n        </CardContent>\n        <CardFooter className=\"flex justify-center border-t py-4!\">\n          <p className=\"text-pretty text-center text-muted-foreground text-sm\">\n            New to Acme?{\" \"}\n            <a className=\"text-primary hover:underline\" href=\"#\">\n              Sign up\n            </a>\n          </p>\n        </CardFooter>\n      </Card>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-08/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-08/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/checkbox.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-08/shared/components/ui/checkbox.tsx",
          "content": "'use client';\n\nimport { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { CheckIcon } from 'lucide-react';\nimport { cn } from \"../../lib/utils\";\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      className={cn(\n        'peer after:-inset-x-3 after:-inset-y-2 relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input outline-none transition-colors after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"checkbox\"\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n        data-slot=\"checkbox-indicator\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n\nexport { Checkbox };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-08/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-08/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-08/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-08/login-08.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-08 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.2.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f",
          "modifications": [
            "Replaced Next.js Link with standard anchors for framework-independent authentication UI."
          ]
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-08/login-08.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-08"
      }
    },
    {
      "name": "login-09",
      "type": "registry:block",
      "title": "Login Signup with Role Selection",
      "description": "Login Signup with Role Selection. Authentication forms and account entry screens for SaaS applications.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/login-09/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/login-09/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.2.0):\n- Replaced Next.js Link with standard anchors for framework-independent authentication UI.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/login/login-09.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-09/login-09.tsx",
          "content": "\"use client\";\n\nimport { BarChart, Code, Eye, EyeOff, User } from \"lucide-react\";\nimport { type JSX, type SVGProps, useState } from \"react\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardFooter,\n  CardHeader,\n} from \"./shared/components/ui/card\";\nimport { Checkbox } from \"./shared/components/ui/checkbox\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\n\nconst Logo = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (\n  <svg\n    fill=\"currentColor\"\n    height=\"48\"\n    viewBox=\"0 0 40 48\"\n    width=\"40\"\n    {...props}\n  >\n    <clipPath id=\"a\">\n      <path d=\"m0 0h40v48h-40z\" />\n    </clipPath>\n    <g clipPath=\"url(#a)\">\n      <path d=\"m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z\" />\n      <path d=\"m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z\" />\n      <path d=\"m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z\" />\n      <path d=\"m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z\" />\n      <path d=\"m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z\" />\n      <path d=\"m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z\" />\n    </g>\n  </svg>\n);\n\nexport default function SignupForm() {\n  const [showPassword, setShowPassword] = useState(false);\n\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center\">\n      <div className=\"w-full max-w-md\">\n        <Card className=\"border-none pb-0 shadow-lg\">\n          <CardHeader className=\"flex flex-col items-center space-y-1.5 pt-6 pb-4\">\n            <Logo className=\"h-12 w-12\" />\n            <div className=\"flex flex-col items-center space-y-0.5\">\n              <h2 className=\"text-balance font-semibold text-2xl text-foreground\">\n                Create an account\n              </h2>\n              <p className=\"text-pretty text-muted-foreground\">\n                Welcome! Create an account to get started.\n              </p>\n            </div>\n          </CardHeader>\n          <CardContent className=\"space-y-6 px-8\">\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"role\">Role</Label>\n              <Select\n                defaultValue=\"designer\"\n                items={{\n                  designer: \"Product Designer\",\n                  developer: \"Developer\",\n                  manager: \"Product Manager\",\n                }}\n              >\n                <SelectTrigger\n                  className=\"[&>span]:flex [&>span]:items-center [&>span]:gap-2 [&>span_svg]:shrink-0\"\n                  id=\"role\"\n                >\n                  <SelectValue placeholder=\"Select role\" />\n                </SelectTrigger>\n                <SelectContent className=\"[&_*[role=option]>span>svg]:shrink-0 [&_*[role=option]>span]:start-auto [&_*[role=option]>span]:end-2 [&_*[role=option]>span]:flex [&_*[role=option]>span]:items-center [&_*[role=option]>span]:gap-2 [&_*[role=option]]:ps-2 [&_*[role=option]]:pe-8\">\n                  <SelectItem value=\"designer\">\n                    <User aria-hidden=\"true\" size={16} />\n                    <span className=\"truncate\">Product Designer</span>\n                  </SelectItem>\n                  <SelectItem value=\"developer\">\n                    <Code aria-hidden=\"true\" size={16} />\n                    <span className=\"truncate\">Developer</span>\n                  </SelectItem>\n                  <SelectItem value=\"manager\">\n                    <BarChart aria-hidden=\"true\" size={16} />\n                    <span className=\"truncate\">Product Manager</span>\n                  </SelectItem>\n                </SelectContent>\n              </Select>\n            </div>\n\n            <div className=\"grid grid-cols-2 gap-4\">\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"firstName\">First name</Label>\n                <Input id=\"firstName\" />\n              </div>\n              <div className=\"space-y-2\">\n                <Label htmlFor=\"lastName\">Last name</Label>\n                <Input id=\"lastName\" />\n              </div>\n            </div>\n\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"username\">Username</Label>\n              <Input id=\"username\" />\n            </div>\n\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"email\">Email address</Label>\n              <Input id=\"email\" type=\"email\" />\n            </div>\n\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"password\">Password</Label>\n              <div className=\"relative\">\n                <Input\n                  className=\"pr-10\"\n                  id=\"password\"\n                  type={showPassword ? \"text\" : \"password\"}\n                />\n                <Button\n                  className=\"absolute top-0 right-0 h-full px-3 text-muted-foreground hover:bg-transparent\"\n                  onClick={() => setShowPassword(!showPassword)}\n                  size=\"icon\"\n                  type=\"button\"\n                  variant=\"ghost\"\n                >\n                  {showPassword ? (\n                    <EyeOff className=\"h-4 w-4\" />\n                  ) : (\n                    <Eye className=\"h-4 w-4\" />\n                  )}\n                </Button>\n              </div>\n            </div>\n\n            <div className=\"flex items-center space-x-2\">\n              <Checkbox id=\"terms\" />\n              <label className=\"text-muted-foreground text-sm\" htmlFor=\"terms\">\n                I agree to the{\" \"}\n                <a className=\"text-primary hover:underline\" href=\"#\">\n                  Terms\n                </a>{\" \"}\n                and{\" \"}\n                <a className=\"text-primary hover:underline\" href=\"#\">\n                  Conditions\n                </a>\n              </label>\n            </div>\n\n            <Button className=\"w-full bg-primary text-primary-foreground\">\n              Create free account\n            </Button>\n          </CardContent>\n          <CardFooter className=\"flex justify-center border-t py-4!\">\n            <p className=\"text-pretty text-center text-muted-foreground text-sm\">\n              Already have an account?{\" \"}\n              <a className=\"text-primary hover:underline\" href=\"#\">\n                Sign in\n              </a>\n            </p>\n          </CardFooter>\n        </Card>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-09/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-09/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/checkbox.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-09/shared/components/ui/checkbox.tsx",
          "content": "'use client';\n\nimport { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { CheckIcon } from 'lucide-react';\nimport { cn } from \"../../lib/utils\";\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      className={cn(\n        'peer after:-inset-x-3 after:-inset-y-2 relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input outline-none transition-colors after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"checkbox\"\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n        data-slot=\"checkbox-indicator\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n\nexport { Checkbox };\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-09/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-09/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/login-09/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/login-09/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "login",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/login-09/login-09.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/login/login-09 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.2.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f",
          "modifications": [
            "Replaced Next.js Link with standard anchors for framework-independent authentication UI."
          ]
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "login",
          "signup",
          "authentication",
          "account",
          "登录",
          "注册",
          "认证",
          "账号"
        ],
        "contentGuidelines": [
          "Connect your authentication provider before using in a live product.",
          "Test password errors, long email addresses, and keyboard submission."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/login-09/login-09.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/login-09"
      }
    },
    {
      "name": "logo-cloud-01",
      "type": "registry:block",
      "title": "Supported Format Cloud",
      "description": "A quiet format compatibility strip that introduces the file types and workflow capabilities supported by your product.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/logo-cloud-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/logo-cloud-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/marketing/logo-cloud-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/logo-cloud-01/logo-cloud-01.tsx",
          "content": "import { Braces, Clapperboard, FileVideo, Film, Video } from \"lucide-react\";\n\nexport type LogoCloud01Props = {\n  eyebrow?: string;\n  formats?: { name: string; detail: string }[];\n};\n\n/** Format compatibility strip; it does not imply customer endorsements. */\nexport default function LogoCloud01({\n  eyebrow = \"Familiar formats. One clear workflow.\",\n  formats = [\n    { name: \"MP4\", detail: \"Video input\" },\n    { name: \"MOV\", detail: \"Video input\" },\n    { name: \"WebM\", detail: \"Video input\" },\n    { name: \"MKV\", detail: \"Video input\" },\n    { name: \"SVG\", detail: \"Demo preview\" },\n  ],\n}: LogoCloud01Props) {\n  const icons = [Film, Clapperboard, Video, FileVideo, Braces];\n  return (\n    <section\n      aria-label=\"Demo input and preview formats\"\n      className=\"w-full border-y border-zinc-200/70 bg-zinc-50/70 py-9 text-zinc-950 dark:border-zinc-800 dark:bg-zinc-900/30 dark:text-white\"\n    >\n      <div className=\"mx-auto max-w-6xl px-6 lg:px-8\">\n        <p className=\"text-center text-xs text-zinc-400\">{eyebrow}</p>\n        <div className=\"mt-7 flex flex-wrap justify-center gap-x-10 gap-y-6 sm:justify-between\">\n          {formats.map((format, index) => {\n            const Icon = icons[index % icons.length];\n            return (\n              <div className=\"flex items-center gap-3\" key={format.name}>\n                <Icon aria-hidden=\"true\" className=\"size-5 text-zinc-400\" />\n                <div>\n                  <p className=\"text-sm font-medium text-zinc-600 dark:text-zinc-300\">\n                    {format.name}\n                  </p>\n                  <p className=\"mt-0.5 font-mono text-[9px] tracking-wide text-zinc-400\">\n                    {format.detail}\n                  </p>\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        }
      ],
      "categories": [
        "marketing",
        "marketing",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/logo-cloud-01/logo-cloud-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/marketing/logo-cloud-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "marketing",
        "featured": true,
        "tags": [
          "formats",
          "compatibility",
          "workflow",
          "files",
          "logo cloud",
          "格式",
          "兼容",
          "流程",
          "文件",
          "支持"
        ],
        "contentGuidelines": [
          "List only file formats and workflow capabilities your product actually supports.",
          "This strip describes format compatibility and does not imply customer endorsements."
        ],
        "recommendedWith": [
          "hero-01",
          "features-01"
        ],
        "entry": "@components/uiblocks/logo-cloud-01/logo-cloud-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/logo-cloud-01"
      }
    },
    {
      "name": "navigation-01",
      "type": "registry:block",
      "title": "Product Navigation",
      "description": "A compact product header with responsive navigation and a clear workspace call to action.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/navigation-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/navigation-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/marketing/navigation-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/navigation-01/navigation-01.tsx",
          "content": "\"use client\";\n\nimport { ArrowUpRight, Layers2, Menu, X } from \"lucide-react\";\nimport { useId, useState } from \"react\";\n\nexport type Navigation01Props = {\n  brand?: string;\n  homeHref?: string;\n  links?: { label: string; href: string }[];\n  actionLabel?: string;\n  actionHref?: string;\n};\n\n/** A responsive product navigation. Supply real section URLs through links. */\nexport default function Navigation01({\n  brand = \"Frame\",\n  homeHref = \"/preview/recipes/tool-landing#frame-top\",\n  links = [\n    { label: \"Features\", href: \"/preview/recipes/tool-landing#features\" },\n    { label: \"Pricing\", href: \"/preview/recipes/tool-landing#pricing\" },\n    { label: \"Questions\", href: \"/preview/recipes/tool-landing#faq\" },\n  ],\n  actionLabel = \"Explore the demo\",\n  actionHref = \"/preview/recipes/tool-workspace\",\n}: Navigation01Props) {\n  const [open, setOpen] = useState(false);\n  const menuId = useId();\n\n  return (\n    <header\n      className=\"w-full border-b border-zinc-200/80 bg-white text-zinc-950 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n      id=\"frame-top\"\n    >\n      <div className=\"mx-auto flex h-20 max-w-6xl items-center justify-between gap-6 px-6 lg:px-8\">\n        <a\n          className=\"inline-flex items-center gap-2.5 rounded-md text-xl font-semibold tracking-tight outline-offset-4 focus-visible:outline-2 focus-visible:outline-violet-500\"\n          href={homeHref}\n        >\n          <span className=\"grid size-8 place-items-center rounded-lg bg-violet-600 text-white\">\n            <Layers2 aria-hidden=\"true\" className=\"size-4.5\" />\n          </span>\n          {brand}\n        </a>\n        <nav\n          aria-label=\"Product navigation\"\n          className=\"hidden items-center gap-8 md:flex\"\n        >\n          {links.map((link) => (\n            <a\n              className=\"rounded text-sm text-zinc-500 transition-colors hover:text-zinc-950 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-zinc-400 dark:hover:text-white\"\n              href={link.href}\n              key={link.href}\n            >\n              {link.label}\n            </a>\n          ))}\n        </nav>\n        <div className=\"flex items-center gap-3\">\n          <a\n            className=\"hidden items-center gap-2 rounded-lg bg-zinc-950 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-zinc-800 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-violet-500 sm:inline-flex dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200\"\n            href={actionHref}\n          >\n            {actionLabel}\n            <ArrowUpRight aria-hidden=\"true\" className=\"size-4\" />\n          </a>\n          <button\n            aria-controls={menuId}\n            aria-expanded={open}\n            aria-label={open ? \"Close product menu\" : \"Open product menu\"}\n            className=\"grid size-10 place-items-center rounded-lg border border-zinc-200 hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-violet-500 md:hidden dark:border-zinc-700 dark:hover:bg-zinc-900\"\n            onClick={() => setOpen(!open)}\n            type=\"button\"\n          >\n            {open ? (\n              <X aria-hidden=\"true\" className=\"size-5\" />\n            ) : (\n              <Menu aria-hidden=\"true\" className=\"size-5\" />\n            )}\n          </button>\n        </div>\n      </div>\n      {open && (\n        <nav\n          aria-label=\"Mobile product navigation\"\n          className=\"border-t border-zinc-200 px-6 py-4 md:hidden dark:border-zinc-800\"\n          id={menuId}\n        >\n          {links.map((link) => (\n            <a\n              className=\"block rounded-md px-2 py-3 text-sm text-zinc-600 hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-zinc-300 dark:hover:bg-zinc-900\"\n              href={link.href}\n              key={link.href}\n              onClick={() => setOpen(false)}\n            >\n              {link.label}\n            </a>\n          ))}\n          <a\n            className=\"mt-2 inline-flex items-center gap-2 rounded-md px-2 py-3 text-sm font-semibold text-violet-600 dark:text-violet-400\"\n            href={actionHref}\n            onClick={() => setOpen(false)}\n          >\n            {actionLabel}\n            <ArrowUpRight aria-hidden=\"true\" className=\"size-4\" />\n          </a>\n        </nav>\n      )}\n    </header>\n  );\n}\n"
        }
      ],
      "categories": [
        "marketing",
        "marketing",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/navigation-01/navigation-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/marketing/navigation-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "marketing",
        "featured": true,
        "tags": [
          "navigation",
          "header",
          "menu",
          "navbar",
          "landing",
          "导航",
          "页头",
          "菜单",
          "官网"
        ],
        "contentGuidelines": [
          "Use a short product name and three to four navigation links.",
          "Set the primary link to your real product entry route."
        ],
        "recommendedWith": [
          "hero-01",
          "footer-01"
        ],
        "entry": "@components/uiblocks/navigation-01/navigation-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/navigation-01"
      }
    },
    {
      "name": "onboarding-01",
      "type": "registry:block",
      "title": "Onboarding",
      "description": "Onboarding. Guided setup, checklists, timelines, and onboarding progress for new users.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/onboarding-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/onboarding-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/onboarding/onboarding-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-01/onboarding-01.tsx",
          "content": "'use client';\n\nimport {\n  IconArchive,\n  IconChevronRight,\n  IconCircleCheckFilled,\n  IconCircleDashed,\n  IconDots,\n  IconMail,\n} from '@tabler/icons-react';\nimport { useState } from 'react';\nimport { Button, buttonVariants } from \"./shared/components/ui/button\";\nimport { Collapsible, CollapsibleContent } from \"./shared/components/ui/collapsible\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst steps = [\n  {\n    id: 'profile',\n    title: 'Complete your profile',\n    description:\n      'Add your name, photo, and role so your team knows who you are.',\n    completed: true,\n    actionLabel: 'Edit profile',\n    actionHref: '#',\n  },\n  {\n    id: 'workspace',\n    title: 'Set up your workspace',\n    description:\n      'Customize your workspace with a name, icon, and default settings.',\n    completed: false,\n    actionLabel: 'Configure workspace',\n    actionHref: '#',\n  },\n  {\n    id: 'invite',\n    title: 'Invite your team',\n    description:\n      'Bring your teammates on board so you can collaborate in real time.',\n    completed: false,\n    actionLabel: 'Send invites',\n    actionHref: '#',\n  },\n  {\n    id: 'integrations',\n    title: 'Connect integrations',\n    description:\n      'Link your favorite tools like Slack, GitHub, and Linear to streamline workflows.',\n    completed: false,\n    actionLabel: 'Browse integrations',\n    actionHref: '#',\n  },\n  {\n    id: 'workflow',\n    title: 'Create your first workflow',\n    description:\n      'Automate a repetitive task with a simple drag-and-drop workflow builder.',\n    completed: false,\n    actionLabel: 'Build workflow',\n    actionHref: '#',\n  },\n  {\n    id: 'notifications',\n    title: 'Set up notifications',\n    description:\n      'Choose how and when you want to be notified about updates and mentions.',\n    completed: false,\n    actionLabel: 'Manage notifications',\n    actionHref: '#',\n  },\n];\n\ninterface OnboardingStep {\n  id: string;\n  title: string;\n  description: string;\n  completed: boolean;\n  actionLabel: string;\n  actionHref: string;\n}\n\nfunction CircularProgress({\n  completed,\n  total,\n}: {\n  completed: number;\n  total: number;\n}) {\n  const progress = total > 0 ? ((total - completed) / total) * 100 : 0;\n  const strokeDashoffset = 100 - progress;\n\n  return (\n    <svg className=\"-rotate-90\" height=\"14\" viewBox=\"0 0 14 14\" width=\"14\">\n      <circle\n        className=\"stroke-muted\"\n        cx=\"7\"\n        cy=\"7\"\n        fill=\"none\"\n        pathLength=\"100\"\n        r=\"6\"\n        strokeWidth=\"2\"\n      />\n      <circle\n        className=\"stroke-primary\"\n        cx=\"7\"\n        cy=\"7\"\n        fill=\"none\"\n        pathLength=\"100\"\n        r=\"6\"\n        strokeDasharray=\"100\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"2\"\n        style={{ strokeDashoffset }}\n      />\n    </svg>\n  );\n}\n\nfunction StepIndicator({ completed }: { completed: boolean }) {\n  if (completed) {\n    return (\n      <IconCircleCheckFilled\n        aria-hidden=\"true\"\n        className=\"mt-1 size-4.5 shrink-0 text-primary\"\n      />\n    );\n  }\n  return (\n    <IconCircleDashed\n      aria-hidden=\"true\"\n      className=\"mt-1 size-5 shrink-0 stroke-muted-foreground/40\"\n      strokeWidth={2}\n    />\n  );\n}\n\nexport function Onboarding01() {\n  const [currentSteps, setCurrentSteps] = useState<OnboardingStep[]>(steps);\n  const [openStepId, setOpenStepId] = useState<string | null>(() => {\n    const firstIncomplete = steps.find((s) => !s.completed);\n    return firstIncomplete?.id ?? steps[0]?.id ?? null;\n  });\n  const [dismissed, setDismissed] = useState(false);\n\n  const completedCount = currentSteps.filter((s) => s.completed).length;\n  const remainingCount = currentSteps.length - completedCount;\n\n  const handleStepClick = (stepId: string) => {\n    setOpenStepId(openStepId === stepId ? null : stepId);\n  };\n\n  const handleStepAction = (step: OnboardingStep) => {\n    const updated = currentSteps.map((s) =>\n      s.id === step.id ? { ...s, completed: true } : s\n    );\n    setCurrentSteps(updated);\n    const nextIncomplete = updated.find((s) => !s.completed);\n    setOpenStepId(nextIncomplete?.id ?? null);\n  };\n\n  if (dismissed) {\n    return (\n      <div className=\"flex min-h-dvh items-center justify-center bg-background p-4\">\n        <div className=\"text-center\">\n          <p className=\"text-pretty text-muted-foreground\">\n            Checklist dismissed\n          </p>\n          <button\n            className=\"mt-2 text-primary text-sm underline\"\n            onClick={() => setDismissed(false)}\n          >\n            Show again\n          </button>\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center bg-background p-4\">\n      <div className=\"w-full max-w-lg\">\n        <div className=\"w-md rounded-lg border bg-card p-4 text-card-foreground shadow-xs\">\n          <div className=\"mr-2 mb-4 flex flex-col justify-between sm:flex-row sm:items-center\">\n            <h3 className=\"ml-2 text-balance font-semibold text-foreground\">\n              Get started with Acme\n            </h3>\n            <div className=\"mt-2 flex items-center justify-end sm:mt-0\">\n              <CircularProgress\n                completed={remainingCount}\n                total={currentSteps.length}\n              />\n              <div className=\"mr-3 ml-1.5 text-muted-foreground text-sm\">\n                <span className=\"font-medium text-foreground\">\n                  {completedCount}\n                </span>\n                {' / '}\n                <span className=\"font-medium text-foreground\">\n                  {currentSteps.length}\n                </span>{' '}\n                completed\n              </div>\n              <DropdownMenu>\n                <DropdownMenuTrigger\n                  render={\n                    <Button className=\"h-6 w-6\" size=\"icon\" variant=\"ghost\" />\n                  }\n                >\n                  <IconDots aria-hidden=\"true\" className=\"h-4 w-4 shrink-0\" />\n                  <span className=\"sr-only\">Options</span>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent align=\"end\" className=\"w-40\">\n                  <DropdownMenuItem onClick={() => setDismissed(true)}>\n                    <IconArchive\n                      aria-hidden=\"true\"\n                      className=\"mr-2 h-4 w-4 shrink-0\"\n                    />\n                    Dismiss\n                  </DropdownMenuItem>\n                  <DropdownMenuItem\n                    onClick={() =>\n                      window.open('mailto:support@acme.com?subject=Feedback')\n                    }\n                  >\n                    <IconMail\n                      aria-hidden=\"true\"\n                      className=\"mr-2 h-4 w-4 shrink-0\"\n                    />\n                    Give feedback\n                  </DropdownMenuItem>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </div>\n          </div>\n\n          <div className=\"space-y-0\">\n            {currentSteps.map((step, index) => {\n              const isOpen = openStepId === step.id;\n              const isFirst = index === 0;\n              const prevStep = currentSteps[index - 1];\n              const isPrevOpen = prevStep && openStepId === prevStep.id;\n\n              const showBorderTop = !(isFirst || isOpen || isPrevOpen);\n\n              return (\n                <div\n                  className={cn(\n                    'group',\n                    isOpen && 'rounded-lg',\n                    showBorderTop && 'border-border border-t'\n                  )}\n                  key={step.id}\n                >\n                  <div\n                    className={cn(\n                      'block w-full cursor-pointer text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n                      isOpen && 'rounded-lg'\n                    )}\n                    onClick={() => handleStepClick(step.id)}\n                    onKeyDown={(e) => {\n                      if (e.key === 'Enter' || e.key === ' ') {\n                        e.preventDefault();\n                        handleStepClick(step.id);\n                      }\n                    }}\n                    role=\"button\"\n                    tabIndex={0}\n                  >\n                    <div\n                      className={cn(\n                        'relative overflow-hidden rounded-lg transition-colors',\n                        isOpen && 'border border-border bg-muted'\n                      )}\n                    >\n                      <div className=\"relative flex items-center justify-between gap-3 py-3 pr-2 pl-4\">\n                        <div className=\"flex w-full gap-3\">\n                          <div className=\"shrink-0\">\n                            <StepIndicator completed={step.completed} />\n                          </div>\n                          <div className=\"mt-0.5 grow\">\n                            <h4\n                              className={cn(\n                                'font-semibold',\n                                step.completed\n                                  ? 'text-primary'\n                                  : 'text-foreground'\n                              )}\n                            >\n                              {step.title}\n                            </h4>\n                            <Collapsible open={isOpen}>\n                              <CollapsibleContent>\n                                <p className=\"mt-2 text-pretty text-muted-foreground text-sm sm:max-w-64 md:max-w-xs\">\n                                  {step.description}\n                                </p>\n                                <a\n                                  className={cn(\n                                    buttonVariants({ size: 'sm' }),\n                                    'mt-3'\n                                  )}\n                                  href={step.actionHref}\n                                  onClick={(e) => {\n                                    e.stopPropagation();\n                                    handleStepAction(step);\n                                  }}\n                                >\n                                  {step.actionLabel}\n                                </a>\n                              </CollapsibleContent>\n                            </Collapsible>\n                          </div>\n                        </div>\n                        {!isOpen && (\n                          <IconChevronRight\n                            aria-hidden=\"true\"\n                            className=\"h-4 w-4 shrink-0 text-muted-foreground\"\n                          />\n                        )}\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              );\n            })}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/collapsible.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-01/shared/components/ui/collapsible.tsx",
          "content": "'use client';\n\nimport { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible';\n\nfunction Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {\n  return (\n    <CollapsiblePrimitive.Trigger data-slot=\"collapsible-trigger\" {...props} />\n  );\n}\n\nfunction CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {\n  return (\n    <CollapsiblePrimitive.Panel data-slot=\"collapsible-content\" {...props} />\n  );\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-01/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "onboarding",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Onboarding01 from @components/uiblocks/onboarding-01/onboarding-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/onboarding/onboarding-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "onboarding",
          "setup",
          "steps",
          "progress",
          "引导",
          "入门",
          "步骤",
          "进度"
        ],
        "contentGuidelines": [
          "Give each step a concrete outcome and a way to continue.",
          "Persist completed setup steps in your application."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/onboarding-01/onboarding-01.tsx",
        "exportName": "Onboarding01",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/onboarding-01"
      }
    },
    {
      "name": "onboarding-02",
      "type": "registry:block",
      "title": "Onboarding Workspace Setup",
      "description": "Onboarding Workspace Setup. Guided setup, checklists, timelines, and onboarding progress for new users.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/onboarding-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/onboarding-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/onboarding/onboarding-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-02/onboarding-02.tsx",
          "content": "'use client';\n\nimport type { Icon } from '@tabler/icons-react';\nimport {\n  IconCircleCheckFilled,\n  IconFolder,\n  IconPlug,\n  IconRocket,\n  IconUsers,\n} from '@tabler/icons-react';\nimport { useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { cn } from \"./shared/lib/utils\";\n\ninterface StepData {\n  title: string;\n  description: string;\n  icon: Icon;\n  actionLabel: string;\n}\n\nconst steps: StepData[] = [\n  {\n    title: 'Create your workspace',\n    description:\n      'Name your workspace and pick a URL. This is where your team will collaborate on everything.',\n    icon: IconRocket,\n    actionLabel: 'Get started',\n  },\n  {\n    title: 'Invite your team',\n    description:\n      'Add teammates by email so everyone can collaborate in real time across projects.',\n    icon: IconUsers,\n    actionLabel: 'Send invites',\n  },\n  {\n    title: 'Set up your first project',\n    description:\n      'Create a project to organize tasks, docs, and milestones all in one place.',\n    icon: IconFolder,\n    actionLabel: 'Create project',\n  },\n  {\n    title: 'Connect your tools',\n    description:\n      'Link Slack, GitHub, or Figma to keep your workflow in sync automatically.',\n    icon: IconPlug,\n    actionLabel: 'Browse integrations',\n  },\n];\n\nfunction getIconColor(isCompleted: boolean, isActive: boolean) {\n  if (isCompleted) {\n    return 'text-muted-foreground/30';\n  }\n  if (isActive) {\n    return 'text-primary';\n  }\n  return 'text-muted-foreground/40';\n}\n\nfunction getTitleClass(isCompleted: boolean, isActive: boolean) {\n  if (isCompleted) {\n    return 'text-muted-foreground/50 line-through';\n  }\n  if (isActive) {\n    return 'text-foreground';\n  }\n  return 'text-muted-foreground';\n}\n\nfunction StepIndicator({\n  index,\n  isCompleted,\n  isActive,\n}: {\n  index: number;\n  isCompleted: boolean;\n  isActive: boolean;\n}) {\n  if (isCompleted) {\n    return (\n      <div className=\"flex size-7 items-center justify-center\">\n        <IconCircleCheckFilled\n          aria-hidden=\"true\"\n          className=\"size-7 text-emerald-500\"\n        />\n      </div>\n    );\n  }\n  return (\n    <div\n      className={cn(\n        'flex size-7 items-center justify-center rounded-full font-semibold text-xs',\n        isActive\n          ? 'bg-primary text-primary-foreground'\n          : 'bg-muted text-muted-foreground'\n      )}\n    >\n      {index + 1}\n    </div>\n  );\n}\n\nfunction StepCard({\n  step,\n  index,\n  isCompleted,\n  isActive,\n  onComplete,\n}: {\n  step: StepData;\n  index: number;\n  isCompleted: boolean;\n  isActive: boolean;\n  onComplete: () => void;\n}) {\n  const StepIcon = step.icon;\n\n  return (\n    <div\n      className={cn(\n        'rounded-lg border p-4 transition-colors',\n        isActive && 'border-primary/30 bg-muted/50',\n        !isActive && 'border-border bg-background'\n      )}\n    >\n      <div className=\"flex gap-3\">\n        <div className=\"mt-0.5 shrink-0\">\n          <StepIndicator\n            index={index}\n            isActive={isActive}\n            isCompleted={isCompleted}\n          />\n        </div>\n        <div className=\"min-w-0 flex-1\">\n          <div className=\"flex items-start gap-3\">\n            <div className=\"min-w-0 flex-1\">\n              <p\n                className={cn(\n                  'font-medium leading-6',\n                  getTitleClass(isCompleted, isActive)\n                )}\n              >\n                {step.title}\n              </p>\n              <p\n                className={cn(\n                  'mt-0.5 text-sm leading-5',\n                  isActive\n                    ? 'text-muted-foreground'\n                    : 'text-muted-foreground/60'\n                )}\n              >\n                {step.description}\n              </p>\n              {isActive && (\n                <Button className=\"mt-3\" onClick={onComplete} size=\"sm\">\n                  <StepIcon\n                    aria-hidden=\"true\"\n                    className=\"-ml-0.5 size-4 shrink-0\"\n                  />\n                  {step.actionLabel}\n                </Button>\n              )}\n            </div>\n            <StepIcon\n              aria-hidden=\"true\"\n              className={cn(\n                'mt-0.5 size-5 shrink-0',\n                getIconColor(isCompleted, isActive)\n              )}\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport function Onboarding02() {\n  const [completedSteps, setCompletedSteps] = useState<Set<number>>(\n    () => new Set([0])\n  );\n\n  const currentStep = steps.findIndex((_, i) => !completedSteps.has(i));\n  const completedCount = completedSteps.size;\n  const allDone = completedCount === steps.length;\n\n  const handleComplete = (index: number) => {\n    setCompletedSteps((prev) => {\n      const next = new Set(prev);\n      next.add(index);\n      return next;\n    });\n  };\n\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center bg-background p-4\">\n      <div className=\"w-full max-w-xl\">\n        <div className=\"mb-6\">\n          <h3 className=\"font-semibold text-foreground text-lg\">\n            Set up your workspace\n          </h3>\n          <p className=\"mt-1 text-muted-foreground text-sm\">\n            Complete these steps to get your team up and running\n          </p>\n          <div className=\"mt-4\">\n            <div className=\"flex items-center justify-between text-sm\">\n              <span className=\"text-muted-foreground\">\n                {allDone ? (\n                  <span className=\"font-medium text-emerald-600\">\n                    All done 🎉\n                  </span>\n                ) : (\n                  <>\n                    <span className=\"font-medium text-foreground\">\n                      {completedCount}\n                    </span>{' '}\n                    of {steps.length} completed\n                  </>\n                )}\n              </span>\n            </div>\n            <div className=\"mt-2 h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n              <div\n                className=\"h-full rounded-full bg-emerald-500 transition-all duration-500\"\n                style={{\n                  width: `${(completedCount / steps.length) * 100}%`,\n                }}\n              />\n            </div>\n          </div>\n        </div>\n\n        <div className=\"space-y-3\">\n          {steps.map((step, index) => (\n            <StepCard\n              index={index}\n              isActive={index === currentStep}\n              isCompleted={completedSteps.has(index)}\n              key={step.title}\n              onComplete={() => handleComplete(index)}\n              step={step}\n            />\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "onboarding",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Onboarding02 from @components/uiblocks/onboarding-02/onboarding-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/onboarding/onboarding-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "onboarding",
          "setup",
          "steps",
          "progress",
          "引导",
          "入门",
          "步骤",
          "进度"
        ],
        "contentGuidelines": [
          "Give each step a concrete outcome and a way to continue.",
          "Persist completed setup steps in your application."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/onboarding-02/onboarding-02.tsx",
        "exportName": "Onboarding02",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/onboarding-02"
      }
    },
    {
      "name": "onboarding-03",
      "type": "registry:block",
      "title": "Onboarding Steps with Progress",
      "description": "Onboarding Steps with Progress. Guided setup, checklists, timelines, and onboarding progress for new users.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/onboarding-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/onboarding-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/onboarding/onboarding-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-03/onboarding-03.tsx",
          "content": "'use client';\n\nimport { IconCircleCheckFilled } from '@tabler/icons-react';\nimport { useState } from 'react';\nimport { Progress } from \"./shared/components/ui/progress\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst steps = [\n  {\n    id: '1.',\n    title: 'Create your store',\n    description:\n      \"Choose a name, pick a subdomain, and configure your store's basic settings.\",\n  },\n  {\n    id: '2.',\n    title: 'Add your products',\n    description:\n      'Upload your catalog with photos, pricing, and inventory details.',\n  },\n  {\n    id: '3.',\n    title: 'Set up payments',\n    description:\n      'Connect Stripe or PayPal to start accepting orders from customers.',\n  },\n  {\n    id: '4.',\n    title: 'Launch your store',\n    description: 'Review everything and publish your storefront to the world.',\n  },\n];\n\nexport function Onboarding03() {\n  const [activeStep, setActiveStep] = useState(0);\n\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center bg-background p-4\">\n      <div className=\"sm:mx-auto sm:max-w-lg\">\n        <h3 className=\"font-semibold text-foreground text-lg\">Store setup</h3>\n        <p className=\"mt-1 text-muted-foreground text-sm leading-6\">\n          Complete each step to launch your online store.\n        </p>\n        <div className=\"mt-4 flex items-center justify-end space-x-4\">\n          <span className=\"text-muted-foreground text-sm\">\n            Step {activeStep + 1}/{steps.length}\n          </span>\n          <Progress\n            className=\"w-32\"\n            value={(activeStep / steps.length) * 100}\n          />\n        </div>\n        <ul className=\"mt-4 space-y-4\">\n          {steps.map((step, index) => (\n            <li key={step.id}>\n              <button\n                className={cn(\n                  'relative w-full cursor-pointer rounded-lg border bg-card p-4 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n                  index === activeStep\n                    ? 'border-foreground/20'\n                    : 'border-border'\n                )}\n                onClick={() => setActiveStep(index)}\n                type=\"button\"\n              >\n                <div className=\"flex items-start space-x-3\">\n                  {index < activeStep ? (\n                    <IconCircleCheckFilled\n                      aria-hidden={true}\n                      className=\"size-6 shrink-0 text-foreground\"\n                    />\n                  ) : (\n                    <span\n                      aria-hidden={true}\n                      className=\"flex size-6 items-center justify-center font-medium text-muted-foreground\"\n                    >\n                      {step.id}\n                    </span>\n                  )}\n                  <div>\n                    <h3\n                      className={cn(\n                        'font-medium',\n                        index < activeStep\n                          ? 'text-muted-foreground line-through'\n                          : 'text-foreground'\n                      )}\n                    >\n                      {step.title}\n                    </h3>\n                    <p className=\"mt-1 text-muted-foreground text-sm leading-6\">\n                      {step.description}\n                    </p>\n                  </div>\n                </div>\n              </button>\n            </li>\n          ))}\n        </ul>\n        <div className=\"mt-6 rounded-lg bg-muted p-4\">\n          <h4 className=\"font-medium text-foreground text-sm\">Need help?</h4>\n          <p className=\"mt-1 text-muted-foreground text-sm\">\n            Connect with a member of our team at{' '}\n            <a\n              className=\"font-medium text-primary\"\n              href=\"mailto:help@example.com\"\n            >\n              help@example.com\n            </a>\n            .\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/progress.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-03/shared/components/ui/progress.tsx",
          "content": "'use client';\n\nimport { Progress as ProgressPrimitive } from '@base-ui/react/progress';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Progress({\n  className,\n  children,\n  value,\n  ...props\n}: ProgressPrimitive.Root.Props) {\n  return (\n    <ProgressPrimitive.Root\n      className={cn('flex flex-wrap gap-3', className)}\n      data-slot=\"progress\"\n      value={value}\n      {...props}\n    >\n      {children}\n      <ProgressTrack>\n        <ProgressIndicator />\n      </ProgressTrack>\n    </ProgressPrimitive.Root>\n  );\n}\n\nfunction ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {\n  return (\n    <ProgressPrimitive.Track\n      className={cn(\n        'relative flex h-1 w-full items-center overflow-x-hidden rounded-full bg-muted',\n        className\n      )}\n      data-slot=\"progress-track\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressIndicator({\n  className,\n  ...props\n}: ProgressPrimitive.Indicator.Props) {\n  return (\n    <ProgressPrimitive.Indicator\n      className={cn('h-full bg-primary transition-all', className)}\n      data-slot=\"progress-indicator\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {\n  return (\n    <ProgressPrimitive.Label\n      className={cn('font-medium text-sm', className)}\n      data-slot=\"progress-label\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {\n  return (\n    <ProgressPrimitive.Value\n      className={cn(\n        'ml-auto text-muted-foreground text-sm tabular-nums',\n        className\n      )}\n      data-slot=\"progress-value\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Progress,\n  ProgressTrack,\n  ProgressIndicator,\n  ProgressLabel,\n  ProgressValue,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "onboarding",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Onboarding03 from @components/uiblocks/onboarding-03/onboarding-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/onboarding/onboarding-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "onboarding",
          "setup",
          "steps",
          "progress",
          "引导",
          "入门",
          "步骤",
          "进度"
        ],
        "contentGuidelines": [
          "Give each step a concrete outcome and a way to continue.",
          "Persist completed setup steps in your application."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/onboarding-03/onboarding-03.tsx",
        "exportName": "Onboarding03",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/onboarding-03"
      }
    },
    {
      "name": "onboarding-04",
      "type": "registry:block",
      "title": "Onboarding Accordion",
      "description": "Onboarding Accordion. Guided setup, checklists, timelines, and onboarding progress for new users.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/onboarding-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/onboarding-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/onboarding/onboarding-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-04/onboarding-04.tsx",
          "content": "'use client';\n\nimport {\n  IconAdjustments,\n  IconCalculator,\n  IconChartBar,\n  IconCircleCheckFilled,\n  IconDatabase,\n} from '@tabler/icons-react';\nimport { useState } from 'react';\nimport {\n  Accordion,\n  AccordionContent,\n  AccordionItem,\n  AccordionTrigger,\n} from \"./shared/components/ui/accordion\";\nimport { Button } from \"./shared/components/ui/button\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst steps = [\n  {\n    title: 'Configure your environment',\n    subtitle: 'Environment ready',\n    icon: IconAdjustments,\n    description:\n      'Set up your development environment with API keys and preferences.',\n    buttonText: 'Edit settings',\n  },\n  {\n    title: 'Connect your database',\n    subtitle: 'Add connection',\n    icon: IconDatabase,\n    description:\n      'Link your PostgreSQL, MySQL, or MongoDB instance to get started.',\n    buttonText: 'Add database',\n  },\n  {\n    title: 'Define your schema',\n    subtitle: 'Create schema',\n    icon: IconCalculator,\n    description: 'Model your data with types, relations, and validation rules.',\n    buttonText: 'Open editor',\n  },\n  {\n    title: 'Build your first API',\n    subtitle: 'Generate endpoints',\n    icon: IconChartBar,\n    description:\n      'Auto-generate REST or GraphQL endpoints from your schema in seconds.',\n    buttonText: 'Generate API',\n  },\n];\n\nexport function Onboarding04() {\n  const [activeStep, setActiveStep] = useState(1);\n\n  const [openItem, setOpenItem] = useState(\n    activeStep < steps.length ? steps[activeStep].title : ''\n  );\n\n  const handleComplete = () => {\n    const next = activeStep + 1;\n    setActiveStep(next);\n    setOpenItem(next < steps.length ? steps[next].title : '');\n  };\n\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center bg-background p-4\">\n      <div className=\"mx-auto w-full min-w-0 max-w-sm sm:min-w-sm\">\n        <h3 className=\"font-semibold text-foreground text-lg\">Quick start</h3>\n        <p className=\"mt-1 text-muted-foreground text-sm\">\n          Set up your backend in four simple steps\n        </p>\n        <Accordion\n          className=\"mt-6 space-y-2\"\n          onValueChange={(value) => setOpenItem(value[0] ?? '')}\n          value={openItem ? [openItem] : []}\n        >\n          {steps.map((step, index) => {\n            let status: 'complete' | 'current' | 'upcoming' = 'upcoming';\n            if (index < activeStep) {\n              status = 'complete';\n            } else if (index === activeStep) {\n              status = 'current';\n            }\n\n            return (\n              <AccordionItem\n                className=\"!border-b rounded-lg border shadow-xs\"\n                key={step.title}\n                value={step.title}\n              >\n                <AccordionTrigger className=\"px-4 hover:no-underline\">\n                  <div className=\"flex items-center space-x-2\">\n                    {status === 'complete' ? (\n                      <span\n                        aria-hidden\n                        className=\"flex size-5 items-center justify-center\"\n                      >\n                        <IconCircleCheckFilled\n                          aria-hidden\n                          className=\"size-6 shrink-0 text-foreground\"\n                        />\n                      </span>\n                    ) : (\n                      <span\n                        aria-hidden\n                        className=\"size-5 shrink-0 rounded-full border border-border\"\n                      />\n                    )}\n                    <p\n                      className={cn(\n                        'font-medium text-sm',\n                        status === 'upcoming'\n                          ? 'text-muted-foreground/60'\n                          : 'text-foreground'\n                      )}\n                    >\n                      {step.title}\n                    </p>\n                  </div>\n                </AccordionTrigger>\n                <AccordionContent className=\"px-4\">\n                  <div className=\"flex items-center justify-center rounded-md bg-muted px-4 py-5 ring-1 ring-ring ring-inset\">\n                    <div className=\"max-w-xs text-center\">\n                      <step.icon\n                        aria-hidden\n                        className=\"mx-auto size-7 shrink-0 text-muted-foreground\"\n                      />\n                      <p className=\"mt-4 font-semibold text-muted-foreground text-sm\">\n                        {step.subtitle}\n                      </p>\n                      <p className=\"mt-1 text-muted-foreground text-sm\">\n                        {step.description}\n                      </p>\n                      {status === 'complete' ? (\n                        <Button className=\"mt-6\" size=\"sm\" variant=\"outline\">\n                          {step.buttonText}\n                        </Button>\n                      ) : (\n                        <Button\n                          className=\"mt-6\"\n                          disabled={status === 'upcoming'}\n                          onClick={() => {\n                            if (status === 'current') {\n                              handleComplete();\n                            }\n                          }}\n                          size=\"sm\"\n                        >\n                          {step.buttonText}\n                        </Button>\n                      )}\n                    </div>\n                  </div>\n                </AccordionContent>\n              </AccordionItem>\n            );\n          })}\n        </Accordion>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/accordion.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-04/shared/components/ui/accordion.tsx",
          "content": "import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion';\nimport { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport { cn } from \"../../lib/utils\";\n\nfunction Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {\n  return (\n    <AccordionPrimitive.Root\n      className={cn('flex w-full flex-col', className)}\n      data-slot=\"accordion\"\n      {...props}\n    />\n  );\n}\n\nfunction AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {\n  return (\n    <AccordionPrimitive.Item\n      className={cn('not-last:border-b', className)}\n      data-slot=\"accordion-item\"\n      {...props}\n    />\n  );\n}\n\nfunction AccordionTrigger({\n  className,\n  children,\n  ...props\n}: AccordionPrimitive.Trigger.Props) {\n  return (\n    <AccordionPrimitive.Header className=\"flex\">\n      <AccordionPrimitive.Trigger\n        className={cn(\n          'group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left font-medium text-sm outline-none transition-all hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground',\n          className\n        )}\n        data-slot=\"accordion-trigger\"\n        {...props}\n      >\n        {children}\n        <ChevronDownIcon\n          className=\"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden\"\n          data-slot=\"accordion-trigger-icon\"\n        />\n        <ChevronUpIcon\n          className=\"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline\"\n          data-slot=\"accordion-trigger-icon\"\n        />\n      </AccordionPrimitive.Trigger>\n    </AccordionPrimitive.Header>\n  );\n}\n\nfunction AccordionContent({\n  className,\n  children,\n  ...props\n}: AccordionPrimitive.Panel.Props) {\n  return (\n    <AccordionPrimitive.Panel\n      className=\"overflow-hidden text-sm data-closed:animate-accordion-up data-open:animate-accordion-down\"\n      data-slot=\"accordion-content\"\n      {...props}\n    >\n      <div\n        className={cn(\n          'h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',\n          className\n        )}\n      >\n        {children}\n      </div>\n    </AccordionPrimitive.Panel>\n  );\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-04/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "onboarding",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Onboarding04 from @components/uiblocks/onboarding-04/onboarding-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/onboarding/onboarding-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "onboarding",
          "setup",
          "steps",
          "progress",
          "引导",
          "入门",
          "步骤",
          "进度"
        ],
        "contentGuidelines": [
          "Give each step a concrete outcome and a way to continue.",
          "Persist completed setup steps in your application."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/onboarding-04/onboarding-04.tsx",
        "exportName": "Onboarding04",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/onboarding-04"
      }
    },
    {
      "name": "onboarding-05",
      "type": "registry:block",
      "title": "Onboarding Activity Feed",
      "description": "Onboarding Activity Feed. Guided setup, checklists, timelines, and onboarding progress for new users.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/onboarding-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/onboarding-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/onboarding/onboarding-05.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-05/onboarding-05.tsx",
          "content": "import { cn } from \"./shared/lib/utils\";\n\nconst steps = [\n  {\n    id: 1,\n    type: 'created',\n    description: 'Created project \"Brand Refresh\"',\n    user: { name: 'Sarah K.', initial: 'S', bgColor: 'bg-violet-500' },\n    activityTime: '3d ago',\n  },\n  {\n    id: 2,\n    type: 'created',\n    description: 'Uploaded 12 design assets',\n    user: { name: 'Marcus L.', initial: 'M', bgColor: 'bg-orange-500' },\n    activityTime: '2d ago',\n  },\n  {\n    id: 3,\n    type: 'created',\n    description: 'Shared prototype with stakeholders',\n    user: { name: 'Sarah K.', initial: 'S', bgColor: 'bg-emerald-500' },\n    activityTime: '2h ago',\n  },\n  {\n    id: 4,\n    type: 'created',\n    description: 'Left feedback on homepage layout',\n    user: { name: 'Jamie R.', initial: 'J', bgColor: 'bg-fuchsia-500' },\n    activityTime: '5min ago',\n  },\n  {\n    id: 5,\n    type: 'in progress',\n    description: 'Scheduled design review meeting',\n    user: { name: 'Team Ops', initial: 'T', bgColor: 'bg-blue-500' },\n    activityTime: 'today 2:30pm',\n  },\n];\n\nexport function Onboarding05() {\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center bg-background p-4\">\n      <div className=\"sm:max-w-lg md:mx-auto\">\n        <h3 className=\"font-medium text-foreground\">Project activity</h3>\n        <p className=\"mt-1 text-muted-foreground text-sm leading-6\">\n          Recent updates from your team\n        </p>\n        <ul className=\"mt-6 space-y-6 pb-2\">\n          {steps.map((step, stepIdx) => (\n            <li className=\"relative flex gap-x-3\" key={step.id}>\n              <div\n                className={cn(\n                  'absolute top-0 left-0 flex w-6 justify-center',\n                  stepIdx === steps.length - 1 ? 'h-6' : '-bottom-6'\n                )}\n              >\n                <span aria-hidden className=\"w-px bg-border\" />\n              </div>\n              <div className=\"flex items-start space-x-2\">\n                <div className=\"flex items-center space-x-2\">\n                  <div className=\"relative flex size-6 flex-none items-center justify-center bg-background\">\n                    {step.type === 'created' ? (\n                      <div className=\"size-3 rounded-full border border-gray-300 bg-muted/50 ring-4 ring-background\" />\n                    ) : (\n                      <div className=\"size-3 rounded-full border border-gray-300 bg-background ring-4 ring-background\" />\n                    )}\n                  </div>\n                  <span\n                    aria-hidden\n                    className={cn(\n                      step.user.bgColor,\n                      'inline-flex size-6 flex-none items-center justify-center rounded-full text-primary-foreground text-xs'\n                    )}\n                  >\n                    {step.user.initial}\n                  </span>\n                </div>\n                <p className=\"mt-0.5 font-medium text-foreground text-sm\">\n                  {step.user.name}\n                  <span className=\"font-normal text-muted-foreground\">\n                    {' '}\n                    {step.description}\n                  </span>\n                  <span className=\"font-normal text-muted-foreground/60\">\n                    {' '}\n                    &#8729; {step.activityTime}\n                  </span>\n                </p>\n              </div>\n            </li>\n          ))}\n        </ul>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "onboarding",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Onboarding05 from @components/uiblocks/onboarding-05/onboarding-05.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/onboarding/onboarding-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "onboarding",
          "setup",
          "steps",
          "progress",
          "引导",
          "入门",
          "步骤",
          "进度"
        ],
        "contentGuidelines": [
          "Give each step a concrete outcome and a way to continue.",
          "Persist completed setup steps in your application."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/onboarding-05/onboarding-05.tsx",
        "exportName": "Onboarding05",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/onboarding-05"
      }
    },
    {
      "name": "onboarding-06",
      "type": "registry:block",
      "title": "Onboarding Timeline",
      "description": "Onboarding Timeline. Guided setup, checklists, timelines, and onboarding progress for new users.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@tabler/icons-react@3.50.0",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/onboarding-06/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/onboarding-06/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/onboarding/onboarding-06.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-06/onboarding-06.tsx",
          "content": "import { IconCheck } from '@tabler/icons-react';\nimport { cn } from \"./shared/lib/utils\";\n\nconst steps = [\n  {\n    id: 1,\n    type: 'done',\n    title: 'Repository connected',\n    description: 'Linked GitHub repository to deployment pipeline',\n    activityTime: '3d ago',\n  },\n  {\n    id: 2,\n    type: 'done',\n    title: 'Build configuration set',\n    description: 'Configured build commands and environment variables',\n    activityTime: '2d ago',\n  },\n  {\n    id: 3,\n    type: 'done',\n    title: 'Domain configured',\n    description: 'Custom domain verified and SSL certificate issued',\n    activityTime: '31min ago',\n  },\n  {\n    id: 4,\n    type: 'in progress',\n    title: 'Running health checks',\n    description: 'Verifying all endpoints respond correctly',\n    activityTime: 'Running now...',\n  },\n  {\n    id: 5,\n    type: 'open',\n    title: 'Go live',\n    description: 'Switch traffic to the new deployment',\n    activityTime: 'Upcoming',\n  },\n];\n\nexport function Onboarding06() {\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center bg-background p-4\">\n      <div className=\"sm:mx-auto sm:max-w-lg\">\n        <h3 className=\"font-medium text-foreground\">Deployment progress</h3>\n        <ul className=\"mt-6 space-y-6\">\n          {steps.map((step, stepIdx) => (\n            <li className=\"relative flex gap-x-3\" key={step.id}>\n              <div\n                className={cn(\n                  'absolute top-0 left-0 flex w-6 justify-center',\n                  stepIdx === steps.length - 1 ? 'h-6' : '-bottom-6'\n                )}\n              >\n                <span aria-hidden className=\"w-px bg-border\" />\n              </div>\n              <div className=\"flex items-start space-x-2.5\">\n                <div className=\"relative flex size-6 flex-none items-center justify-center bg-background\">\n                  {step.type === 'done' ? (\n                    <IconCheck aria-hidden className=\"size-5 text-primary\" />\n                  ) : step.type === 'in progress' ? (\n                    <div\n                      aria-hidden\n                      className=\"size-2.5 rounded-full bg-primary ring-4 ring-background\"\n                    />\n                  ) : (\n                    <div\n                      aria-hidden\n                      className=\"size-3 rounded-full border border-border bg-background ring-4 ring-background\"\n                    />\n                  )}\n                </div>\n                <div>\n                  <p className=\"mt-0.5 font-medium text-foreground text-sm\">\n                    {step.title}{' '}\n                    <span className=\"font-normal text-muted-foreground/60\">\n                      &#8729; {step.activityTime}\n                    </span>\n                  </p>\n                  <p className=\"mt-0.5 text-muted-foreground text-sm leading-6\">\n                    {step.description}\n                  </p>\n                </div>\n              </div>\n            </li>\n          ))}\n        </ul>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-06/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "onboarding",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Onboarding06 from @components/uiblocks/onboarding-06/onboarding-06.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/onboarding/onboarding-06 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "onboarding",
          "setup",
          "steps",
          "progress",
          "引导",
          "入门",
          "步骤",
          "进度"
        ],
        "contentGuidelines": [
          "Give each step a concrete outcome and a way to continue.",
          "Persist completed setup steps in your application."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/onboarding-06/onboarding-06.tsx",
        "exportName": "Onboarding06",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/onboarding-06"
      }
    },
    {
      "name": "onboarding-07",
      "type": "registry:block",
      "title": "Onboarding Deploy Pipeline",
      "description": "Onboarding Deploy Pipeline. Guided setup, checklists, timelines, and onboarding progress for new users.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/onboarding-07/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/onboarding-07/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/onboarding/onboarding-07.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-07/onboarding-07.tsx",
          "content": "'use client';\n\nimport {\n  IconCircleCheckFilled,\n  IconLoader2,\n  IconRefresh,\n} from '@tabler/icons-react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n  Accordion,\n  AccordionContent,\n  AccordionItem,\n  AccordionTrigger,\n} from \"./shared/components/ui/accordion\";\nimport { Button } from \"./shared/components/ui/button\";\nimport { Progress } from \"./shared/components/ui/progress\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst initialSteps = [\n  {\n    id: 1,\n    type: 'created' as const,\n    description: 'Export records',\n    value: 100,\n    createdOn: '2024-03-10 09:32',\n    runTime: '12min 18s',\n  },\n  {\n    id: 2,\n    type: 'created' as const,\n    description: 'Transform schema',\n    value: 100,\n    createdOn: '2024-03-10 10:03',\n    runTime: '18min 45s',\n  },\n  {\n    id: 3,\n    type: 'in progress' as const,\n    description: 'Import to new system',\n    value: 45,\n    createdOn: null as string | null,\n    runTime: null as string | null,\n  },\n];\n\nexport function Onboarding07() {\n  const [steps, setSteps] = useState(initialSteps);\n  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n  const allComplete = steps.every((s) => s.value === 100);\n\n  const startAnimation = useCallback(() => {\n    if (intervalRef.current) {\n      clearInterval(intervalRef.current);\n    }\n    intervalRef.current = setInterval(() => {\n      setSteps((prev) => {\n        const next = prev.map((step) => {\n          if (step.id !== 3 || step.value >= 100) {\n            return step;\n          }\n          const increment = Math.random() * 0.8 + 0.3;\n          const newValue = Math.min(step.value + increment, 100);\n          if (newValue >= 100) {\n            return {\n              ...step,\n              value: 100,\n              type: 'created' as const,\n              createdOn: '2024-03-10 10:47',\n              runTime: '6min 32s',\n            };\n          }\n          return { ...step, value: newValue };\n        });\n        if (next[2].value >= 100 && intervalRef.current) {\n          clearInterval(intervalRef.current);\n          intervalRef.current = null;\n        }\n        return next;\n      });\n    }, 50);\n  }, []);\n\n  useEffect(() => {\n    startAnimation();\n    return () => {\n      if (intervalRef.current) {\n        clearInterval(intervalRef.current);\n      }\n    };\n  }, [startAnimation]);\n\n  const handleRunAgain = () => {\n    setSteps(initialSteps);\n    startAnimation();\n  };\n\n  return (\n    <div className=\"flex min-h-dvh items-center justify-center bg-background p-4\">\n      <div className=\"w-full max-w-xl\">\n        <div className=\"flex items-center justify-between\">\n          <h3 className=\"font-semibold text-foreground\">Data Migration</h3>\n          <Button\n            className={\n              allComplete ? 'opacity-100' : 'pointer-events-none opacity-0'\n            }\n            onClick={handleRunAgain}\n            size=\"icon\"\n            variant=\"secondary\"\n          >\n            <IconRefresh aria-hidden=\"true\" className=\"size-4\" />\n          </Button>\n        </div>\n        <p className=\"mt-1 text-muted-foreground text-sm leading-6\">\n          Migrating records from your legacy system\n        </p>\n        <div className=\"mt-6 flex items-center space-x-2\">\n          {steps.map((step) => (\n            <div className=\"w-full truncate\" key={step.id}>\n              <Progress className=\"h-1.5\" value={step.value} />\n              <div className=\"mt-2 flex items-center space-x-1 truncate\">\n                {step.value === 100 ? (\n                  <IconCircleCheckFilled\n                    aria-hidden={true}\n                    className=\"size-4 shrink-0 text-primary\"\n                  />\n                ) : (\n                  <IconLoader2\n                    aria-hidden={true}\n                    className=\"size-4 shrink-0 animate-spin text-primary\"\n                  />\n                )}\n                <p className=\"truncate text-muted-foreground text-xs\">\n                  {step.description}\n                </p>\n              </div>\n            </div>\n          ))}\n        </div>\n        <Accordion className=\"mt-8\" defaultValue={['logs']}>\n          <AccordionItem className=\"rounded-sm border-b-0\" value=\"logs\">\n            <AccordionTrigger className=\"font-medium text-foreground text-sm\">\n              Logs overview ({steps.length})\n            </AccordionTrigger>\n            <AccordionContent>\n              <ul className=\"mt-2 space-y-6 pb-2\">\n                {steps.map((step, stepIdx) => (\n                  <li className=\"relative flex gap-x-3\" key={step.id}>\n                    <div\n                      className={cn(\n                        'absolute top-0 left-0 flex w-6 justify-center',\n                        stepIdx === steps.length - 1 ? 'h-6' : '-bottom-6'\n                      )}\n                    >\n                      <span aria-hidden={true} className=\"w-px bg-border\" />\n                    </div>\n                    <div className=\"flex items-start space-x-2.5\">\n                      <div className=\"relative flex size-6 flex-none items-center justify-center bg-background\">\n                        {step.type === 'created' ? (\n                          <div className=\"size-3 rounded-full border border-border bg-muted/50 ring-4 ring-background\" />\n                        ) : (\n                          <div className=\"size-3 rounded-full border border-border bg-background ring-4 ring-background\" />\n                        )}\n                      </div>\n                      <div>\n                        <p className=\"mt-0.5 font-medium text-foreground text-sm\">\n                          {step.description}\n                        </p>\n                        {step.type === 'created' ? (\n                          <p className=\"text-muted-foreground text-sm leading-6\">\n                            Created on {step.createdOn}, Runtime: {step.runTime}\n                          </p>\n                        ) : (\n                          <p className=\"text-muted-foreground text-sm leading-6\">\n                            12,847 of 28,500 records processed...\n                          </p>\n                        )}\n                      </div>\n                    </div>\n                  </li>\n                ))}\n              </ul>\n            </AccordionContent>\n          </AccordionItem>\n        </Accordion>\n      </div>\n    </div>\n  );\n}\n"
        },
        {
          "path": "components/ui/accordion.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-07/shared/components/ui/accordion.tsx",
          "content": "import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion';\nimport { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport { cn } from \"../../lib/utils\";\n\nfunction Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {\n  return (\n    <AccordionPrimitive.Root\n      className={cn('flex w-full flex-col', className)}\n      data-slot=\"accordion\"\n      {...props}\n    />\n  );\n}\n\nfunction AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {\n  return (\n    <AccordionPrimitive.Item\n      className={cn('not-last:border-b', className)}\n      data-slot=\"accordion-item\"\n      {...props}\n    />\n  );\n}\n\nfunction AccordionTrigger({\n  className,\n  children,\n  ...props\n}: AccordionPrimitive.Trigger.Props) {\n  return (\n    <AccordionPrimitive.Header className=\"flex\">\n      <AccordionPrimitive.Trigger\n        className={cn(\n          'group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left font-medium text-sm outline-none transition-all hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground',\n          className\n        )}\n        data-slot=\"accordion-trigger\"\n        {...props}\n      >\n        {children}\n        <ChevronDownIcon\n          className=\"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden\"\n          data-slot=\"accordion-trigger-icon\"\n        />\n        <ChevronUpIcon\n          className=\"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline\"\n          data-slot=\"accordion-trigger-icon\"\n        />\n      </AccordionPrimitive.Trigger>\n    </AccordionPrimitive.Header>\n  );\n}\n\nfunction AccordionContent({\n  className,\n  children,\n  ...props\n}: AccordionPrimitive.Panel.Props) {\n  return (\n    <AccordionPrimitive.Panel\n      className=\"overflow-hidden text-sm data-closed:animate-accordion-up data-open:animate-accordion-down\"\n      data-slot=\"accordion-content\"\n      {...props}\n    >\n      <div\n        className={cn(\n          'h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',\n          className\n        )}\n      >\n        {children}\n      </div>\n    </AccordionPrimitive.Panel>\n  );\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-07/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/progress.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-07/shared/components/ui/progress.tsx",
          "content": "'use client';\n\nimport { Progress as ProgressPrimitive } from '@base-ui/react/progress';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Progress({\n  className,\n  children,\n  value,\n  ...props\n}: ProgressPrimitive.Root.Props) {\n  return (\n    <ProgressPrimitive.Root\n      className={cn('flex flex-wrap gap-3', className)}\n      data-slot=\"progress\"\n      value={value}\n      {...props}\n    >\n      {children}\n      <ProgressTrack>\n        <ProgressIndicator />\n      </ProgressTrack>\n    </ProgressPrimitive.Root>\n  );\n}\n\nfunction ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {\n  return (\n    <ProgressPrimitive.Track\n      className={cn(\n        'relative flex h-1 w-full items-center overflow-x-hidden rounded-full bg-muted',\n        className\n      )}\n      data-slot=\"progress-track\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressIndicator({\n  className,\n  ...props\n}: ProgressPrimitive.Indicator.Props) {\n  return (\n    <ProgressPrimitive.Indicator\n      className={cn('h-full bg-primary transition-all', className)}\n      data-slot=\"progress-indicator\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {\n  return (\n    <ProgressPrimitive.Label\n      className={cn('font-medium text-sm', className)}\n      data-slot=\"progress-label\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {\n  return (\n    <ProgressPrimitive.Value\n      className={cn(\n        'ml-auto text-muted-foreground text-sm tabular-nums',\n        className\n      )}\n      data-slot=\"progress-value\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Progress,\n  ProgressTrack,\n  ProgressIndicator,\n  ProgressLabel,\n  ProgressValue,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/onboarding-07/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "onboarding",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Onboarding07 from @components/uiblocks/onboarding-07/onboarding-07.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/onboarding/onboarding-07 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "onboarding",
          "setup",
          "steps",
          "progress",
          "引导",
          "入门",
          "步骤",
          "进度"
        ],
        "contentGuidelines": [
          "Give each step a concrete outcome and a way to continue.",
          "Persist completed setup steps in your application."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/onboarding-07/onboarding-07.tsx",
        "exportName": "Onboarding07",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/onboarding-07"
      }
    },
    {
      "name": "pricing-01",
      "type": "registry:block",
      "title": "Simple Subscription Pricing",
      "description": "A three-tier pricing section with a monthly/yearly selector and a visually emphasized recommended plan.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/pricing-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/pricing-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/marketing/pricing-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/pricing-01/pricing-01.tsx",
          "content": "\"use client\";\n\nimport { ArrowRight, Check } from \"lucide-react\";\nimport { useState } from \"react\";\n\nexport type PricingPlan = {\n  name: string;\n  description: string;\n  monthlyPrice: number;\n  annualMonthlyPrice: number;\n  features: string[];\n  highlighted?: boolean;\n  actionLabel?: string;\n  actionHref?: string;\n};\n\nexport type Pricing01Props = {\n  eyebrow?: string;\n  title?: string;\n  description?: string;\n  plans?: PricingPlan[];\n  currency?: string;\n};\n\n/** Illustrative plan comparison with a working billing-period toggle. */\nexport default function Pricing01({\n  eyebrow = \"ROOM TO GROW\",\n  title = \"Start small. Keep creating.\",\n  description = \"A simple plan comparison for your next product. All prices below are illustrative.\",\n  currency = \"$\",\n  plans = [\n    {\n      name: \"Personal\",\n      description: \"For the occasional good idea.\",\n      monthlyPrice: 0,\n      annualMonthlyPrice: 0,\n      features: [\n        \"3 projects\",\n        \"Single-file workflow\",\n        \"Standard exports\",\n        \"Local demo workspace\",\n      ],\n      actionLabel: \"Explore the demo\",\n    },\n    {\n      name: \"Creator\",\n      description: \"For a workflow that keeps moving.\",\n      monthlyPrice: 19,\n      annualMonthlyPrice: 15,\n      features: [\n        \"Unlimited projects\",\n        \"Batch file workflows\",\n        \"Reusable presets\",\n        \"Export history\",\n      ],\n      highlighted: true,\n      actionLabel: \"Explore Creator\",\n    },\n    {\n      name: \"Studio\",\n      description: \"For a little more room to make.\",\n      monthlyPrice: 39,\n      annualMonthlyPrice: 32,\n      features: [\n        \"Everything in Creator\",\n        \"Shared project folders\",\n        \"Team-ready layouts\",\n        \"Usage overview\",\n      ],\n      actionLabel: \"Explore Studio\",\n    },\n  ],\n}: Pricing01Props) {\n  const [period, setPeriod] = useState<\"monthly\" | \"yearly\">(\"monthly\");\n  return (\n    <section\n      className=\"w-full scroll-mt-10 border-y border-zinc-200/70 bg-zinc-50/70 py-20 text-zinc-950 md:py-24 dark:border-zinc-800 dark:bg-zinc-900/30 dark:text-white\"\n      id=\"pricing\"\n    >\n      <div className=\"mx-auto max-w-6xl px-6 lg:px-8\">\n        <div className=\"flex flex-col items-start justify-between gap-6 sm:flex-row sm:items-end\">\n          <div className=\"max-w-lg\">\n            <p className=\"text-[10px] font-semibold tracking-[0.16em] text-violet-600 dark:text-violet-400\">\n              {eyebrow}\n            </p>\n            <h2 className=\"mt-4 text-balance text-3xl font-semibold tracking-[-0.035em] sm:text-4xl\">\n              {title}\n            </h2>\n            <p className=\"mt-4 text-sm leading-6 text-zinc-500 dark:text-zinc-400\">\n              {description}\n            </p>\n          </div>\n          <div\n            aria-label=\"Billing period\"\n            className=\"inline-flex shrink-0 gap-1 rounded-lg border border-zinc-200 bg-white p-1 text-xs dark:border-zinc-700 dark:bg-zinc-950\"\n            role=\"group\"\n          >\n            {([\"monthly\", \"yearly\"] as const).map((value) => (\n              <button\n                aria-pressed={period === value}\n                className={`rounded-md px-4 py-2 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-violet-500 ${period === value ? \"bg-zinc-950 text-white dark:bg-white dark:text-zinc-950\" : \"text-zinc-500 hover:text-zinc-950 dark:text-zinc-400 dark:hover:text-white\"}`}\n                key={value}\n                onClick={() => setPeriod(value)}\n                type=\"button\"\n              >\n                {value === \"monthly\" ? \"Monthly\" : \"Yearly\"}\n              </button>\n            ))}\n          </div>\n        </div>\n        <div className=\"mt-10 grid gap-4 md:grid-cols-3\">\n          {plans.map((plan) => {\n            const price =\n              period === \"monthly\"\n                ? plan.monthlyPrice\n                : plan.annualMonthlyPrice;\n            return (\n              <article\n                className={`relative flex flex-col rounded-xl border bg-white p-6 lg:p-7 dark:bg-zinc-950 ${plan.highlighted ? \"border-violet-500 ring-1 ring-violet-500/10 dark:border-violet-400\" : \"border-zinc-200 dark:border-zinc-700\"}`}\n                key={plan.name}\n              >\n                <div className=\"flex min-h-6 flex-wrap items-center justify-between gap-2\">\n                  <h3 className=\"text-sm font-semibold\">{plan.name}</h3>\n                  {plan.highlighted && (\n                    <span className=\"rounded-full bg-violet-50 px-2 py-1 text-[9px] font-semibold text-violet-600 dark:bg-violet-500/10 dark:text-violet-400\">\n                      FOR EVERYDAY CREATING\n                    </span>\n                  )}\n                </div>\n                <p className=\"mt-2 min-h-10 text-xs leading-5 text-zinc-500 dark:text-zinc-400\">\n                  {plan.description}\n                </p>\n                <p\n                  aria-live=\"polite\"\n                  className=\"mt-5 flex items-baseline gap-1\"\n                >\n                  <span className=\"text-4xl font-semibold tracking-tight\">\n                    {currency}\n                    {price}\n                  </span>\n                  <span className=\"text-xs text-zinc-400\">/ month</span>\n                </p>\n                <p className=\"mt-2 min-h-5 text-[10px] text-zinc-400\">\n                  {price === 0\n                    ? \"A place to try things out\"\n                    : period === \"yearly\"\n                      ? `${currency}${price * 12} billed yearly`\n                      : \"Billed monthly\"}\n                </p>\n                <a\n                  className={`mt-6 inline-flex min-h-10 items-center justify-center gap-2 rounded-lg border px-3 py-2.5 text-xs font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-violet-500 ${plan.highlighted ? \"border-violet-600 bg-violet-600 text-white hover:bg-violet-700\" : \"border-zinc-200 text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-900\"}`}\n                  href={plan.actionHref ?? \"/preview/recipes/tool-workspace\"}\n                >\n                  {plan.actionLabel ?? `Explore ${plan.name}`}\n                  <ArrowRight aria-hidden=\"true\" className=\"size-3.5\" />\n                </a>\n                <ul className=\"mt-7 space-y-3 border-t border-zinc-100 pt-6 dark:border-zinc-800\">\n                  {plan.features.map((feature) => (\n                    <li\n                      className=\"flex items-start gap-2.5 text-xs leading-5 text-zinc-600 dark:text-zinc-400\"\n                      key={feature}\n                    >\n                      <Check\n                        aria-hidden=\"true\"\n                        className=\"mt-0.5 size-3.5 shrink-0 text-violet-500\"\n                      />\n                      {feature}\n                    </li>\n                  ))}\n                </ul>\n              </article>\n            );\n          })}\n        </div>\n        <p\n          className=\"mt-6 scroll-mt-8 text-center text-[11px] leading-5 text-zinc-400\"\n          id=\"pricing-demo\"\n        >\n          This is an interactive UI demo. No subscriptions or payments are\n          created.\n        </p>\n      </div>\n    </section>\n  );\n}\n"
        }
      ],
      "categories": [
        "marketing",
        "marketing",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/pricing-01/pricing-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/marketing/pricing-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "marketing",
        "featured": true,
        "tags": [
          "pricing",
          "subscription",
          "plans",
          "billing",
          "价格",
          "定价",
          "套餐",
          "订阅",
          "账单"
        ],
        "contentGuidelines": [
          "Demo prices are examples; define real entitlements and billing periods before launch.",
          "Keep feature lists comparable across the three plans and connect purchase links."
        ],
        "recommendedWith": [
          "features-01",
          "faq-01",
          "usage-panel-01"
        ],
        "entry": "@components/uiblocks/pricing-01/pricing-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/pricing-01"
      }
    },
    {
      "name": "results-gallery-01",
      "type": "registry:block",
      "title": "Generated Results Gallery",
      "description": "A result gallery with output previews and download actions for a media creation workspace.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/results-gallery-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/results-gallery-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/workspace/results-gallery-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/results-gallery-01/results-gallery-01.tsx",
          "content": "\"use client\";\n\nimport {\n  ArrowDownToLine,\n  Check,\n  FileImage,\n  ImagePlus,\n  Maximize2,\n  X,\n} from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\n\nexport type DemoResult = {\n  id: string;\n  name: string;\n  format: string;\n  detail: string;\n  tone?: \"violet\" | \"mint\" | \"peach\";\n};\nexport type ResultsGallery01Props = {\n  title?: string;\n  results?: DemoResult[];\n  searchQuery?: string;\n};\nconst sampleResults: DemoResult[] = [\n  {\n    id: \"result-1\",\n    name: \"Autumn campaign\",\n    format: \"MP4\",\n    detail: \"Sample output · SVG preview\",\n    tone: \"violet\",\n  },\n  {\n    id: \"result-2\",\n    name: \"Product walkthrough\",\n    format: \"WebM\",\n    detail: \"Sample output · SVG preview\",\n    tone: \"mint\",\n  },\n];\n\nfunction escapeXml(value: string) {\n  return value.replace(\n    /[<>&\"']/g,\n    (character) =>\n      ({\n        \"<\": \"&lt;\",\n        \">\": \"&gt;\",\n        \"&\": \"&amp;\",\n        '\"': \"&quot;\",\n        \"'\": \"&apos;\",\n      })[character] ?? character\n  );\n}\n\n/** Downloadable SVG preview cards. No video conversion is implied. */\nexport default function ResultsGallery01({\n  title = \"Ready for your next chapter.\",\n  results = sampleResults,\n  searchQuery = \"\",\n}: ResultsGallery01Props = {}) {\n  const [selected, setSelected] = useState<DemoResult | null>(null);\n  const [downloaded, setDownloaded] = useState<string | null>(null);\n  const id = useId();\n  const dialogRef = useRef<HTMLDialogElement>(null);\n  useEffect(() => {\n    const dialog = dialogRef.current;\n    if (!dialog) return;\n    if (selected && !dialog.open) dialog.showModal();\n    else if (!selected && dialog.open) dialog.close();\n  }, [selected]);\n  const visible = results.filter((result) =>\n    result.name.toLowerCase().includes(searchQuery.toLowerCase())\n  );\n\n  function download(result: DemoResult) {\n    const colors = { violet: \"#ede9fe\", mint: \"#d1fae5\", peach: \"#ffedd5\" };\n    const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"750\" viewBox=\"0 0 1200 750\"><rect width=\"1200\" height=\"750\" fill=\"${colors[result.tone ?? \"violet\"]}\"/><rect x=\"120\" y=\"100\" width=\"960\" height=\"550\" rx=\"32\" fill=\"#fff\"/><text x=\"180\" y=\"200\" font-family=\"system-ui,sans-serif\" font-size=\"24\" fill=\"#7c3aed\">FRAME / WORKFLOW DEMO</text><text x=\"180\" y=\"310\" font-family=\"system-ui,sans-serif\" font-size=\"38\" font-weight=\"600\" fill=\"#18181b\">${escapeXml(result.name.slice(0, 45))}</text><text x=\"180\" y=\"375\" font-family=\"system-ui,sans-serif\" font-size=\"20\" fill=\"#71717a\">Selected output: ${escapeXml(result.format)}</text><text x=\"180\" y=\"520\" font-family=\"system-ui,sans-serif\" font-size=\"18\" fill=\"#71717a\">This is an SVG demo preview. No video was uploaded or converted.</text></svg>`;\n    const url = URL.createObjectURL(new Blob([svg], { type: \"image/svg+xml\" }));\n    const anchor = document.createElement(\"a\");\n    anchor.href = url;\n    anchor.download = `${result.name.replace(/[^a-z0-9-]/gi, \"-\").toLowerCase()}-demo.svg`;\n    anchor.click();\n    window.setTimeout(() => URL.revokeObjectURL(url), 1000);\n    setDownloaded(result.id);\n  }\n\n  function artwork(result: DemoResult, expanded = false) {\n    const tones = {\n      violet: \"bg-violet-100 dark:bg-violet-950/60\",\n      mint: \"bg-emerald-100 dark:bg-emerald-950/60\",\n      peach: \"bg-orange-100 dark:bg-orange-950/60\",\n    };\n    return (\n      <div\n        className={`relative flex aspect-[16/10] items-center justify-center overflow-hidden ${tones[result.tone ?? \"violet\"]} ${expanded ? \"rounded-xl\" : \"\"}`}\n      >\n        <div\n          className=\"absolute inset-0 opacity-30\"\n          style={{\n            backgroundImage:\n              \"radial-gradient(currentColor 0.7px, transparent 0.7px)\",\n            backgroundSize: \"12px 12px\",\n            color: \"#a1a1aa\",\n          }}\n        />\n        <div className=\"relative w-[68%] -rotate-6 rounded-lg border border-white/80 bg-white p-[8%] shadow-lg shadow-zinc-800/5 dark:border-zinc-700 dark:bg-zinc-900\">\n          <span className=\"text-[8px] font-medium uppercase tracking-[0.18em] text-zinc-400\">\n            FRAME STUDIO\n          </span>\n          <div className=\"mt-3 space-y-1.5\">\n            <div className=\"h-2.5 w-4/5 rounded bg-zinc-800 dark:bg-zinc-200\" />\n            <div className=\"h-2.5 w-3/5 rounded bg-zinc-800 dark:bg-zinc-200\" />\n          </div>\n          <div className=\"mt-4 flex gap-1\">\n            <span className=\"h-1 w-3 rounded bg-violet-400\" />\n            <span className=\"h-1 w-7 rounded bg-zinc-200 dark:bg-zinc-700\" />\n          </div>\n        </div>\n        <span className=\"absolute right-2.5 bottom-2 rounded-md bg-white/80 px-1.5 py-0.5 text-[8px] font-medium text-zinc-600 backdrop-blur dark:bg-zinc-900/80 dark:text-zinc-300\">\n          SVG demo\n        </span>\n      </div>\n    );\n  }\n\n  return (\n    <section\n      aria-labelledby={id}\n      className=\"w-full rounded-2xl border border-zinc-200 bg-white p-5 text-zinc-950 shadow-sm shadow-zinc-950/[0.02] sm:p-6 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n    >\n      <div className=\"flex items-center justify-between gap-3\">\n        <div>\n          <h2 className=\"text-sm font-semibold\" id={id}>\n            {title}\n          </h2>\n          <p className=\"mt-1 text-[11px] text-zinc-400\">\n            Preview cards you can actually download.\n          </p>\n        </div>\n        <span className=\"grid size-8 place-items-center rounded-lg border border-zinc-100 text-zinc-400 dark:border-zinc-800\">\n          <FileImage aria-hidden=\"true\" className=\"size-4\" />\n        </span>\n      </div>\n      {visible.length ? (\n        <div className=\"mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n          {visible.map((result) => (\n            <article\n              className=\"overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800\"\n              key={result.id}\n            >\n              <button\n                aria-label={`Preview ${result.name}`}\n                className=\"group relative block w-full text-left focus-visible:outline-2 focus-visible:outline-violet-500\"\n                onClick={() => setSelected(result)}\n                type=\"button\"\n              >\n                {artwork(result)}\n                <span className=\"absolute top-2.5 right-2.5 grid size-7 place-items-center rounded-lg bg-white/80 text-zinc-500 opacity-100 transition-opacity sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-visible:opacity-100 dark:bg-zinc-900/80\">\n                  <Maximize2 aria-hidden=\"true\" className=\"size-3\" />\n                </span>\n              </button>\n              <div className=\"flex items-center gap-2 p-3\">\n                <div className=\"min-w-0 flex-1\">\n                  <h3 className=\"truncate text-xs font-medium\">\n                    {result.name}\n                  </h3>\n                  <p className=\"mt-1 text-[9px] text-zinc-400\">\n                    {result.detail}\n                  </p>\n                </div>\n                <button\n                  aria-label={`Download SVG demo for ${result.name}`}\n                  className=\"grid size-8 shrink-0 place-items-center rounded-lg border border-zinc-200 text-zinc-500 transition-colors hover:border-violet-300 hover:bg-violet-50 hover:text-violet-600 focus-visible:outline-2 focus-visible:outline-violet-500 dark:border-zinc-700 dark:hover:bg-violet-950/40 dark:hover:text-violet-300\"\n                  onClick={() => download(result)}\n                  type=\"button\"\n                >\n                  {downloaded === result.id ? (\n                    <Check\n                      aria-hidden=\"true\"\n                      className=\"size-3.5 text-emerald-500\"\n                    />\n                  ) : (\n                    <ArrowDownToLine aria-hidden=\"true\" className=\"size-3.5\" />\n                  )}\n                </button>\n              </div>\n            </article>\n          ))}\n        </div>\n      ) : (\n        <div className=\"flex min-h-48 flex-col items-center justify-center rounded-xl border border-dashed border-zinc-200 p-6 text-center dark:border-zinc-800\">\n          <ImagePlus\n            aria-hidden=\"true\"\n            className=\"size-7 text-zinc-300 dark:text-zinc-600\"\n          />\n          <p className=\"mt-3 text-xs font-medium\">\n            {searchQuery ? \"No matching results\" : \"Room for your next idea.\"}\n          </p>\n          <p className=\"mt-1 max-w-56 text-[11px] leading-5 text-zinc-400\">\n            {searchQuery\n              ? \"Try another file name.\"\n              : \"Complete a demo workflow to see your preview cards here.\"}\n          </p>\n        </div>\n      )}\n      {downloaded && (\n        <p\n          className=\"mt-3 text-[10px] text-emerald-600 dark:text-emerald-400\"\n          role=\"status\"\n        >\n          SVG demo preview downloaded. Your source video was not converted.\n        </p>\n      )}\n      <dialog\n        aria-label={\n          selected ? `${selected.name} demo preview` : \"Result preview\"\n        }\n        className=\"fixed inset-0 m-0 h-dvh max-h-none w-screen max-w-none items-center justify-center bg-transparent p-5 text-zinc-950 open:flex backdrop:bg-zinc-950/50 backdrop:backdrop-blur-sm dark:text-zinc-50\"\n        onCancel={(event) => {\n          event.preventDefault();\n          setSelected(null);\n        }}\n        onClick={(event) => {\n          if (event.target === event.currentTarget) setSelected(null);\n        }}\n        onClose={() => setSelected(null)}\n        ref={dialogRef}\n      >\n        {selected && (\n          <div className=\"relative my-auto w-full max-w-xl rounded-2xl bg-white p-5 shadow-2xl dark:bg-zinc-950\">\n            <div className=\"mb-4 flex items-center justify-between gap-4\">\n              <h3 className=\"truncate text-sm font-medium\">{selected.name}</h3>\n              <button\n                aria-label=\"Close preview\"\n                autoFocus\n                className=\"grid size-8 shrink-0 place-items-center rounded-lg text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n                onClick={() => setSelected(null)}\n                type=\"button\"\n              >\n                <X aria-hidden=\"true\" className=\"size-4\" />\n              </button>\n            </div>\n            {artwork(selected, true)}\n            <p className=\"mt-4 text-xs leading-5 text-zinc-500\">\n              This is an SVG demo card. Your videos stay local and are not\n              converted.\n            </p>\n            <button\n              className=\"mt-4 inline-flex min-h-10 items-center gap-2 rounded-lg bg-violet-600 px-4 text-xs font-medium text-white hover:bg-violet-700\"\n              onClick={() => download(selected)}\n              type=\"button\"\n            >\n              <ArrowDownToLine aria-hidden=\"true\" className=\"size-3.5\" />{\" \"}\n              Download SVG demo\n            </button>\n          </div>\n        )}\n      </dialog>\n    </section>\n  );\n}\n"
        }
      ],
      "categories": [
        "workspace",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/results-gallery-01/results-gallery-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/workspace/results-gallery-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": true,
        "tags": [
          "results",
          "gallery",
          "output",
          "download",
          "media",
          "结果",
          "作品",
          "图库",
          "下载",
          "媒体"
        ],
        "contentGuidelines": [
          "Give each output a useful title, format, and downloadable file.",
          "Keep thumbnail aspect ratios consistent and provide an empty state."
        ],
        "recommendedWith": [
          "upload-workflow-01",
          "task-queue-01"
        ],
        "entry": "@components/uiblocks/results-gallery-01/results-gallery-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/results-gallery-01"
      }
    },
    {
      "name": "sidebar-01",
      "type": "registry:block",
      "title": "Sidebar Collapsible Sections",
      "description": "Sidebar Collapsible Sections. Application navigation shells with collapsible sidebars and responsive workspace layouts.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cmdk@1.1.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/sidebar/sidebar-01/app-sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/app-sidebar.tsx",
          "content": "'use client';\n\nimport {\n  IconAd2,\n  IconBellRinging,\n  IconCalendar,\n  IconCalendarStats,\n  IconListDetails,\n  IconNews,\n  IconNotebook,\n  IconProgressCheck,\n  IconSettingsCode,\n} from '@tabler/icons-react';\nimport { LayoutDashboard, Package } from 'lucide-react';\nimport { Sidebar, SidebarContent } from \"./shared/components/ui/sidebar\";\nimport { NavCollapsible } from \"./nav-collapsible\";\nimport { NavFooter } from \"./nav-footer\";\nimport { NavHeader } from \"./nav-header\";\nimport { NavMain } from \"./nav-main\";\nimport type { SidebarData } from \"./types\";\n\nconst data: SidebarData = {\n  user: {\n    name: 'ephraim',\n    email: 'ephraim@blocks.so',\n    avatar: 'https://uiblocks.org/avatar-01.png',\n  },\n  navMain: [\n    {\n      id: 'overview',\n      title: 'Overview',\n      url: '#',\n      icon: LayoutDashboard,\n      isActive: true,\n    },\n    {\n      id: 'tasks',\n      title: 'Tasks',\n      url: '#',\n      icon: IconListDetails,\n    },\n    {\n      id: 'meetings',\n      title: 'Meetings',\n      url: '#',\n      icon: IconCalendarStats,\n    },\n    {\n      id: 'notes',\n      title: 'Notes',\n      url: '#',\n      icon: IconNotebook,\n    },\n    {\n      id: 'calendar',\n      title: 'Calendar',\n      url: '#',\n      icon: IconCalendar,\n    },\n    {\n      id: 'completed',\n      title: 'Completed',\n      url: '#',\n      icon: IconProgressCheck,\n    },\n    {\n      id: 'notifications',\n      title: 'Notifications',\n      url: '#',\n      icon: IconBellRinging,\n    },\n  ],\n  navCollapsible: {\n    favorites: [\n      {\n        id: 'design',\n        title: 'Design',\n        href: '#',\n        color: 'bg-green-400 dark:bg-green-300',\n      },\n      {\n        id: 'development',\n        title: 'Development',\n        href: '#',\n        color: 'bg-blue-400 dark:bg-blue-300',\n      },\n      {\n        id: 'workshop',\n        title: 'Workshop',\n        href: '#',\n        color: 'bg-orange-400 dark:bg-orange-300',\n      },\n      {\n        id: 'personal',\n        title: 'Personal',\n        href: '#',\n        color: 'bg-red-400 dark:bg-red-300',\n      },\n    ],\n    teams: [\n      {\n        id: 'engineering',\n        title: 'Engineering',\n        icon: IconSettingsCode,\n      },\n      {\n        id: 'marketing',\n        title: 'Marketing',\n        icon: IconAd2,\n      },\n    ],\n    topics: [\n      {\n        id: 'product-updates',\n        title: 'Product Updates',\n        icon: Package,\n      },\n      {\n        id: 'company-news',\n        title: 'Company News',\n        icon: IconNews,\n      },\n    ],\n  },\n};\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n  return (\n    <Sidebar {...props}>\n      <NavHeader data={data} />\n      <SidebarContent>\n        <NavMain items={data.navMain} />\n        <NavCollapsible\n          favorites={data.navCollapsible.favorites}\n          teams={data.navCollapsible.teams}\n          topics={data.navCollapsible.topics}\n        />\n      </SidebarContent>\n      <NavFooter user={data.user} />\n    </Sidebar>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-01/index.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/index.tsx",
          "content": "import {\n  SidebarInset,\n  SidebarProvider,\n  SidebarTrigger,\n} from \"./shared/components/ui/sidebar\";\nimport { AppSidebar } from \"./app-sidebar\";\n\nexport default function Sidebar01() {\n  return (\n    <SidebarProvider>\n      <AppSidebar />\n      <SidebarInset>\n        <header className=\"flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12\">\n          <div className=\"flex items-center gap-2 px-4\">\n            <SidebarTrigger className=\"sm:hidden\" />\n          </div>\n        </header>\n      </SidebarInset>\n    </SidebarProvider>\n  );\n}\n"
        },
        {
          "path": "licenses/sidebar-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/sidebar-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/sidebar/sidebar-01/nav-collapsible.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/nav-collapsible.tsx",
          "content": "'use client';\nimport { ChevronDown } from 'lucide-react';\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"./shared/components/ui/collapsible\";\nimport {\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"./shared/components/ui/sidebar\";\nimport type { FavoriteItem, TeamItem, TopicItem } from \"./types\";\n\ninterface NavCollapsibleProps {\n  favorites: FavoriteItem[];\n  teams: TeamItem[];\n  topics: TopicItem[];\n}\n\nexport function NavCollapsible({\n  favorites,\n  teams,\n  topics,\n}: NavCollapsibleProps) {\n  return (\n    <div className=\"space-y-0\">\n      {favorites && favorites.length > 0 && (\n        <Collapsible className=\"group/collapsible\" defaultOpen>\n          <SidebarGroup>\n            <SidebarGroupLabel\n              className=\"text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\"\n              render={<CollapsibleTrigger />}\n            >\n              Favorites\n              <ChevronDown className=\"ml-auto transition-transform group-data-open/collapsible:rotate-180\" />\n            </SidebarGroupLabel>\n            <CollapsibleContent>\n              <SidebarGroupContent>\n                <SidebarMenu>\n                  {favorites.map((item) => (\n                    <SidebarMenuItem key={item.id}>\n                      <SidebarMenuButton\n                        render={\n                          <a\n                            className=\"flex items-center gap-3\"\n                            href={item.href}\n                          />\n                        }\n                      >\n                        <div className={`h-3 w-3 rounded ${item.color}`} />\n                        <span>{item.title}</span>\n                      </SidebarMenuButton>\n                    </SidebarMenuItem>\n                  ))}\n                </SidebarMenu>\n              </SidebarGroupContent>\n            </CollapsibleContent>\n          </SidebarGroup>\n        </Collapsible>\n      )}\n\n      {[\n        { label: 'Teams', items: teams },\n        { label: 'Topics', items: topics },\n      ].map(\n        ({ label, items }) =>\n          items &&\n          items.length > 0 && (\n            <Collapsible className=\"group/collapsible\" key={label}>\n              <SidebarGroup>\n                <SidebarGroupLabel\n                  className=\"text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\"\n                  render={<CollapsibleTrigger />}\n                >\n                  {label}\n                  <ChevronDown className=\"ml-auto transition-transform group-data-open/collapsible:rotate-180\" />\n                </SidebarGroupLabel>\n                <CollapsibleContent>\n                  <SidebarGroupContent>\n                    <SidebarMenu>\n                      {items.map((item) => {\n                        const Icon = item.icon;\n                        return (\n                          <SidebarMenuItem key={item.id}>\n                            <SidebarMenuButton>\n                              <Icon className=\"mr-2 h-4 w-4\" />\n                              {item.title}\n                            </SidebarMenuButton>\n                          </SidebarMenuItem>\n                        );\n                      })}\n                    </SidebarMenu>\n                  </SidebarGroupContent>\n                </CollapsibleContent>\n              </SidebarGroup>\n            </Collapsible>\n          )\n      )}\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-01/nav-footer.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/nav-footer.tsx",
          "content": "'use client';\n\nimport {\n  BookmarkPlus,\n  CircleHelp,\n  LogOut,\n  Plus,\n  PlusCircle,\n  Puzzle,\n  Settings,\n  User,\n} from 'lucide-react';\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  SidebarFooter,\n  SidebarMenu,\n  SidebarMenuItem,\n} from \"./shared/components/ui/sidebar\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"./shared/components/ui/tooltip\";\n\nexport function NavFooter({\n  user,\n}: {\n  user: {\n    name: string;\n    email: string;\n    avatar: string;\n  };\n}) {\n  return (\n    <SidebarFooter className=\"p-4\">\n      <SidebarMenu>\n        <SidebarMenuItem>\n          <div className=\"flex items-center justify-between gap-2\">\n            <div className=\"flex items-center gap-2\">\n              <DropdownMenu>\n                <DropdownMenuTrigger\n                  nativeButton={false}\n                  render={<Avatar className=\"h-8 w-8 rounded-full\" />}\n                >\n                  <AvatarImage alt={user.name} src={user.avatar} />\n                  <AvatarFallback className=\"rounded-full\">CN</AvatarFallback>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent className=\"m-2\">\n                  <DropdownMenuItem>\n                    <User aria-hidden=\"true\" className=\"opacity-80\" size={16} />\n                    Profile\n                  </DropdownMenuItem>\n                  <DropdownMenuItem>\n                    <Settings\n                      aria-hidden=\"true\"\n                      className=\"opacity-80\"\n                      size={16}\n                    />\n                    Settings\n                  </DropdownMenuItem>\n                  <DropdownMenuItem>\n                    <LogOut\n                      aria-hidden=\"true\"\n                      className=\"opacity-80\"\n                      size={16}\n                    />\n                    Logout\n                  </DropdownMenuItem>\n                </DropdownMenuContent>\n              </DropdownMenu>\n\n              <TooltipProvider delay={0}>\n                <Tooltip>\n                  <TooltipTrigger\n                    render={\n                      <CircleHelp\n                        aria-hidden=\"true\"\n                        className=\"cursor-pointer opacity-60 hover:opacity-100\"\n                        size={16}\n                      />\n                    }\n                  />\n                  <TooltipContent\n                    className=\"m-2 max-w-[150px] border bg-popover px-2 py-1 text-popover-foreground\"\n                    side=\"top\"\n                  >\n                    <div className=\"space-y-1 text-xs\">\n                      <p className=\"font-medium\">User Information</p>\n                      <p className=\"text-muted-foreground\">\n                        More details about the current user or section can be\n                        displayed here.\n                      </p>\n                    </div>\n                  </TooltipContent>\n                </Tooltip>\n              </TooltipProvider>\n            </div>\n            <DropdownMenu>\n              <DropdownMenuTrigger\n                render={\n                  <Button\n                    aria-label=\"Open edit menu\"\n                    className=\"rounded-full shadow-none focus-visible:ring-0 focus-visible:ring-offset-0\"\n                    size=\"icon\"\n                    variant=\"ghost\"\n                  />\n                }\n              >\n                <Plus aria-hidden=\"true\" size={16} />\n              </DropdownMenuTrigger>\n              <DropdownMenuContent className=\"pb-2\">\n                <DropdownMenuLabel>Add New</DropdownMenuLabel>\n                <DropdownMenuItem>\n                  <PlusCircle\n                    aria-hidden=\"true\"\n                    className=\"mr-2 opacity-80\"\n                    size={16}\n                  />\n                  Add New Item\n                </DropdownMenuItem>\n                <DropdownMenuItem>\n                  <BookmarkPlus\n                    aria-hidden=\"true\"\n                    className=\"mr-2 opacity-80\"\n                    size={16}\n                  />\n                  Add Bookmark\n                </DropdownMenuItem>\n                <DropdownMenuItem>\n                  <Puzzle\n                    aria-hidden=\"true\"\n                    className=\"mr-2 opacity-80\"\n                    size={16}\n                  />\n                  Add Integration\n                </DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </div>\n        </SidebarMenuItem>\n      </SidebarMenu>\n    </SidebarFooter>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-01/nav-header.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/nav-header.tsx",
          "content": "'use client';\n\nimport { Search } from 'lucide-react';\nimport * as React from 'react';\nimport { useEffect } from 'react';\nimport {\n  Command,\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n} from \"./shared/components/ui/command\";\nimport { SidebarHeader } from \"./shared/components/ui/sidebar\";\nimport { cn } from \"./shared/lib/utils\";\nimport type { SidebarData } from \"./types\";\n\ninterface NavHeaderProps {\n  data: SidebarData;\n}\n\nexport function NavHeader({ data }: NavHeaderProps) {\n  const [open, setOpen] = React.useState(false);\n\n  useEffect(() => {\n    const down = (e: KeyboardEvent) => {\n      if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {\n        e.preventDefault();\n        setOpen((open) => !open);\n      }\n    };\n    document.addEventListener('keydown', down);\n    return () => document.removeEventListener('keydown', down);\n  }, []);\n\n  return (\n    <>\n      <SidebarHeader>\n        <div\n          className=\"flex cursor-pointer items-center justify-between px-2 pt-3 pb-0\"\n          onClick={() => setOpen(true)}\n        >\n          <div className=\"flex flex-1 items-center gap-3\">\n            <Search className=\"h-4 w-4 text-muted-foreground\" />\n            <span className=\"font-normal text-muted-foreground text-sm\">\n              Search\n            </span>\n          </div>\n          <div className=\"flex items-center justify-center rounded-md border border-border px-2 py-1\">\n            <kbd className=\"inline-flex font-[inherit] font-medium text-muted-foreground text-xs\">\n              <span className=\"opacity-70\">⌘K</span>\n            </kbd>\n          </div>\n        </div>\n      </SidebarHeader>\n\n      <CommandDialog onOpenChange={setOpen} open={open}>\n        <Command>\n          <CommandInput placeholder=\"Search everything...\" />\n          <CommandList>\n            <CommandEmpty>No results found.</CommandEmpty>\n            <CommandGroup heading=\"Navigation\">\n              {data.navMain.map((item) => (\n                <CommandItem\n                  className=\"py-2!\"\n                  key={item.id}\n                  onSelect={() => {\n                    setOpen(false);\n                  }}\n                >\n                  <item.icon className=\"mr-2 h-4 w-4\" />\n                  <span>{item.title}</span>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n            <CommandSeparator className=\"my-2\" />\n            <CommandGroup heading=\"Favorites\">\n              {data.navCollapsible.favorites.map((item) => (\n                <CommandItem\n                  className=\"py-2!\"\n                  key={item.id}\n                  onSelect={() => {\n                    setOpen(false);\n                  }}\n                >\n                  <div\n                    className={cn('mr-2 h-3 w-3 rounded-full', item.color)}\n                  />\n                  <span>{item.title}</span>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n            <CommandSeparator className=\"my-2\" />\n            <CommandGroup heading=\"Teams\">\n              {data.navCollapsible.teams.map((item) => (\n                <CommandItem\n                  className=\"py-2!\"\n                  key={item.id}\n                  onSelect={() => {\n                    setOpen(false);\n                  }}\n                >\n                  <item.icon className=\"mr-2 h-4 w-4\" />\n                  <span>{item.title}</span>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n            <CommandSeparator className=\"my-2\" />\n            <CommandGroup heading=\"Topics\">\n              {data.navCollapsible.topics.map((item) => (\n                <CommandItem\n                  className=\"py-2!\"\n                  key={item.id}\n                  onSelect={() => {\n                    setOpen(false);\n                  }}\n                >\n                  <item.icon className=\"mr-2 h-4 w-4\" />\n                  <span>{item.title}</span>\n                </CommandItem>\n              ))}\n            </CommandGroup>\n          </CommandList>\n        </Command>\n      </CommandDialog>\n    </>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-01/nav-main.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/nav-main.tsx",
          "content": "'use client';\n\nimport {\n  SidebarGroup,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"./shared/components/ui/sidebar\";\nimport type { NavItem } from \"./types\";\n\nexport function NavMain({ items }: { items: NavItem[] }) {\n  return (\n    <SidebarGroup>\n      <SidebarMenu>\n        {items.map((item) => {\n          const Icon = item.icon;\n\n          return (\n            <SidebarMenuItem key={item.id}>\n              <SidebarMenuButton tooltip={item.title}>\n                {Icon && <Icon className=\"mr-2 h-4 w-4\" />}\n                <span>{item.title}</span>\n              </SidebarMenuButton>\n            </SidebarMenuItem>\n          );\n        })}\n      </SidebarMenu>\n    </SidebarGroup>\n  );\n}\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/collapsible.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/collapsible.tsx",
          "content": "'use client';\n\nimport { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible';\n\nfunction Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {\n  return (\n    <CollapsiblePrimitive.Trigger data-slot=\"collapsible-trigger\" {...props} />\n  );\n}\n\nfunction CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {\n  return (\n    <CollapsiblePrimitive.Panel data-slot=\"collapsible-content\" {...props} />\n  );\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
        },
        {
          "path": "components/ui/command.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/command.tsx",
          "content": "'use client';\n\nimport { Command as CommandPrimitive } from 'cmdk';\nimport { CheckIcon, SearchIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n} from \"./dialog\";\nimport { InputGroup, InputGroupAddon } from \"./input-group\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Command({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive>) {\n  return (\n    <CommandPrimitive\n      className={cn(\n        'flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground',\n        className\n      )}\n      data-slot=\"command\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandDialog({\n  title = 'Command Palette',\n  description = 'Search for a command to run...',\n  children,\n  className,\n  showCloseButton = false,\n  ...props\n}: Omit<React.ComponentProps<typeof Dialog>, 'children'> & {\n  title?: string;\n  description?: string;\n  className?: string;\n  showCloseButton?: boolean;\n  children: React.ReactNode;\n}) {\n  return (\n    <Dialog {...props}>\n      <DialogHeader className=\"sr-only\">\n        <DialogTitle>{title}</DialogTitle>\n        <DialogDescription>{description}</DialogDescription>\n      </DialogHeader>\n      <DialogContent\n        className={cn(\n          'top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0',\n          className\n        )}\n        showCloseButton={showCloseButton}\n      >\n        {children}\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nfunction CommandInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n  return (\n    <div className=\"p-1 pb-0\" data-slot=\"command-input-wrapper\">\n      <InputGroup className=\"h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!\">\n        <CommandPrimitive.Input\n          className={cn(\n            'w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',\n            className\n          )}\n          data-slot=\"command-input\"\n          {...props}\n        />\n        <InputGroupAddon>\n          <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n        </InputGroupAddon>\n      </InputGroup>\n    </div>\n  );\n}\n\nfunction CommandList({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) {\n  return (\n    <CommandPrimitive.List\n      className={cn(\n        'no-scrollbar max-h-72 scroll-py-1 overflow-y-auto overflow-x-hidden outline-none',\n        className\n      )}\n      data-slot=\"command-list\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandEmpty({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n  return (\n    <CommandPrimitive.Empty\n      className={cn('py-6 text-center text-sm', className)}\n      data-slot=\"command-empty\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n  return (\n    <CommandPrimitive.Group\n      className={cn(\n        'overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group-heading]]:text-xs',\n        className\n      )}\n      data-slot=\"command-group\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n  return (\n    <CommandPrimitive.Separator\n      className={cn('-mx-1 h-px bg-border', className)}\n      data-slot=\"command-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction CommandItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) {\n  return (\n    <CommandPrimitive.Item\n      className={cn(\n        \"group/command-item relative flex cursor-default select-none items-center gap-2 in-data-[slot=dialog-content]:rounded-lg! rounded-sm px-2 py-1.5 text-sm outline-hidden data-[disabled=true]:pointer-events-none data-selected:bg-muted data-selected:text-foreground data-[disabled=true]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-selected:*:[svg]:text-foreground\",\n        className\n      )}\n      data-slot=\"command-item\"\n      {...props}\n    >\n      {children}\n      <CheckIcon className=\"ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100\" />\n    </CommandPrimitive.Item>\n  );\n}\n\nfunction CommandShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-data-selected/command-item:text-foreground',\n        className\n      )}\n      data-slot=\"command-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Command,\n  CommandDialog,\n  CommandInput,\n  CommandList,\n  CommandEmpty,\n  CommandGroup,\n  CommandItem,\n  CommandShortcut,\n  CommandSeparator,\n};\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input-group.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/input-group.tsx",
          "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Textarea } from \"./textarea\";\nimport { cn } from \"../../lib/utils\";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input outline-none transition-colors in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',\n        className\n      )}\n      data-slot=\"input-group\"\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupAddonVariants = cva(\n  \"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      align: {\n        'inline-start':\n          'order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]',\n        'inline-end':\n          'order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]',\n        'block-start':\n          'order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2',\n        'block-end':\n          'order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2',\n      },\n    },\n    defaultVariants: {\n      align: 'inline-start',\n    },\n  }\n);\n\nfunction InputGroupAddon({\n  className,\n  align = 'inline-start',\n  ...props\n}: React.ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {\n  return (\n    <div\n      className={cn(inputGroupAddonVariants({ align }), className)}\n      data-align={align}\n      data-slot=\"input-group-addon\"\n      onClick={(e) => {\n        if ((e.target as HTMLElement).closest('button')) {\n          return;\n        }\n        e.currentTarget.parentElement?.querySelector('input')?.focus();\n      }}\n      role=\"group\"\n      {...props}\n    />\n  );\n}\n\nconst inputGroupButtonVariants = cva(\n  'flex items-center gap-2 text-sm shadow-none',\n  {\n    variants: {\n      size: {\n        xs: \"h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5\",\n        sm: '',\n        'icon-xs':\n          'size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0',\n        'icon-sm': 'size-8 p-0 has-[>svg]:p-0',\n      },\n    },\n    defaultVariants: {\n      size: 'xs',\n    },\n  }\n);\n\nfunction InputGroupButton({\n  className,\n  type = 'button',\n  variant = 'ghost',\n  size = 'xs',\n  ...props\n}: Omit<React.ComponentProps<typeof Button>, 'size' | 'type'> &\n  VariantProps<typeof inputGroupButtonVariants> & {\n    type?: 'button' | 'submit' | 'reset';\n  }) {\n  return (\n    <Button\n      className={cn(inputGroupButtonVariants({ size }), className)}\n      data-size={size}\n      type={type}\n      variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        \"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none\",\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupInput({\n  className,\n  ...props\n}: React.ComponentProps<'input'>) {\n  return (\n    <Input\n      className={cn(\n        'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nfunction InputGroupTextarea({\n  className,\n  ...props\n}: React.ComponentProps<'textarea'>) {\n  return (\n    <Textarea\n      className={cn(\n        'flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 disabled:bg-transparent aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent',\n        className\n      )}\n      data-slot=\"input-group-control\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupText,\n  InputGroupInput,\n  InputGroupTextarea,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/sheet.tsx",
          "content": "'use client';\n\nimport { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"sheet-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        className={cn(\n          'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sheet-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                className=\"absolute top-3 right-3\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 p-4', className)}\n      data-slot=\"sheet-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      data-slot=\"sheet-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      className={cn('font-medium text-base text-foreground', className)}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/sidebar.tsx",
          "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport { cn } from \"../../lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className\n        )}\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className\n        )}\n        data-slot=\"sidebar\"\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>\n        <SheetContent\n          className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n          data-mobile=\"true\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          dir={dir}\n          side={side}\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-side={side}\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-variant={variant}\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n        data-slot=\"sidebar-gap\"\n      />\n      <div\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sidebar-container\"\n        {...props}\n      >\n        <div\n          className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      className={cn(className)}\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      className={cn(\n        'group-data-[side=left]:-right-4 ltr:-translate-x-1/2 rtl:-translate-x-1/2 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      className={cn(\n        'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',\n        className\n      )}\n      data-slot=\"sidebar-inset\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      className={cn('h-8 w-full bg-background shadow-none', className)}\n      data-sidebar=\"input\"\n      data-slot=\"sidebar-input\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"header\"\n      data-slot=\"sidebar-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"footer\"\n      data-slot=\"sidebar-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      data-sidebar=\"separator\"\n      data-slot=\"sidebar-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      data-sidebar=\"content\"\n      data-slot=\"sidebar-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      data-sidebar=\"group\"\n      data-slot=\"sidebar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'group-data-[collapsible=icon]:-mt-8 flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label',\n    },\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action',\n    },\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('w-full text-sm', className)}\n      data-sidebar=\"group-content\"\n      data-slot=\"sidebar-group-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn('flex w-full min-w-0 flex-col gap-0', className)}\n      data-sidebar=\"menu\"\n      data-slot=\"sidebar-menu\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-item relative', className)}\n      data-sidebar=\"menu-item\"\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n      },\n      props\n    ),\n    render: tooltip ? <TooltipTrigger render={render} /> : render,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive,\n    },\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        side=\"right\"\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          showOnHover &&\n            'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action',\n    },\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',\n        className\n      )}\n      data-sidebar=\"menu-badge\"\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      data-sidebar=\"menu-skeleton\"\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      data-sidebar=\"menu-sub\"\n      data-slot=\"sidebar-menu-sub\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-sub-item relative', className)}\n      data-sidebar=\"menu-sub-item\"\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          '-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive,\n    },\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/skeleton.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('animate-pulse rounded-md bg-muted', className)}\n      data-slot=\"skeleton\"\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
        },
        {
          "path": "components/ui/textarea.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/textarea.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n  return (\n    <textarea\n      className={cn(\n        'field-sizing-content flex min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"textarea\"\n      {...props}\n    />\n  );\n}\n\nexport { Textarea };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/hooks/use-mobile.ts",
          "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/sidebar/sidebar-01/types.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-01/types.ts",
          "content": "import type { ElementType } from 'react';\n\nexport interface NavItem {\n  id: string;\n  title: string;\n  icon: ElementType;\n  url?: string;\n  isActive?: boolean;\n}\n\nexport interface User {\n  name: string;\n  email: string;\n  avatar: string;\n}\n\nexport interface FavoriteItem {\n  id: string;\n  title: string;\n  href: string;\n  color: string;\n}\n\nexport interface TeamItem {\n  id: string;\n  title: string;\n  icon: ElementType;\n}\n\nexport interface TopicItem {\n  id: string;\n  title: string;\n  icon: ElementType;\n}\n\nexport interface SidebarData {\n  user: User;\n  navMain: NavItem[];\n  navCollapsible: {\n    favorites: FavoriteItem[];\n    teams: TeamItem[];\n    topics: TopicItem[];\n  };\n}\n"
        }
      ],
      "categories": [
        "sidebar",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/sidebar-01/index.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/sidebar/sidebar-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "sidebar",
          "navigation",
          "shell",
          "dashboard",
          "侧栏",
          "导航",
          "布局",
          "后台"
        ],
        "contentGuidelines": [
          "Replace sample destinations and resource data with real routes.",
          "Keep top-level navigation short and verify the mobile drawer."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/sidebar-01/index.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/sidebar-01"
      }
    },
    {
      "name": "sidebar-02",
      "type": "registry:block",
      "title": "Sidebar Dashboard Inset",
      "description": "Sidebar Dashboard Inset. Application navigation shells with collapsible sidebars and responsive workspace layouts.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "framer-motion@13.5.1",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/sidebar/sidebar-02/app-sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/app-sidebar.tsx",
          "content": "'use client';\n\nimport { motion } from 'framer-motion';\nimport {\n  Activity,\n  DollarSign,\n  Home,\n  Infinity,\n  LinkIcon,\n  Package2,\n  Percent,\n  PieChart,\n  Settings,\n  ShoppingBag,\n  Sparkles,\n  Store,\n  TrendingUp,\n  Users,\n} from 'lucide-react';\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarHeader,\n  SidebarTrigger,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\nimport { cn } from \"./shared/lib/utils\";\nimport { Logo } from \"./logo\";\nimport type { Route } from \"./nav-main\";\nimport DashboardNavigation from \"./nav-main\";\nimport { NotificationsPopover } from \"./nav-notifications\";\nimport { TeamSwitcher } from \"./team-switcher\";\n\nconst sampleNotifications = [\n  {\n    id: '1',\n    avatar: 'https://uiblocks.org/avatar-01.png',\n    fallback: 'OM',\n    text: 'New order received.',\n    time: '10m ago',\n  },\n  {\n    id: '2',\n    avatar: 'https://uiblocks.org/avatar-02.jpg',\n    fallback: 'JL',\n    text: 'Server upgrade completed.',\n    time: '1h ago',\n  },\n  {\n    id: '3',\n    avatar: 'https://uiblocks.org/avatar-03.png',\n    fallback: 'HH',\n    text: 'New user signed up.',\n    time: '2h ago',\n  },\n];\n\nconst dashboardRoutes: Route[] = [\n  {\n    id: 'home',\n    title: 'Home',\n    icon: <Home className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'products',\n    title: 'Products',\n    icon: <Package2 className=\"size-4\" />,\n    link: '#',\n    subs: [\n      {\n        title: 'Catalogue',\n        link: '#',\n        icon: <Package2 className=\"size-4\" />,\n      },\n      {\n        title: 'Checkout Links',\n        link: '#',\n        icon: <LinkIcon className=\"size-4\" />,\n      },\n      {\n        title: 'Discounts',\n        link: '#',\n        icon: <Percent className=\"size-4\" />,\n      },\n    ],\n  },\n  {\n    id: 'usage-billing',\n    title: 'Usage Billing',\n    icon: <PieChart className=\"size-4\" />,\n    link: '#',\n    subs: [\n      {\n        title: 'Meters',\n        link: '#',\n        icon: <PieChart className=\"size-4\" />,\n      },\n      {\n        title: 'Events',\n        link: '#',\n        icon: <Activity className=\"size-4\" />,\n      },\n    ],\n  },\n  {\n    id: 'benefits',\n    title: 'Benefits',\n    icon: <Sparkles className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'customers',\n    title: 'Customers',\n    icon: <Users className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'sales',\n    title: 'Sales',\n    icon: <ShoppingBag className=\"size-4\" />,\n    link: '#',\n    subs: [\n      {\n        title: 'Orders',\n        link: '#',\n        icon: <ShoppingBag className=\"size-4\" />,\n      },\n      {\n        title: 'Subscriptions',\n        link: '#',\n        icon: <Infinity className=\"size-4\" />,\n      },\n    ],\n  },\n  {\n    id: 'storefront',\n    title: 'Storefront',\n    icon: <Store className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'analytics',\n    title: 'Analytics',\n    icon: <TrendingUp className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'finance',\n    title: 'Finance',\n    icon: <DollarSign className=\"size-4\" />,\n    link: '#',\n    subs: [\n      { title: 'Incoming', link: '#' },\n      { title: 'Outgoing', link: '#' },\n      { title: 'Payout Account', link: '#' },\n    ],\n  },\n  {\n    id: 'settings',\n    title: 'Settings',\n    icon: <Settings className=\"size-4\" />,\n    link: '#',\n    subs: [\n      { title: 'General', link: '#' },\n      { title: 'Webhooks', link: '#' },\n      { title: 'Custom Fields', link: '#' },\n    ],\n  },\n];\n\nconst teams = [\n  { id: '1', name: 'Alpha Inc.', logo: Logo, plan: 'Free' },\n  { id: '2', name: 'Beta Corp.', logo: Logo, plan: 'Free' },\n  { id: '3', name: 'Gamma Tech', logo: Logo, plan: 'Free' },\n];\n\nexport function DashboardSidebar() {\n  const { state } = useSidebar();\n  const isCollapsed = state === 'collapsed';\n\n  return (\n    <Sidebar collapsible=\"icon\" variant=\"inset\">\n      <SidebarHeader\n        className={cn(\n          'flex md:pt-3.5',\n          isCollapsed\n            ? 'flex-row items-center justify-between gap-y-4 md:flex-col md:items-start md:justify-start'\n            : 'flex-row items-center justify-between'\n        )}\n      >\n        <a className=\"flex items-center gap-2\" href=\"#\">\n          <Logo className=\"h-8 w-8\" />\n          {!isCollapsed && (\n            <span className=\"font-semibold text-black dark:text-white\">\n              Acme\n            </span>\n          )}\n        </a>\n\n        <motion.div\n          animate={{ opacity: 1 }}\n          className={cn(\n            'flex items-center gap-2',\n            isCollapsed ? 'flex-row md:flex-col-reverse' : 'flex-row'\n          )}\n          initial={{ opacity: 0 }}\n          key={isCollapsed ? 'header-collapsed' : 'header-expanded'}\n          transition={{ duration: 0.8 }}\n        >\n          <NotificationsPopover notifications={sampleNotifications} />\n          <SidebarTrigger />\n        </motion.div>\n      </SidebarHeader>\n      <SidebarContent className=\"gap-4 px-2 py-4\">\n        <DashboardNavigation routes={dashboardRoutes} />\n      </SidebarContent>\n      <SidebarFooter className=\"px-2\">\n        <TeamSwitcher teams={teams} />\n      </SidebarFooter>\n    </Sidebar>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-02/index.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/index.tsx",
          "content": "import { SidebarInset, SidebarProvider } from \"./shared/components/ui/sidebar\";\nimport { DashboardSidebar } from \"./app-sidebar\";\n\nexport default function Sidebar02() {\n  return (\n    <SidebarProvider>\n      <div className=\"relative flex h-dvh w-full\">\n        <DashboardSidebar />\n        <SidebarInset className=\"flex flex-col\" />\n      </div>\n    </SidebarProvider>\n  );\n}\n"
        },
        {
          "path": "licenses/sidebar-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/sidebar-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.2.0):\n- Replaced Next.js Link and route prefetching with standard anchors in the navigation component.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/sidebar/sidebar-02/logo.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/logo.tsx",
          "content": "import type { SVGProps } from 'react';\n\nexport const Logo = (props: SVGProps<SVGSVGElement>) => (\n  <svg\n    fill=\"currentColor\"\n    height=\"48\"\n    viewBox=\"0 0 40 48\"\n    width=\"40\"\n    {...props}\n  >\n    <clipPath id=\"a\">\n      <path d=\"m0 0h40v48h-40z\" />\n    </clipPath>\n    <g clipPath=\"url(#a)\">\n      <path d=\"m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z\" />\n      <path d=\"m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z\" />\n      <path d=\"m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z\" />\n      <path d=\"m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z\" />\n      <path d=\"m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z\" />\n      <path d=\"m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z\" />\n    </g>\n  </svg>\n);\n"
        },
        {
          "path": "content/components/sidebar/sidebar-02/nav-main.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/nav-main.tsx",
          "content": "\"use client\";\n\nimport { ChevronDown, ChevronUp } from \"lucide-react\";\nimport type React from \"react\";\nimport { useState } from \"react\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"./shared/components/ui/collapsible\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuItem as SidebarMenuSubItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport type Route = {\n  id: string;\n  title: string;\n  icon?: React.ReactNode;\n  link: string;\n  subs?: {\n    title: string;\n    link: string;\n    icon?: React.ReactNode;\n  }[];\n};\n\nexport default function DashboardNavigation({ routes }: { routes: Route[] }) {\n  const { state } = useSidebar();\n  const isCollapsed = state === \"collapsed\";\n  const [openCollapsible, setOpenCollapsible] = useState<string | null>(null);\n\n  return (\n    <SidebarMenu>\n      {routes.map((route) => {\n        const isOpen = !isCollapsed && openCollapsible === route.id;\n        const hasSubRoutes = !!route.subs?.length;\n\n        return (\n          <SidebarMenuItem key={route.id}>\n            {hasSubRoutes ? (\n              <Collapsible\n                className=\"w-full\"\n                onOpenChange={(open) =>\n                  setOpenCollapsible(open ? route.id : null)\n                }\n                open={isOpen}\n              >\n                <CollapsibleTrigger\n                  render={\n                    <SidebarMenuButton\n                      className={cn(\n                        \"flex w-full items-center rounded-lg px-2 transition-colors\",\n                        isOpen\n                          ? \"bg-sidebar-muted text-foreground\"\n                          : \"text-muted-foreground hover:bg-sidebar-muted hover:text-foreground\",\n                        isCollapsed && \"justify-center\"\n                      )}\n                    />\n                  }\n                >\n                  {route.icon}\n                  {!isCollapsed && (\n                    <span className=\"ml-2 flex-1 font-medium text-sm\">\n                      {route.title}\n                    </span>\n                  )}\n                  {!isCollapsed && hasSubRoutes && (\n                    <span className=\"ml-auto\">\n                      {isOpen ? (\n                        <ChevronUp className=\"size-4\" />\n                      ) : (\n                        <ChevronDown className=\"size-4\" />\n                      )}\n                    </span>\n                  )}\n                </CollapsibleTrigger>\n\n                {!isCollapsed && (\n                  <CollapsibleContent>\n                    <SidebarMenuSub className=\"my-1 ml-3.5 \">\n                      {route.subs?.map((subRoute) => (\n                        <SidebarMenuSubItem\n                          className=\"h-auto\"\n                          key={`${route.id}-${subRoute.title}`}\n                        >\n                          <SidebarMenuSubButton\n                            render={\n                              <a\n                                className=\"flex items-center rounded-md px-4 py-1.5 font-medium text-muted-foreground text-sm hover:bg-sidebar-muted hover:text-foreground\"\n                                href={subRoute.link}\n                              />\n                            }\n                          >\n                            {subRoute.title}\n                          </SidebarMenuSubButton>\n                        </SidebarMenuSubItem>\n                      ))}\n                    </SidebarMenuSub>\n                  </CollapsibleContent>\n                )}\n              </Collapsible>\n            ) : (\n              <SidebarMenuButton\n                render={\n                  <a\n                    className={cn(\n                      \"flex items-center rounded-lg px-2 text-muted-foreground transition-colors hover:bg-sidebar-muted hover:text-foreground\",\n                      isCollapsed && \"justify-center\"\n                    )}\n                    href={route.link}\n                  />\n                }\n                tooltip={route.title}\n              >\n                {route.icon}\n                {!isCollapsed && (\n                  <span className=\"ml-2 font-medium text-sm\">\n                    {route.title}\n                  </span>\n                )}\n              </SidebarMenuButton>\n            )}\n          </SidebarMenuItem>\n        );\n      })}\n    </SidebarMenu>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-02/nav-notifications.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/nav-notifications.tsx",
          "content": "'use client';\n\nimport { BellIcon } from 'lucide-react';\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\n\ntype Notification = {\n  id: string;\n  avatar: string;\n  fallback: string;\n  text: string;\n  time: string;\n};\n\nexport function NotificationsPopover({\n  notifications,\n}: {\n  notifications: Notification[];\n}) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            aria-label=\"Open notifications\"\n            className=\"rounded-full\"\n            size=\"icon\"\n            variant=\"ghost\"\n          />\n        }\n      >\n        <BellIcon className=\"size-5\" />\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"my-6 w-80\" side=\"right\">\n        <DropdownMenuLabel>Notifications</DropdownMenuLabel>\n        <DropdownMenuSeparator />\n        {notifications.map(({ id, avatar, fallback, text, time }) => (\n          <DropdownMenuItem className=\"flex items-start gap-3\" key={id}>\n            <Avatar className=\"size-8\">\n              <AvatarImage alt=\"Avatar\" src={avatar} />\n              <AvatarFallback>{fallback}</AvatarFallback>\n            </Avatar>\n            <div className=\"flex flex-col\">\n              <span className=\"font-medium text-sm\">{text}</span>\n              <span className=\"text-muted-foreground text-xs\">{time}</span>\n            </div>\n          </DropdownMenuItem>\n        ))}\n        <DropdownMenuSeparator />\n        <DropdownMenuItem className=\"justify-center text-muted-foreground text-sm hover:text-primary\">\n          View all notifications\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/collapsible.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/collapsible.tsx",
          "content": "'use client';\n\nimport { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible';\n\nfunction Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {\n  return (\n    <CollapsiblePrimitive.Trigger data-slot=\"collapsible-trigger\" {...props} />\n  );\n}\n\nfunction CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {\n  return (\n    <CollapsiblePrimitive.Panel data-slot=\"collapsible-content\" {...props} />\n  );\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/sheet.tsx",
          "content": "'use client';\n\nimport { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"sheet-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        className={cn(\n          'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sheet-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                className=\"absolute top-3 right-3\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 p-4', className)}\n      data-slot=\"sheet-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      data-slot=\"sheet-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      className={cn('font-medium text-base text-foreground', className)}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/sidebar.tsx",
          "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport { cn } from \"../../lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className\n        )}\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className\n        )}\n        data-slot=\"sidebar\"\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>\n        <SheetContent\n          className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n          data-mobile=\"true\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          dir={dir}\n          side={side}\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-side={side}\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-variant={variant}\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n        data-slot=\"sidebar-gap\"\n      />\n      <div\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sidebar-container\"\n        {...props}\n      >\n        <div\n          className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      className={cn(className)}\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      className={cn(\n        'group-data-[side=left]:-right-4 ltr:-translate-x-1/2 rtl:-translate-x-1/2 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      className={cn(\n        'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',\n        className\n      )}\n      data-slot=\"sidebar-inset\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      className={cn('h-8 w-full bg-background shadow-none', className)}\n      data-sidebar=\"input\"\n      data-slot=\"sidebar-input\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"header\"\n      data-slot=\"sidebar-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"footer\"\n      data-slot=\"sidebar-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      data-sidebar=\"separator\"\n      data-slot=\"sidebar-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      data-sidebar=\"content\"\n      data-slot=\"sidebar-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      data-sidebar=\"group\"\n      data-slot=\"sidebar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'group-data-[collapsible=icon]:-mt-8 flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label',\n    },\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action',\n    },\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('w-full text-sm', className)}\n      data-sidebar=\"group-content\"\n      data-slot=\"sidebar-group-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn('flex w-full min-w-0 flex-col gap-0', className)}\n      data-sidebar=\"menu\"\n      data-slot=\"sidebar-menu\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-item relative', className)}\n      data-sidebar=\"menu-item\"\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n      },\n      props\n    ),\n    render: tooltip ? <TooltipTrigger render={render} /> : render,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive,\n    },\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        side=\"right\"\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          showOnHover &&\n            'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action',\n    },\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',\n        className\n      )}\n      data-sidebar=\"menu-badge\"\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      data-sidebar=\"menu-skeleton\"\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      data-sidebar=\"menu-sub\"\n      data-slot=\"sidebar-menu-sub\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-sub-item relative', className)}\n      data-sidebar=\"menu-sub-item\"\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          '-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive,\n    },\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/skeleton.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('animate-pulse rounded-md bg-muted', className)}\n      data-slot=\"skeleton\"\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/hooks/use-mobile.ts",
          "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/sidebar/sidebar-02/team-switcher.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-02/team-switcher.tsx",
          "content": "'use client';\n\nimport { ChevronsUpDown, Plus } from 'lucide-react';\nimport * as React from 'react';\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\n\ntype Team = {\n  name: string;\n  logo: React.ElementType;\n  plan: string;\n};\n\nexport function TeamSwitcher({ teams }: { teams: Team[] }) {\n  const { isMobile } = useSidebar();\n  const [activeTeam, setActiveTeam] = React.useState(teams[0]);\n\n  if (!activeTeam) return null;\n\n  const Logo = activeTeam.logo;\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <SidebarMenuButton\n                className=\"data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground\"\n                size=\"lg\"\n              />\n            }\n          >\n            <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-background text-foreground\">\n              <Logo className=\"size-4\" />\n            </div>\n            <div className=\"grid flex-1 text-left text-sm leading-tight\">\n              <span className=\"truncate font-semibold\">{activeTeam.name}</span>\n              <span className=\"truncate text-xs\">{activeTeam.plan}</span>\n            </div>\n            <ChevronsUpDown className=\"ml-auto\" />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"start\"\n            className=\"mb-4 w-(--anchor-width) min-w-56 rounded-lg\"\n            side={isMobile ? 'bottom' : 'right'}\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n              Teams\n            </DropdownMenuLabel>\n            {teams.map((team, index) => (\n              <DropdownMenuItem\n                className=\"gap-2 p-2\"\n                key={team.name}\n                onClick={() => setActiveTeam(team)}\n              >\n                <div className=\"flex size-6 items-center justify-center rounded-sm border\">\n                  <team.logo className=\"size-4 shrink-0\" />\n                </div>\n                {team.name}\n                <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>\n              </DropdownMenuItem>\n            ))}\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className=\"gap-2 p-2\">\n              <div className=\"flex size-6 items-center justify-center rounded-md border bg-background\">\n                <Plus className=\"size-4\" />\n              </div>\n              <div className=\"font-medium text-muted-foreground\">Add team</div>\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n}\n"
        }
      ],
      "categories": [
        "sidebar",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/sidebar-02/index.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/sidebar/sidebar-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.2.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f",
          "modifications": [
            "Replaced Next.js Link and route prefetching with standard anchors in the navigation component."
          ]
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "sidebar",
          "navigation",
          "shell",
          "dashboard",
          "侧栏",
          "导航",
          "布局",
          "后台"
        ],
        "contentGuidelines": [
          "Replace sample destinations and resource data with real routes.",
          "Keep top-level navigation short and verify the mobile drawer."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/sidebar-02/index.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/sidebar-02"
      }
    },
    {
      "name": "sidebar-03",
      "type": "registry:block",
      "title": "Sidebar Dashboard Floating",
      "description": "Sidebar Dashboard Floating. Application navigation shells with collapsible sidebars and responsive workspace layouts.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "framer-motion@13.5.1",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/sidebar/sidebar-03/app-sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/app-sidebar.tsx",
          "content": "'use client';\n\nimport { motion } from 'framer-motion';\nimport {\n  Activity,\n  DollarSign,\n  Home,\n  Infinity,\n  LinkIcon,\n  Package2,\n  Percent,\n  PieChart,\n  Settings,\n  ShoppingBag,\n  Sparkles,\n  Store,\n  TrendingUp,\n  Users,\n} from 'lucide-react';\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarHeader,\n  SidebarTrigger,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\nimport { cn } from \"./shared/lib/utils\";\nimport { Logo } from \"./logo\";\nimport type { Route } from \"./nav-main\";\nimport DashboardNavigation from \"./nav-main\";\nimport { NotificationsPopover } from \"./nav-notifications\";\nimport { TeamSwitcher } from \"./team-switcher\";\n\nconst sampleNotifications = [\n  {\n    id: '1',\n    avatar: 'https://uiblocks.org/avatar-01.png',\n    fallback: 'OM',\n    text: 'New order received.',\n    time: '10m ago',\n  },\n  {\n    id: '2',\n    avatar: 'https://uiblocks.org/avatar-02.jpg',\n    fallback: 'JL',\n    text: 'Server upgrade completed.',\n    time: '1h ago',\n  },\n  {\n    id: '3',\n    avatar: 'https://uiblocks.org/avatar-03.png',\n    fallback: 'HH',\n    text: 'New user signed up.',\n    time: '2h ago',\n  },\n];\n\nconst dashboardRoutes: Route[] = [\n  {\n    id: 'home',\n    title: 'Home',\n    icon: <Home className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'products',\n    title: 'Products',\n    icon: <Package2 className=\"size-4\" />,\n    link: '#',\n    subs: [\n      {\n        title: 'Catalogue',\n        link: '#',\n        icon: <Package2 className=\"size-4\" />,\n      },\n      {\n        title: 'Checkout Links',\n        link: '#',\n        icon: <LinkIcon className=\"size-4\" />,\n      },\n      {\n        title: 'Discounts',\n        link: '#',\n        icon: <Percent className=\"size-4\" />,\n      },\n    ],\n  },\n  {\n    id: 'usage-billing',\n    title: 'Usage Billing',\n    icon: <PieChart className=\"size-4\" />,\n    link: '#',\n    subs: [\n      {\n        title: 'Meters',\n        link: '#',\n        icon: <PieChart className=\"size-4\" />,\n      },\n      {\n        title: 'Events',\n        link: '#',\n        icon: <Activity className=\"size-4\" />,\n      },\n    ],\n  },\n  {\n    id: 'benefits',\n    title: 'Benefits',\n    icon: <Sparkles className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'customers',\n    title: 'Customers',\n    icon: <Users className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'sales',\n    title: 'Sales',\n    icon: <ShoppingBag className=\"size-4\" />,\n    link: '#',\n    subs: [\n      {\n        title: 'Orders',\n        link: '#',\n        icon: <ShoppingBag className=\"size-4\" />,\n      },\n      {\n        title: 'Subscriptions',\n        link: '#',\n        icon: <Infinity className=\"size-4\" />,\n      },\n    ],\n  },\n  {\n    id: 'storefront',\n    title: 'Storefront',\n    icon: <Store className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'analytics',\n    title: 'Analytics',\n    icon: <TrendingUp className=\"size-4\" />,\n    link: '#',\n  },\n  {\n    id: 'finance',\n    title: 'Finance',\n    icon: <DollarSign className=\"size-4\" />,\n    link: '#',\n    subs: [\n      { title: 'Incoming', link: '#' },\n      { title: 'Outgoing', link: '#' },\n      { title: 'Payout Account', link: '#' },\n    ],\n  },\n  {\n    id: 'settings',\n    title: 'Settings',\n    icon: <Settings className=\"size-4\" />,\n    link: '#',\n    subs: [\n      { title: 'General', link: '#' },\n      { title: 'Webhooks', link: '#' },\n      { title: 'Custom Fields', link: '#' },\n    ],\n  },\n];\n\nconst teams = [\n  { id: '1', name: 'Alpha Inc.', logo: Logo, plan: 'Free' },\n  { id: '2', name: 'Beta Corp.', logo: Logo, plan: 'Free' },\n  { id: '3', name: 'Gamma Tech', logo: Logo, plan: 'Free' },\n];\n\nexport function DashboardSidebar() {\n  const { state } = useSidebar();\n  const isCollapsed = state === 'collapsed';\n\n  return (\n    <Sidebar collapsible=\"icon\" variant=\"floating\">\n      <SidebarHeader\n        className={cn(\n          'flex md:pt-3.5',\n          isCollapsed\n            ? 'flex-row items-center justify-between gap-y-4 md:flex-col md:items-start md:justify-start'\n            : 'flex-row items-center justify-between'\n        )}\n      >\n        <a className=\"flex items-center gap-2\" href=\"#\">\n          <Logo className=\"h-8 w-8\" />\n          {!isCollapsed && (\n            <span className=\"font-semibold text-black dark:text-white\">\n              Acme\n            </span>\n          )}\n        </a>\n\n        <motion.div\n          animate={{ opacity: 1 }}\n          className={cn(\n            'flex items-center gap-2',\n            isCollapsed ? 'flex-row md:flex-col-reverse' : 'flex-row'\n          )}\n          initial={{ opacity: 0 }}\n          key={isCollapsed ? 'header-collapsed' : 'header-expanded'}\n          transition={{ duration: 0.8 }}\n        >\n          <NotificationsPopover notifications={sampleNotifications} />\n          <SidebarTrigger />\n        </motion.div>\n      </SidebarHeader>\n      <SidebarContent className=\"gap-4 px-2 py-4\">\n        <DashboardNavigation routes={dashboardRoutes} />\n      </SidebarContent>\n      <SidebarFooter className=\"px-2\">\n        <TeamSwitcher teams={teams} />\n      </SidebarFooter>\n    </Sidebar>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-03/index.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/index.tsx",
          "content": "import { SidebarInset, SidebarProvider } from \"./shared/components/ui/sidebar\";\nimport { DashboardSidebar } from \"./app-sidebar\";\n\nexport default function Sidebar03() {\n  return (\n    <SidebarProvider>\n      <div className=\"relative flex h-dvh w-full\">\n        <DashboardSidebar />\n        <SidebarInset className=\"flex flex-col\" />\n      </div>\n    </SidebarProvider>\n  );\n}\n"
        },
        {
          "path": "licenses/sidebar-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/sidebar-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.2.0):\n- Replaced Next.js Link and route prefetching with standard anchors in the navigation component.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/sidebar/sidebar-03/logo.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/logo.tsx",
          "content": "import type { SVGProps } from 'react';\n\nexport const Logo = (props: SVGProps<SVGSVGElement>) => (\n  <svg\n    fill=\"currentColor\"\n    height=\"48\"\n    viewBox=\"0 0 40 48\"\n    width=\"40\"\n    {...props}\n  >\n    <title>Logo</title>\n    <clipPath id=\"a\">\n      <path d=\"m0 0h40v48h-40z\" />\n    </clipPath>\n    <g clipPath=\"url(#a)\">\n      <path d=\"m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z\" />\n      <path d=\"m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z\" />\n      <path d=\"m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z\" />\n      <path d=\"m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z\" />\n      <path d=\"m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z\" />\n      <path d=\"m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z\" />\n    </g>\n  </svg>\n);\n"
        },
        {
          "path": "content/components/sidebar/sidebar-03/nav-main.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/nav-main.tsx",
          "content": "\"use client\";\n\nimport { ChevronDown, ChevronUp } from \"lucide-react\";\nimport type React from \"react\";\nimport { useState } from \"react\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"./shared/components/ui/collapsible\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuItem as SidebarMenuSubItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\nimport { cn } from \"./shared/lib/utils\";\n\nexport type Route = {\n  id: string;\n  title: string;\n  icon?: React.ReactNode;\n  link: string;\n  subs?: {\n    title: string;\n    link: string;\n    icon?: React.ReactNode;\n  }[];\n};\n\nexport default function DashboardNavigation({ routes }: { routes: Route[] }) {\n  const { state } = useSidebar();\n  const isCollapsed = state === \"collapsed\";\n  const [openCollapsible, setOpenCollapsible] = useState<string | null>(null);\n\n  return (\n    <SidebarMenu>\n      {routes.map((route) => {\n        const isOpen = !isCollapsed && openCollapsible === route.id;\n        const hasSubRoutes = !!route.subs?.length;\n\n        return (\n          <SidebarMenuItem key={route.id}>\n            {hasSubRoutes ? (\n              <Collapsible\n                className=\"w-full\"\n                onOpenChange={(open) =>\n                  setOpenCollapsible(open ? route.id : null)\n                }\n                open={isOpen}\n              >\n                <CollapsibleTrigger\n                  render={\n                    <SidebarMenuButton\n                      className={cn(\n                        \"flex w-full items-center rounded-lg px-2 transition-colors\",\n                        isOpen\n                          ? \"bg-sidebar-muted text-foreground\"\n                          : \"text-muted-foreground hover:bg-sidebar-muted hover:text-foreground\",\n                        isCollapsed && \"justify-center\"\n                      )}\n                    />\n                  }\n                >\n                  {route.icon}\n                  {!isCollapsed && (\n                    <span className=\"ml-2 flex-1 font-medium text-sm\">\n                      {route.title}\n                    </span>\n                  )}\n                  {!isCollapsed && hasSubRoutes && (\n                    <span className=\"ml-auto\">\n                      {isOpen ? (\n                        <ChevronUp className=\"size-4\" />\n                      ) : (\n                        <ChevronDown className=\"size-4\" />\n                      )}\n                    </span>\n                  )}\n                </CollapsibleTrigger>\n\n                {!isCollapsed && (\n                  <CollapsibleContent>\n                    <SidebarMenuSub className=\"my-1 ml-3.5 \">\n                      {route.subs?.map((subRoute) => (\n                        <SidebarMenuSubItem\n                          className=\"h-auto\"\n                          key={`${route.id}-${subRoute.title}`}\n                        >\n                          <SidebarMenuSubButton\n                            render={\n                              <a\n                                className=\"flex items-center rounded-md px-4 py-1.5 font-medium text-muted-foreground text-sm hover:bg-sidebar-muted hover:text-foreground\"\n                                href={subRoute.link}\n                              />\n                            }\n                          >\n                            {subRoute.title}\n                          </SidebarMenuSubButton>\n                        </SidebarMenuSubItem>\n                      ))}\n                    </SidebarMenuSub>\n                  </CollapsibleContent>\n                )}\n              </Collapsible>\n            ) : (\n              <SidebarMenuButton\n                render={\n                  <a\n                    className={cn(\n                      \"flex items-center rounded-lg px-2 text-muted-foreground transition-colors hover:bg-sidebar-muted hover:text-foreground\",\n                      isCollapsed && \"justify-center\"\n                    )}\n                    href={route.link}\n                  />\n                }\n                tooltip={route.title}\n              >\n                {route.icon}\n                {!isCollapsed && (\n                  <span className=\"ml-2 font-medium text-sm\">\n                    {route.title}\n                  </span>\n                )}\n              </SidebarMenuButton>\n            )}\n          </SidebarMenuItem>\n        );\n      })}\n    </SidebarMenu>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-03/nav-notifications.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/nav-notifications.tsx",
          "content": "'use client';\n\nimport { BellIcon } from 'lucide-react';\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\n\ntype Notification = {\n  id: string;\n  avatar: string;\n  fallback: string;\n  text: string;\n  time: string;\n};\n\nexport function NotificationsPopover({\n  notifications,\n}: {\n  notifications: Notification[];\n}) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            aria-label=\"Open notifications\"\n            className=\"rounded-full\"\n            size=\"icon\"\n            variant=\"ghost\"\n          />\n        }\n      >\n        <BellIcon className=\"size-5\" />\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"my-6 w-80\" side=\"right\">\n        <DropdownMenuLabel>Notifications</DropdownMenuLabel>\n        <DropdownMenuSeparator />\n        {notifications.map(({ id, avatar, fallback, text, time }) => (\n          <DropdownMenuItem className=\"flex items-start gap-3\" key={id}>\n            <Avatar className=\"size-8\">\n              <AvatarImage alt=\"Avatar\" src={avatar} />\n              <AvatarFallback>{fallback}</AvatarFallback>\n            </Avatar>\n            <div className=\"flex flex-col\">\n              <span className=\"font-medium text-sm\">{text}</span>\n              <span className=\"text-muted-foreground text-xs\">{time}</span>\n            </div>\n          </DropdownMenuItem>\n        ))}\n        <DropdownMenuSeparator />\n        <DropdownMenuItem className=\"justify-center text-muted-foreground text-sm hover:text-primary\">\n          View all notifications\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/collapsible.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/collapsible.tsx",
          "content": "'use client';\n\nimport { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible';\n\nfunction Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {\n  return (\n    <CollapsiblePrimitive.Trigger data-slot=\"collapsible-trigger\" {...props} />\n  );\n}\n\nfunction CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {\n  return (\n    <CollapsiblePrimitive.Panel data-slot=\"collapsible-content\" {...props} />\n  );\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/sheet.tsx",
          "content": "'use client';\n\nimport { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"sheet-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        className={cn(\n          'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sheet-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                className=\"absolute top-3 right-3\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 p-4', className)}\n      data-slot=\"sheet-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      data-slot=\"sheet-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      className={cn('font-medium text-base text-foreground', className)}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/sidebar.tsx",
          "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport { cn } from \"../../lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className\n        )}\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className\n        )}\n        data-slot=\"sidebar\"\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>\n        <SheetContent\n          className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n          data-mobile=\"true\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          dir={dir}\n          side={side}\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-side={side}\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-variant={variant}\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n        data-slot=\"sidebar-gap\"\n      />\n      <div\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sidebar-container\"\n        {...props}\n      >\n        <div\n          className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      className={cn(className)}\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      className={cn(\n        'group-data-[side=left]:-right-4 ltr:-translate-x-1/2 rtl:-translate-x-1/2 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      className={cn(\n        'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',\n        className\n      )}\n      data-slot=\"sidebar-inset\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      className={cn('h-8 w-full bg-background shadow-none', className)}\n      data-sidebar=\"input\"\n      data-slot=\"sidebar-input\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"header\"\n      data-slot=\"sidebar-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"footer\"\n      data-slot=\"sidebar-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      data-sidebar=\"separator\"\n      data-slot=\"sidebar-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      data-sidebar=\"content\"\n      data-slot=\"sidebar-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      data-sidebar=\"group\"\n      data-slot=\"sidebar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'group-data-[collapsible=icon]:-mt-8 flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label',\n    },\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action',\n    },\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('w-full text-sm', className)}\n      data-sidebar=\"group-content\"\n      data-slot=\"sidebar-group-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn('flex w-full min-w-0 flex-col gap-0', className)}\n      data-sidebar=\"menu\"\n      data-slot=\"sidebar-menu\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-item relative', className)}\n      data-sidebar=\"menu-item\"\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n      },\n      props\n    ),\n    render: tooltip ? <TooltipTrigger render={render} /> : render,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive,\n    },\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        side=\"right\"\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          showOnHover &&\n            'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action',\n    },\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',\n        className\n      )}\n      data-sidebar=\"menu-badge\"\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      data-sidebar=\"menu-skeleton\"\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      data-sidebar=\"menu-sub\"\n      data-slot=\"sidebar-menu-sub\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-sub-item relative', className)}\n      data-sidebar=\"menu-sub-item\"\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          '-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive,\n    },\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/skeleton.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('animate-pulse rounded-md bg-muted', className)}\n      data-slot=\"skeleton\"\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/hooks/use-mobile.ts",
          "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/sidebar/sidebar-03/team-switcher.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-03/team-switcher.tsx",
          "content": "'use client';\n\nimport { ChevronsUpDown, Plus } from 'lucide-react';\nimport * as React from 'react';\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\n\ntype Team = {\n  name: string;\n  logo: React.ElementType;\n  plan: string;\n};\n\nexport function TeamSwitcher({ teams }: { teams: Team[] }) {\n  const { isMobile } = useSidebar();\n  const [activeTeam, setActiveTeam] = React.useState(teams[0]);\n\n  if (!activeTeam) return null;\n\n  const Logo = activeTeam.logo;\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <SidebarMenuButton\n                className=\"data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground\"\n                size=\"lg\"\n              />\n            }\n          >\n            <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-background text-foreground\">\n              <Logo className=\"size-4\" />\n            </div>\n            <div className=\"grid flex-1 text-left text-sm leading-tight\">\n              <span className=\"truncate font-semibold\">{activeTeam.name}</span>\n              <span className=\"truncate text-xs\">{activeTeam.plan}</span>\n            </div>\n            <ChevronsUpDown className=\"ml-auto\" />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"start\"\n            className=\"mb-4 w-(--anchor-width) min-w-56 rounded-lg\"\n            side={isMobile ? 'bottom' : 'right'}\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n              Teams\n            </DropdownMenuLabel>\n            {teams.map((team, index) => (\n              <DropdownMenuItem\n                className=\"gap-2 p-2\"\n                key={team.name}\n                onClick={() => setActiveTeam(team)}\n              >\n                <div className=\"flex size-6 items-center justify-center rounded-sm border\">\n                  <team.logo className=\"size-4 shrink-0\" />\n                </div>\n                {team.name}\n                <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>\n              </DropdownMenuItem>\n            ))}\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className=\"gap-2 p-2\">\n              <div className=\"flex size-6 items-center justify-center rounded-md border bg-background\">\n                <Plus className=\"size-4\" />\n              </div>\n              <div className=\"font-medium text-muted-foreground\">Add team</div>\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n}\n"
        }
      ],
      "categories": [
        "sidebar",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/sidebar-03/index.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/sidebar/sidebar-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.2.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f",
          "modifications": [
            "Replaced Next.js Link and route prefetching with standard anchors in the navigation component."
          ]
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "sidebar",
          "navigation",
          "shell",
          "dashboard",
          "侧栏",
          "导航",
          "布局",
          "后台"
        ],
        "contentGuidelines": [
          "Replace sample destinations and resource data with real routes.",
          "Keep top-level navigation short and verify the mobile drawer."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/sidebar-03/index.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/sidebar-03"
      }
    },
    {
      "name": "sidebar-04",
      "type": "registry:block",
      "title": "Sidebar Double-Sided Mail",
      "description": "Sidebar Double-Sided Mail. Application navigation shells with collapsible sidebars and responsive workspace layouts.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/sidebar/sidebar-04/app-sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/app-sidebar.tsx",
          "content": "'use client';\n\nimport {\n  IconBrandAmongUs,\n  IconCarambola,\n  IconHourglassHigh,\n  IconMailbox,\n} from '@tabler/icons-react';\nimport { Archive, Flag } from 'lucide-react';\nimport * as React from 'react';\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\nimport { useMail } from \"./mail-context\";\nimport { NavUser } from \"./nav-user\";\n\n// This is sample data\nconst data = {\n  user: {\n    name: 'ephraim',\n    email: 'ephraim@blocks.so',\n    avatar: 'https://uiblocks.org/avatar-01.png',\n  },\n  navMain: [\n    {\n      title: 'Inbox',\n      url: '#',\n      icon: IconMailbox,\n      isActive: true,\n    },\n    {\n      title: 'Starred',\n      url: '#',\n      icon: IconCarambola,\n      isActive: false,\n    },\n    {\n      title: 'Important',\n      url: '#',\n      icon: Flag,\n      isActive: false,\n    },\n    {\n      title: 'Scheduled',\n      url: '#',\n      icon: IconHourglassHigh,\n      isActive: false,\n    },\n    {\n      title: 'Archive',\n      url: '#',\n      icon: Archive,\n      isActive: false,\n    },\n  ],\n  labels: [\n    { title: 'Personal', color: 'bg-green-400 dark:bg-green-300' },\n    { title: 'Work', color: 'bg-blue-400 dark:bg-blue-300' },\n    { title: 'Travel', color: 'bg-orange-400 dark:bg-orange-300' },\n    { title: 'Receipts', color: 'bg-purple-400 dark:bg-purple-300' },\n  ],\n  mails: [\n    {\n      name: 'Nora Patel',\n      email: 'nora@acme.co',\n      subject: 'Welcome to Acme Mail',\n      date: '08:15 AM',\n      teaser:\n        \"Hi there — here's a quick tour of your new inbox.\\nPin, label, and schedule messages to stay organized.\",\n    },\n    {\n      name: 'Stripe',\n      email: 'no-reply@stripe.com',\n      subject: 'Your payout has arrived',\n      date: 'Yesterday',\n      teaser:\n        'A payout of $3,245.90 was sent to your bank account.\\nView details in your dashboard.',\n    },\n    {\n      name: 'GitHub',\n      email: 'noreply@github.com',\n      subject: 'New activity on acme/app',\n      date: 'Tue',\n      teaser:\n        '3 pull requests need your review. CI passed on main.\\nClick to open the review queue.',\n    },\n    {\n      name: 'Ava Chen',\n      email: 'ava.chen@example.com',\n      subject: 'Agenda for Friday standup',\n      date: 'Mon',\n      teaser:\n        \"Let's cover onboarding, billing bugs, and Q4 goals.\\nReply with anything you want to add.\",\n    },\n    {\n      name: 'Figma',\n      email: 'updates@figma.com',\n      subject: \"What's new in Figma\",\n      date: 'Sep 12',\n      teaser:\n        'Variables, auto layout improvements, and dev mode updates.\\nWatch the recap to learn more.',\n    },\n    {\n      name: 'Linear',\n      email: 'bot@linear.app',\n      subject: '[ACME-432] Edit modal broken',\n      date: 'Sep 11',\n      teaser:\n        'Issue created by Wendy. Repro steps included.\\nSeverity: high, priority: P1.',\n    },\n    {\n      name: 'Airbnb',\n      email: 'booking@airbnb.com',\n      subject: 'Your trip to Kyoto',\n      date: 'Sep 10',\n      teaser:\n        'Get ready for your stay. Check-in details and local recommendations inside.',\n    },\n    {\n      name: 'Notion',\n      email: 'team@notion.so',\n      subject: 'Weekly team recap',\n      date: 'Sep 09',\n      teaser:\n        'Marketing shipped pricing page revamp. Eng closed 14 issues.\\nSee full notes in the doc.',\n    },\n    {\n      name: 'Google Calendar',\n      email: 'calendar@google.com',\n      subject: 'Design sync moved',\n      date: 'Sep 09',\n      teaser:\n        \"Event 'Design sync' moved to 3:30 PM. Meet link updated in the invite.\",\n    },\n    {\n      name: 'HN Daily',\n      email: 'digest@ycombinator.com',\n      subject: 'Top stories today',\n      date: 'Sep 08',\n      teaser:\n        'OpenAI releases new API, SQLite 3.46, and why we left Kubernetes.\\nRead the full digest.',\n    },\n  ],\n};\n\nexport function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {\n  // Note: I'm using state to show active item.\n  // IRL you should use the url/router.\n  const [activeItem, setActiveItem] = React.useState(data.navMain[0]);\n  const [mails, setMails] = React.useState(data.mails);\n  const [query, setQuery] = React.useState('');\n  const { setOpen } = useSidebar();\n  const { setSelectedMail } = useMail();\n\n  const filteredMails = React.useMemo(() => {\n    const q = query.trim().toLowerCase();\n    if (!q) return mails;\n    return mails.filter((m) =>\n      [m.name, m.email, m.subject, m.teaser]\n        .join('\\n')\n        .toLowerCase()\n        .includes(q)\n    );\n  }, [mails, query]);\n\n  return (\n    <div className=\"flex\">\n      {/* This is the first sidebar */}\n      <Sidebar\n        className=\"border-r p-2 px-1\"\n        collapsible=\"none\"\n        style={{ '--sidebar-width': '12rem' } as React.CSSProperties}\n      >\n        <SidebarHeader>\n          <SidebarMenu>\n            <SidebarMenuItem>\n              <SidebarMenuButton\n                className=\"md:h-8 md:p-0\"\n                render={<a href=\"#\" />}\n                size=\"lg\"\n              >\n                <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-accent text-sidebar-accent-foreground\">\n                  <IconBrandAmongUs className=\"size-4\" />\n                </div>\n                <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                  <span className=\"truncate font-medium\">Acme Inc</span>\n                  <span className=\"truncate text-xs\">Enterprise</span>\n                </div>\n              </SidebarMenuButton>\n            </SidebarMenuItem>\n          </SidebarMenu>\n        </SidebarHeader>\n        <SidebarContent>\n          <SidebarGroup>\n            <SidebarGroupContent className=\"px-1.5 md:px-0\">\n              <SidebarMenu>\n                {data.navMain.map((item) => (\n                  <SidebarMenuItem key={item.title}>\n                    <SidebarMenuButton\n                      className=\"px-2.5 md:px-2\"\n                      isActive={activeItem?.title === item.title}\n                      onClick={() => {\n                        setActiveItem(item);\n                        const mail = data.mails.sort(() => Math.random() - 0.5);\n                        setMails(\n                          mail.slice(\n                            0,\n                            Math.max(5, Math.floor(Math.random() * 10) + 1)\n                          )\n                        );\n                        setOpen(true);\n                      }}\n                    >\n                      <item.icon />\n                      <span>{item.title}</span>\n                    </SidebarMenuButton>\n                  </SidebarMenuItem>\n                ))}\n              </SidebarMenu>\n            </SidebarGroupContent>\n          </SidebarGroup>\n          <SidebarGroup>\n            <SidebarGroupLabel className=\"px-1.5 text-xs md:px-0\">\n              Labels\n            </SidebarGroupLabel>\n            <SidebarGroupContent className=\"px-1.5 md:px-0\">\n              <SidebarMenu>\n                {data.labels.map((label) => (\n                  <SidebarMenuItem key={label.title}>\n                    <SidebarMenuButton\n                      className=\"px-2.5 md:px-2\"\n                      render={<div className=\"flex items-center gap-3\" />}\n                    >\n                      <div className={`h-3 w-3 rounded ${label.color}`} />\n                      <span>{label.title}</span>\n                    </SidebarMenuButton>\n                  </SidebarMenuItem>\n                ))}\n              </SidebarMenu>\n            </SidebarGroupContent>\n          </SidebarGroup>\n        </SidebarContent>\n        <SidebarFooter>\n          <NavUser user={data.user} />\n        </SidebarFooter>\n      </Sidebar>\n\n      {/* This is the second sidebar */}\n      {/* We disable collapsible and let it fill remaining space */}\n      <Sidebar\n        className=\"hidden min-w-96 flex-1 border-r md:flex\"\n        collapsible=\"none\"\n      >\n        <SidebarHeader className=\"gap-3.5 border-b p-4\">\n          <div className=\"flex w-full items-center justify-between\">\n            <div className=\"font-medium text-base text-foreground\">\n              {activeItem?.title}\n            </div>\n          </div>\n          <SidebarInput\n            onChange={(e) => setQuery(e.target.value)}\n            placeholder=\"Type to search...\"\n            value={query}\n          />\n        </SidebarHeader>\n        <SidebarContent>\n          <SidebarGroup className=\"px-0 pt-0\">\n            <SidebarGroupContent>\n              {filteredMails.length === 0 && (\n                <div className=\"p-4 text-muted-foreground text-sm\">\n                  No results\n                </div>\n              )}\n              {filteredMails.map((mail) => (\n                <a\n                  className=\"flex flex-col items-start gap-2 whitespace-nowrap border-b p-4 text-sm leading-tight hover:bg-sidebar-accent hover:text-sidebar-accent-foreground\"\n                  href=\"#\"\n                  key={mail.email}\n                  onClick={(e) => {\n                    e.preventDefault();\n                    setSelectedMail(mail);\n                  }}\n                >\n                  <div className=\"flex w-full items-center gap-2\">\n                    <span>{mail.name}</span>{' '}\n                    <span className=\"ml-auto text-xs\">{mail.date}</span>\n                  </div>\n                  <span className=\"font-medium\">{mail.subject}</span>\n                  <span className=\"line-clamp-2 w-[260px] whitespace-break-spaces text-xs\">\n                    {mail.teaser}\n                  </span>\n                </a>\n              ))}\n            </SidebarGroupContent>\n          </SidebarGroup>\n        </SidebarContent>\n      </Sidebar>\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-04/app/page.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/app/page.tsx",
          "content": "'use client';\n\nimport { SidebarInset, SidebarProvider } from \"../shared/components/ui/sidebar\";\nimport { AppSidebar } from \"../app-sidebar\";\nimport { MailProvider, useMail } from \"../mail-context\";\n\nfunction MailPreview() {\n  const { selectedMail } = useMail();\n  if (!selectedMail) {\n    return (\n      <div className=\"flex h-full items-center justify-center text-muted-foreground text-sm\">\n        Select an email to preview\n      </div>\n    );\n  }\n  return (\n    <div className=\"flex h-full flex-col\">\n      <div className=\"border-b p-4\">\n        <div className=\"font-semibold text-lg\">{selectedMail.subject}</div>\n        <div className=\"text-muted-foreground text-sm\">\n          From: {selectedMail.name} ({selectedMail.email})\n        </div>\n      </div>\n      <div className=\"whitespace-pre-wrap p-4 text-sm\">{`${selectedMail.teaser}\\n\\nThis is a sample message body for the selected email.\\nIt demonstrates the preview area on the right side.`}</div>\n    </div>\n  );\n}\n\nexport default function Page() {\n  return (\n    <SidebarProvider>\n      <MailProvider>\n        <div className=\"flex h-dvh w-full\">\n          <AppSidebar />\n          <SidebarInset>\n            <div className=\"m-2 flex-1 rounded-xl border\">\n              <MailPreview />\n            </div>\n          </SidebarInset>\n        </div>\n      </MailProvider>\n    </SidebarProvider>\n  );\n}\n"
        },
        {
          "path": "licenses/sidebar-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/sidebar-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/sidebar/sidebar-04/mail-context.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/mail-context.tsx",
          "content": "'use client';\n\nimport * as React from 'react';\n\nexport type Mail = {\n  name: string;\n  email: string;\n  subject: string;\n  date: string;\n  teaser: string;\n  labels?: string[];\n};\n\ntype MailContextValue = {\n  selectedMail: Mail | null;\n  setSelectedMail: (mail: Mail | null) => void;\n};\n\nconst MailContext = React.createContext<MailContextValue | undefined>(\n  undefined\n);\n\nexport function MailProvider({ children }: { children: React.ReactNode }) {\n  const [selectedMail, setSelectedMail] = React.useState<Mail | null>(null);\n  const value = React.useMemo(\n    () => ({ selectedMail, setSelectedMail }),\n    [selectedMail]\n  );\n  return <MailContext.Provider value={value}>{children}</MailContext.Provider>;\n}\n\nexport function useMail() {\n  const ctx = React.useContext(MailContext);\n  if (!ctx) throw new Error('useMail must be used within MailProvider');\n  return ctx;\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-04/nav-user.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/nav-user.tsx",
          "content": "'use client';\n\nimport {\n  BadgeCheck,\n  Bell,\n  ChevronsUpDown,\n  CreditCard,\n  LogOut,\n  Sparkles,\n} from 'lucide-react';\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\n\nexport function NavUser({\n  user,\n}: {\n  user: {\n    name: string;\n    email: string;\n    avatar: string;\n  };\n}) {\n  const { isMobile } = useSidebar();\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <SidebarMenuButton\n                className=\"data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground md:h-8 md:p-0\"\n                size=\"lg\"\n              />\n            }\n          >\n            <Avatar className=\"h-8 w-8 rounded-lg\">\n              <AvatarImage alt={user.name} src={user.avatar} />\n              <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n            </Avatar>\n            <div className=\"grid flex-1 text-left text-sm leading-tight\">\n              <span className=\"truncate font-medium\">{user.name}</span>\n              <span className=\"truncate text-xs\">{user.email}</span>\n            </div>\n            <ChevronsUpDown className=\"ml-auto size-4\" />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"end\"\n            className=\"w-(--anchor-width) min-w-56 rounded-lg\"\n            side={isMobile ? 'bottom' : 'right'}\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"p-0 font-normal\">\n              <div className=\"flex items-center gap-2 px-1 py-1.5 text-left text-sm\">\n                <Avatar className=\"h-8 w-8 rounded-lg\">\n                  <AvatarImage alt={user.name} src={user.avatar} />\n                  <AvatarFallback className=\"rounded-lg\">CN</AvatarFallback>\n                </Avatar>\n                <div className=\"grid flex-1 text-left text-sm leading-tight\">\n                  <span className=\"truncate font-medium\">{user.name}</span>\n                  <span className=\"truncate text-xs\">{user.email}</span>\n                </div>\n              </div>\n            </DropdownMenuLabel>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup>\n              <DropdownMenuItem>\n                <Sparkles />\n                Upgrade to Pro\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup>\n              <DropdownMenuItem>\n                <BadgeCheck />\n                Account\n              </DropdownMenuItem>\n              <DropdownMenuItem>\n                <CreditCard />\n                Billing\n              </DropdownMenuItem>\n              <DropdownMenuItem>\n                <Bell />\n                Notifications\n              </DropdownMenuItem>\n            </DropdownMenuGroup>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem>\n              <LogOut />\n              Log out\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n}\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/sheet.tsx",
          "content": "'use client';\n\nimport { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"sheet-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        className={cn(\n          'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sheet-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                className=\"absolute top-3 right-3\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 p-4', className)}\n      data-slot=\"sheet-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      data-slot=\"sheet-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      className={cn('font-medium text-base text-foreground', className)}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/sidebar.tsx",
          "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport { cn } from \"../../lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className\n        )}\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className\n        )}\n        data-slot=\"sidebar\"\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>\n        <SheetContent\n          className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n          data-mobile=\"true\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          dir={dir}\n          side={side}\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-side={side}\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-variant={variant}\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n        data-slot=\"sidebar-gap\"\n      />\n      <div\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sidebar-container\"\n        {...props}\n      >\n        <div\n          className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      className={cn(className)}\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      className={cn(\n        'group-data-[side=left]:-right-4 ltr:-translate-x-1/2 rtl:-translate-x-1/2 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      className={cn(\n        'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',\n        className\n      )}\n      data-slot=\"sidebar-inset\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      className={cn('h-8 w-full bg-background shadow-none', className)}\n      data-sidebar=\"input\"\n      data-slot=\"sidebar-input\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"header\"\n      data-slot=\"sidebar-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"footer\"\n      data-slot=\"sidebar-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      data-sidebar=\"separator\"\n      data-slot=\"sidebar-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      data-sidebar=\"content\"\n      data-slot=\"sidebar-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      data-sidebar=\"group\"\n      data-slot=\"sidebar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'group-data-[collapsible=icon]:-mt-8 flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label',\n    },\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action',\n    },\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('w-full text-sm', className)}\n      data-sidebar=\"group-content\"\n      data-slot=\"sidebar-group-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn('flex w-full min-w-0 flex-col gap-0', className)}\n      data-sidebar=\"menu\"\n      data-slot=\"sidebar-menu\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-item relative', className)}\n      data-sidebar=\"menu-item\"\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n      },\n      props\n    ),\n    render: tooltip ? <TooltipTrigger render={render} /> : render,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive,\n    },\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        side=\"right\"\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          showOnHover &&\n            'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action',\n    },\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',\n        className\n      )}\n      data-sidebar=\"menu-badge\"\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      data-sidebar=\"menu-skeleton\"\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      data-sidebar=\"menu-sub\"\n      data-slot=\"sidebar-menu-sub\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-sub-item relative', className)}\n      data-sidebar=\"menu-sub-item\"\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          '-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive,\n    },\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/skeleton.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('animate-pulse rounded-md bg-muted', className)}\n      data-slot=\"skeleton\"\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/hooks/use-mobile.ts",
          "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        }
      ],
      "categories": [
        "sidebar",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/sidebar-04/app/page.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/sidebar/sidebar-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "sidebar",
          "navigation",
          "shell",
          "dashboard",
          "侧栏",
          "导航",
          "布局",
          "后台"
        ],
        "contentGuidelines": [
          "Replace sample destinations and resource data with real routes.",
          "Keep top-level navigation short and verify the mobile drawer."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/sidebar-04/app/page.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/sidebar-04"
      }
    },
    {
      "name": "sidebar-05",
      "type": "registry:block",
      "title": "Sidebar Double-Sided",
      "description": "Sidebar Double-Sided. Application navigation shells with collapsible sidebars and responsive workspace layouts.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/sidebar/sidebar-05/app-sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/app-sidebar.tsx",
          "content": "'use client';\n\nimport {\n  IconActivityHeartbeat,\n  IconArchive,\n  IconBackground,\n  IconBellRinging,\n  IconBrandGoogle,\n  IconBrandMeta,\n  IconBrandNpm,\n  IconBrandOpenai,\n  IconBug,\n  IconChartBar,\n  IconChevronRight,\n  IconCloud,\n  IconDatabase,\n  IconFileText,\n  IconFolder,\n  IconFolders,\n  IconGitCommit,\n  IconGitMerge,\n  IconGitPullRequest,\n  IconHome,\n  IconKey,\n  IconLockExclamation,\n  IconLockPassword,\n  IconLogout,\n  IconNorthStar,\n  IconPackageExport,\n  IconPackages,\n  IconPasswordFingerprint,\n  IconPlayerPlay,\n  IconScanEye,\n  IconSettings,\n  IconShieldLock,\n  IconStar,\n  IconTarget,\n  IconTerminal2,\n  IconUser,\n  IconUserPlus,\n  IconWebhook,\n  IconX,\n} from '@tabler/icons-react';\nimport type React from 'react';\nimport { useState } from 'react';\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarHeader,\n  SidebarMenu,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"./shared/components/ui/sidebar\";\nimport { cn } from \"./shared/lib/utils\";\nimport { TeamSwitcher } from \"./team-switcher\";\n\nconst data = {\n  teams: [\n    {\n      name: 'OpenAI',\n      logo: IconBrandOpenai,\n      plan: 'Enterprise',\n    },\n    {\n      name: 'Anthropic',\n      logo: IconNorthStar,\n      plan: 'Pro',\n    },\n    {\n      name: 'Google',\n      logo: IconBrandGoogle,\n      plan: 'Free',\n    },\n    {\n      name: 'Meta',\n      logo: IconBrandMeta,\n      plan: 'Free',\n    },\n  ],\n};\n\ninterface SidebarItem {\n  id: string;\n  label: string;\n  icon: React.ComponentType<{ className?: string }>;\n  badge?: string;\n  route?: string;\n  subItems?: {\n    id: string;\n    label: string;\n    icon: React.ComponentType<{ className?: string }>;\n    description?: string;\n    route?: string;\n  }[];\n}\n\nconst sidebarItems: SidebarItem[] = [\n  {\n    id: 'overview',\n    label: 'Overview',\n    icon: IconHome,\n    subItems: [\n      {\n        id: 'dashboard',\n        label: 'Dashboard',\n        icon: IconChartBar,\n        description: 'Project overview and activity',\n        route: '/overview/dashboard',\n      },\n      {\n        id: 'activity',\n        label: 'Activity',\n        icon: IconActivityHeartbeat,\n        description: 'Recent commits and changes',\n        route: '/overview/activity',\n      },\n      {\n        id: 'insights',\n        label: 'Insights',\n        icon: IconTarget,\n        description: 'Code analytics and metrics',\n        route: '/overview/insights',\n      },\n    ],\n  },\n  {\n    id: 'repositories',\n    label: 'Repositories',\n    icon: IconFolders,\n    badge: '12',\n    subItems: [\n      {\n        id: 'all-repos',\n        label: 'All Repositories',\n        icon: IconFolder,\n        description: 'Browse all your repositories',\n        route: '/repositories',\n      },\n      {\n        id: 'starred',\n        label: 'Starred',\n        icon: IconStar,\n        description: 'Your starred repositories',\n        route: '/repositories/starred',\n      },\n      {\n        id: 'archived',\n        label: 'Archived',\n        icon: IconArchive,\n        description: 'Archived repositories',\n        route: '/repositories/archived',\n      },\n    ],\n  },\n  {\n    id: 'pull-requests',\n    label: 'Pull Requests',\n    icon: IconGitPullRequest,\n    badge: '3',\n    subItems: [\n      {\n        id: 'open-prs',\n        label: 'Open',\n        icon: IconGitPullRequest,\n        description: 'Open pull requests',\n        route: '/pull-requests/open',\n      },\n      {\n        id: 'review-requests',\n        label: 'Review Requests',\n        icon: IconScanEye,\n        description: 'PRs awaiting your review',\n        route: '/pull-requests/review',\n      },\n      {\n        id: 'merged',\n        label: 'Merged',\n        icon: IconGitMerge,\n        description: 'Recently merged PRs',\n        route: '/pull-requests/merged',\n      },\n    ],\n  },\n  {\n    id: 'issues',\n    label: 'Issues',\n    icon: IconBug,\n    badge: '7',\n    subItems: [\n      {\n        id: 'open-issues',\n        label: 'Open Issues',\n        icon: IconBug,\n        description: 'Active issues and bugs',\n        route: '/issues/open',\n      },\n      {\n        id: 'assigned',\n        label: 'Assigned to Me',\n        icon: IconUserPlus,\n        description: 'Issues assigned to you',\n        route: '/issues/assigned',\n      },\n      {\n        id: 'created',\n        label: 'Created by Me',\n        icon: IconGitCommit,\n        description: \"Issues you've created\",\n        route: '/issues/created',\n      },\n    ],\n  },\n  {\n    id: 'actions',\n    label: 'Actions',\n    icon: IconBackground,\n    subItems: [\n      {\n        id: 'workflows',\n        label: 'Workflows',\n        icon: IconPlayerPlay,\n        description: 'CI/CD workflows and pipelines',\n        route: '/actions/workflows',\n      },\n      {\n        id: 'runners',\n        label: 'Runners',\n        icon: IconTerminal2,\n        description: 'Self-hosted runners',\n        route: '/actions/runners',\n      },\n      {\n        id: 'deployments',\n        label: 'Deployments',\n        icon: IconCloud,\n        description: 'Deployment history',\n        route: '/actions/deployments',\n      },\n    ],\n  },\n  {\n    id: 'packages',\n    label: 'Packages',\n    icon: IconPackages,\n    subItems: [\n      {\n        id: 'published',\n        label: 'Published',\n        icon: IconPackageExport,\n        description: 'Your published packages',\n        route: '/packages/published',\n      },\n      {\n        id: 'container-registry',\n        label: 'Container Registry',\n        icon: IconDatabase,\n        description: 'Docker images and containers',\n        route: '/packages/containers',\n      },\n      {\n        id: 'npm-packages',\n        label: 'npm Packages',\n        icon: IconBrandNpm,\n        description: 'Node.js packages',\n        route: '/packages/npm',\n      },\n    ],\n  },\n  {\n    id: 'security',\n    label: 'Security',\n    icon: IconLockPassword,\n    badge: '2',\n    subItems: [\n      {\n        id: 'alerts',\n        label: 'Security Alerts',\n        icon: IconLockExclamation,\n        description: 'Vulnerability alerts',\n        route: '/security/alerts',\n      },\n      {\n        id: 'advisories',\n        label: 'Advisories',\n        icon: IconShieldLock,\n        description: 'Security advisories',\n        route: '/security/advisories',\n      },\n      {\n        id: 'secrets',\n        label: 'Secrets',\n        icon: IconPasswordFingerprint,\n        description: 'Repository secrets',\n        route: '/security/secrets',\n      },\n    ],\n  },\n  {\n    id: 'settings',\n    label: 'Settings',\n    icon: IconSettings,\n    subItems: [\n      {\n        id: 'profile',\n        label: 'Profile',\n        icon: IconUser,\n        description: 'Your profile settings',\n        route: '/settings/profile',\n      },\n      {\n        id: 'notifications',\n        label: 'Notifications',\n        icon: IconBellRinging,\n        description: 'Notification preferences',\n        route: '/settings/notifications',\n      },\n      {\n        id: 'webhooks',\n        label: 'Webhooks',\n        icon: IconWebhook,\n        description: 'Webhook configurations',\n        route: '/settings/webhooks',\n      },\n      {\n        id: 'api-keys',\n        label: 'API Keys',\n        icon: IconKey,\n        description: 'Personal access tokens',\n        route: '/settings/api-keys',\n      },\n    ],\n  },\n  {\n    id: 'docs',\n    label: 'Documentation',\n    icon: IconFileText,\n    route: '/docs',\n  },\n];\n\nexport function AppSidebar() {\n  const [activeItem, setActiveItem] = useState<string | null>('overview');\n  const [selectedSubItem, setSelectedSubItem] = useState<string | null>(null);\n\n  const activeItemData = sidebarItems.find((item) => item.id === activeItem);\n\n  const handleItemClick = (item: SidebarItem) => {\n    if (item.subItems) {\n      const isActive = activeItem === item.id;\n      setActiveItem(isActive ? null : item.id);\n      if (isActive) {\n        setSelectedSubItem(null);\n      }\n    } else {\n      if (activeItem) {\n        setActiveItem(null);\n        setSelectedSubItem(null);\n      }\n      console.log(`[v0] Navigating to: ${item.route}`);\n    }\n  };\n\n  const handleSubItemClick = (subItem: { id: string; route?: string }) => {\n    setSelectedSubItem(selectedSubItem === subItem.id ? null : subItem.id);\n    if (subItem.route) {\n      console.log(`[v0] Navigating to: ${subItem.route}`);\n    }\n  };\n\n  return (\n    <div className=\"flex h-dvh bg-background\">\n      <Sidebar\n        className=\"w-64 border-r\"\n        collapsible=\"none\"\n        side=\"left\"\n        variant=\"sidebar\"\n      >\n        <SidebarHeader>\n          <TeamSwitcher teams={data.teams} />\n        </SidebarHeader>\n\n        <SidebarContent>\n          <SidebarGroup>\n            <SidebarGroupContent>\n              <SidebarMenu>\n                {sidebarItems.map((item) => {\n                  const Icon = item.icon;\n                  const isActive = activeItem === item.id;\n                  const chevronIndicator = (\n                    <IconChevronRight\n                      className={cn(\n                        'h-4 w-4 shrink-0 transition-transform',\n                        isActive && 'rotate-90'\n                      )}\n                    />\n                  );\n\n                  return (\n                    <SidebarMenuItem key={item.id}>\n                      <SidebarMenuButton\n                        className=\"h-10 w-full px-3\"\n                        isActive={isActive}\n                        onClick={() => handleItemClick(item)}\n                      >\n                        <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n                          <Icon className=\"h-4 w-4 shrink-0\" />\n                          <span className=\"truncate\">{item.label}</span>\n                        </div>\n                        <div className=\"ml-auto flex min-w-fit shrink-0 items-center gap-1\">\n                          {(item.badge || item.subItems) &&\n                            (item.badge ? (\n                              <SidebarMenuBadge\n                                className={cn(\n                                  'min-w-fit',\n                                  item.subItems && 'gap-x-3'\n                                )}\n                              >\n                                {item.badge}\n                                {item.subItems && chevronIndicator}\n                              </SidebarMenuBadge>\n                            ) : (\n                              chevronIndicator\n                            ))}\n                        </div>\n                      </SidebarMenuButton>\n                    </SidebarMenuItem>\n                  );\n                })}\n              </SidebarMenu>\n            </SidebarGroupContent>\n          </SidebarGroup>\n        </SidebarContent>\n\n        <SidebarFooter>\n          <SidebarMenu>\n            <SidebarMenuItem>\n              <SidebarMenuButton className=\"h-12 w-full px-3\">\n                <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n                  <Avatar className=\"h-8 w-8 rounded-full\">\n                    <AvatarImage alt=\"ephraim\" src=\"https://uiblocks.org/avatar-01.png\" />\n                    <AvatarFallback className=\"rounded-full\">E</AvatarFallback>\n                  </Avatar>\n                  <div className=\"min-w-0 flex-1 text-left\">\n                    <div className=\"truncate font-medium text-sm\">ephraim</div>\n                    <div className=\"truncate text-muted-foreground text-xs\">\n                      ephraim@blocks.so\n                    </div>\n                  </div>\n                </div>\n                <IconLogout className=\"h-4 w-4 shrink-0\" />\n              </SidebarMenuButton>\n            </SidebarMenuItem>\n          </SidebarMenu>\n        </SidebarFooter>\n      </Sidebar>\n\n      {activeItem && activeItemData?.subItems && (\n        <Sidebar\n          className=\"slide-in-from-left-5 w-72 animate-in border-r duration-200\"\n          collapsible=\"none\"\n          side=\"left\"\n          variant=\"sidebar\"\n        >\n          <SidebarHeader className=\"flex flex-row items-center justify-between border-b px-4\">\n            <h3 className=\"font-medium\">{activeItemData.label}</h3>\n            <button\n              className=\"flex h-6 w-6 items-center justify-center rounded-md p-0 hover:bg-sidebar-accent\"\n              onClick={() => setActiveItem(null)}\n            >\n              <IconX className=\"h-4 w-4\" />\n            </button>\n          </SidebarHeader>\n\n          <SidebarContent>\n            <SidebarGroup>\n              <SidebarGroupContent>\n                <SidebarMenu>\n                  {activeItemData.subItems.map((subItem) => {\n                    const SubIcon = subItem.icon;\n                    const isSelected = selectedSubItem === subItem.id;\n\n                    return (\n                      <SidebarMenuItem key={subItem.id}>\n                        <SidebarMenuButton\n                          className=\"h-auto w-full justify-start gap-3 px-3 py-2\"\n                          isActive={isSelected}\n                          onClick={() => handleSubItemClick(subItem)}\n                        >\n                          <SubIcon className=\"mt-0.5 h-5 w-5 shrink-0 self-start\" />\n\n                          <div className=\"min-w-0 flex-1 text-left\">\n                            <div className=\"font-medium\">{subItem.label}</div>\n                            {subItem.description && (\n                              <div className=\"mt-0.5 text-muted-foreground text-xs\">\n                                {subItem.description}\n                              </div>\n                            )}\n                          </div>\n                        </SidebarMenuButton>\n                      </SidebarMenuItem>\n                    );\n                  })}\n                </SidebarMenu>\n              </SidebarGroupContent>\n            </SidebarGroup>\n          </SidebarContent>\n        </Sidebar>\n      )}\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-05/app/page.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/app/page.tsx",
          "content": "'use client';\n\nimport { SidebarProvider } from \"../shared/components/ui/sidebar\";\nimport { AppSidebar } from \"../app-sidebar\";\n\nexport default function Page() {\n  return (\n    <SidebarProvider>\n      <div className=\"flex h-dvh w-full\">\n        <AppSidebar />\n      </div>\n    </SidebarProvider>\n  );\n}\n"
        },
        {
          "path": "licenses/sidebar-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/sidebar-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/sheet.tsx",
          "content": "'use client';\n\nimport { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"sheet-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        className={cn(\n          'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sheet-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                className=\"absolute top-3 right-3\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 p-4', className)}\n      data-slot=\"sheet-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      data-slot=\"sheet-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      className={cn('font-medium text-base text-foreground', className)}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/sidebar.tsx",
          "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport { cn } from \"../../lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className\n        )}\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className\n        )}\n        data-slot=\"sidebar\"\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>\n        <SheetContent\n          className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n          data-mobile=\"true\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          dir={dir}\n          side={side}\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-side={side}\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-variant={variant}\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n        data-slot=\"sidebar-gap\"\n      />\n      <div\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sidebar-container\"\n        {...props}\n      >\n        <div\n          className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      className={cn(className)}\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      className={cn(\n        'group-data-[side=left]:-right-4 ltr:-translate-x-1/2 rtl:-translate-x-1/2 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      className={cn(\n        'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',\n        className\n      )}\n      data-slot=\"sidebar-inset\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      className={cn('h-8 w-full bg-background shadow-none', className)}\n      data-sidebar=\"input\"\n      data-slot=\"sidebar-input\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"header\"\n      data-slot=\"sidebar-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"footer\"\n      data-slot=\"sidebar-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      data-sidebar=\"separator\"\n      data-slot=\"sidebar-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      data-sidebar=\"content\"\n      data-slot=\"sidebar-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      data-sidebar=\"group\"\n      data-slot=\"sidebar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'group-data-[collapsible=icon]:-mt-8 flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label',\n    },\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action',\n    },\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('w-full text-sm', className)}\n      data-sidebar=\"group-content\"\n      data-slot=\"sidebar-group-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn('flex w-full min-w-0 flex-col gap-0', className)}\n      data-sidebar=\"menu\"\n      data-slot=\"sidebar-menu\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-item relative', className)}\n      data-sidebar=\"menu-item\"\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n      },\n      props\n    ),\n    render: tooltip ? <TooltipTrigger render={render} /> : render,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive,\n    },\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        side=\"right\"\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          showOnHover &&\n            'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action',\n    },\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',\n        className\n      )}\n      data-sidebar=\"menu-badge\"\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      data-sidebar=\"menu-skeleton\"\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      data-sidebar=\"menu-sub\"\n      data-slot=\"sidebar-menu-sub\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-sub-item relative', className)}\n      data-sidebar=\"menu-sub-item\"\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          '-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive,\n    },\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/skeleton.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('animate-pulse rounded-md bg-muted', className)}\n      data-slot=\"skeleton\"\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/hooks/use-mobile.ts",
          "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/sidebar/sidebar-05/team-switcher.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-05/team-switcher.tsx",
          "content": "'use client';\n\nimport { ChevronsUpDown, Plus } from 'lucide-react';\nimport * as React from 'react';\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\n\nexport function TeamSwitcher({\n  teams,\n}: {\n  teams: {\n    name: string;\n    logo: React.ElementType;\n    plan: string;\n  }[];\n}) {\n  const { isMobile } = useSidebar();\n  const [activeTeam, setActiveTeam] = React.useState(teams[0]);\n\n  if (!activeTeam) {\n    return null;\n  }\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <SidebarMenuButton\n                className=\"data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground\"\n                size=\"lg\"\n              />\n            }\n          >\n            <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-accent text-sidebar-accent-foreground\">\n              <activeTeam.logo className=\"size-4\" />\n            </div>\n            <div className=\"grid flex-1 text-left text-sm leading-tight\">\n              <span className=\"truncate font-medium\">{activeTeam.name}</span>\n              <span className=\"truncate text-xs\">{activeTeam.plan}</span>\n            </div>\n            <ChevronsUpDown className=\"ml-auto\" />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"start\"\n            className=\"w-(--anchor-width) min-w-56 rounded-lg\"\n            side={isMobile ? 'bottom' : 'right'}\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n              Teams\n            </DropdownMenuLabel>\n            {teams.map((team, index) => (\n              <DropdownMenuItem\n                className=\"gap-2 p-2\"\n                key={team.name}\n                onClick={() => setActiveTeam(team)}\n              >\n                <div className=\"flex size-6 items-center justify-center rounded-md border\">\n                  <team.logo className=\"size-3.5 shrink-0\" />\n                </div>\n                {team.name}\n                <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>\n              </DropdownMenuItem>\n            ))}\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className=\"gap-2 p-2\">\n              <div className=\"flex size-6 items-center justify-center rounded-md border bg-transparent\">\n                <Plus className=\"size-4\" />\n              </div>\n              <div className=\"font-medium text-muted-foreground\">Add team</div>\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n}\n"
        }
      ],
      "categories": [
        "sidebar",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/sidebar-05/app/page.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/sidebar/sidebar-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "sidebar",
          "navigation",
          "shell",
          "dashboard",
          "侧栏",
          "导航",
          "布局",
          "后台"
        ],
        "contentGuidelines": [
          "Replace sample destinations and resource data with real routes.",
          "Keep top-level navigation short and verify the mobile drawer."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/sidebar-05/app/page.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/sidebar-05"
      }
    },
    {
      "name": "sidebar-06",
      "type": "registry:block",
      "title": "Sidebar Replacement",
      "description": "Sidebar Replacement. Application navigation shells with collapsible sidebars and responsive workspace layouts.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tabler/icons-react@3.50.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "content/components/sidebar/sidebar-06/app-sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/app-sidebar.tsx",
          "content": "'use client';\n\nimport {\n  IconActivityHeartbeat,\n  IconArchive,\n  IconArrowLeft,\n  IconBackground,\n  IconBellRinging,\n  IconBrandGoogle,\n  IconBrandMeta,\n  IconBrandNpm,\n  IconBrandOpenai,\n  IconBug,\n  IconChartBar,\n  IconChevronRight,\n  IconCloud,\n  IconDatabase,\n  IconFileText,\n  IconFolder,\n  IconFolders,\n  IconGitCommit,\n  IconGitMerge,\n  IconGitPullRequest,\n  IconHome,\n  IconKey,\n  IconLockExclamation,\n  IconLockPassword,\n  IconLogout,\n  IconNorthStar,\n  IconPackageExport,\n  IconPackages,\n  IconPasswordFingerprint,\n  IconPlayerPlay,\n  IconScanEye,\n  IconSettings,\n  IconShieldLock,\n  IconStar,\n  IconTarget,\n  IconTerminal2,\n  IconUser,\n  IconUserPlus,\n  IconWebhook,\n} from '@tabler/icons-react';\nimport type React from 'react';\nimport { useState } from 'react';\nimport { Avatar, AvatarFallback, AvatarImage } from \"./shared/components/ui/avatar\";\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupContent,\n  SidebarHeader,\n  SidebarMenu,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n} from \"./shared/components/ui/sidebar\";\nimport { cn } from \"./shared/lib/utils\";\nimport { TeamSwitcher } from \"./team-switcher\";\n\nconst data = {\n  teams: [\n    {\n      name: 'OpenAI',\n      logo: IconBrandOpenai,\n      plan: 'Enterprise',\n    },\n    {\n      name: 'Anthropic',\n      logo: IconNorthStar,\n      plan: 'Pro',\n    },\n    {\n      name: 'Google',\n      logo: IconBrandGoogle,\n      plan: 'Free',\n    },\n    {\n      name: 'Meta',\n      logo: IconBrandMeta,\n      plan: 'Free',\n    },\n  ],\n};\n\ninterface SidebarItem {\n  id: string;\n  label: string;\n  icon: React.ComponentType<{ className?: string }>;\n  badge?: string;\n  route?: string;\n  subItems?: {\n    id: string;\n    label: string;\n    icon: React.ComponentType<{ className?: string }>;\n    route?: string;\n  }[];\n}\n\nconst sidebarItems: SidebarItem[] = [\n  {\n    id: 'overview',\n    label: 'Overview',\n    icon: IconHome,\n    subItems: [\n      {\n        id: 'dashboard',\n        label: 'Dashboard',\n        icon: IconChartBar,\n        route: '/overview/dashboard',\n      },\n      {\n        id: 'activity',\n        label: 'Activity',\n        icon: IconActivityHeartbeat,\n        route: '/overview/activity',\n      },\n      {\n        id: 'insights',\n        label: 'Insights',\n        icon: IconTarget,\n        route: '/overview/insights',\n      },\n    ],\n  },\n  {\n    id: 'repositories',\n    label: 'Repositories',\n    icon: IconFolders,\n    badge: '12',\n    subItems: [\n      {\n        id: 'all-repos',\n        label: 'All Repositories',\n        icon: IconFolder,\n        route: '/repositories',\n      },\n      {\n        id: 'starred',\n        label: 'Starred',\n        icon: IconStar,\n        route: '/repositories/starred',\n      },\n      {\n        id: 'archived',\n        label: 'Archived',\n        icon: IconArchive,\n        route: '/repositories/archived',\n      },\n    ],\n  },\n  {\n    id: 'pull-requests',\n    label: 'Pull Requests',\n    icon: IconGitPullRequest,\n    badge: '3',\n    subItems: [\n      {\n        id: 'open-prs',\n        label: 'Open',\n        icon: IconGitPullRequest,\n        route: '/pull-requests/open',\n      },\n      {\n        id: 'review-requests',\n        label: 'Review Requests',\n        icon: IconScanEye,\n        route: '/pull-requests/review',\n      },\n      {\n        id: 'merged',\n        label: 'Merged',\n        icon: IconGitMerge,\n        route: '/pull-requests/merged',\n      },\n    ],\n  },\n  {\n    id: 'issues',\n    label: 'Issues',\n    icon: IconBug,\n    badge: '7',\n    subItems: [\n      {\n        id: 'open-issues',\n        label: 'Open Issues',\n        icon: IconBug,\n        route: '/issues/open',\n      },\n      {\n        id: 'assigned',\n        label: 'Assigned to Me',\n        icon: IconUserPlus,\n        route: '/issues/assigned',\n      },\n      {\n        id: 'created',\n        label: 'Created by Me',\n        icon: IconGitCommit,\n        route: '/issues/created',\n      },\n    ],\n  },\n  {\n    id: 'actions',\n    label: 'Actions',\n    icon: IconBackground,\n    subItems: [\n      {\n        id: 'workflows',\n        label: 'Workflows',\n        icon: IconPlayerPlay,\n        route: '/actions/workflows',\n      },\n      {\n        id: 'runners',\n        label: 'Runners',\n        icon: IconTerminal2,\n        route: '/actions/runners',\n      },\n      {\n        id: 'deployments',\n        label: 'Deployments',\n        icon: IconCloud,\n        route: '/actions/deployments',\n      },\n    ],\n  },\n  {\n    id: 'packages',\n    label: 'Packages',\n    icon: IconPackages,\n    subItems: [\n      {\n        id: 'published',\n        label: 'Published',\n        icon: IconPackageExport,\n        route: '/packages/published',\n      },\n      {\n        id: 'container-registry',\n        label: 'Container Registry',\n        icon: IconDatabase,\n        route: '/packages/containers',\n      },\n      {\n        id: 'npm-packages',\n        label: 'npm Packages',\n        icon: IconBrandNpm,\n        route: '/packages/npm',\n      },\n    ],\n  },\n  {\n    id: 'security',\n    label: 'Security',\n    icon: IconLockPassword,\n    badge: '2',\n    subItems: [\n      {\n        id: 'alerts',\n        label: 'Security Alerts',\n        icon: IconLockExclamation,\n        route: '/security/alerts',\n      },\n      {\n        id: 'advisories',\n        label: 'Advisories',\n        icon: IconShieldLock,\n        route: '/security/advisories',\n      },\n      {\n        id: 'secrets',\n        label: 'Secrets',\n        icon: IconPasswordFingerprint,\n        route: '/security/secrets',\n      },\n    ],\n  },\n  {\n    id: 'settings',\n    label: 'Settings',\n    icon: IconSettings,\n    subItems: [\n      {\n        id: 'profile',\n        label: 'Profile',\n        icon: IconUser,\n        route: '/settings/profile',\n      },\n      {\n        id: 'notifications',\n        label: 'Notifications',\n        icon: IconBellRinging,\n        route: '/settings/notifications',\n      },\n      {\n        id: 'webhooks',\n        label: 'Webhooks',\n        icon: IconWebhook,\n        route: '/settings/webhooks',\n      },\n      {\n        id: 'api-keys',\n        label: 'API Keys',\n        icon: IconKey,\n        route: '/settings/api-keys',\n      },\n    ],\n  },\n  {\n    id: 'docs',\n    label: 'Documentation',\n    icon: IconFileText,\n    route: '/docs',\n  },\n];\n\nexport function AppSidebar() {\n  const [activeItem, setActiveItem] = useState<string | null>(null);\n  const [selectedSubItem, setSelectedSubItem] = useState<string | null>(null);\n\n  const activeItemData = sidebarItems.find((item) => item.id === activeItem);\n\n  const handleItemClick = (item: SidebarItem) => {\n    if (item.subItems) {\n      setActiveItem(item.id);\n      setSelectedSubItem(null);\n    } else {\n      console.log(`[v0] Navigating to: ${item.route}`);\n    }\n  };\n\n  const handleSubItemClick = (subItem: { id: string; route?: string }) => {\n    setSelectedSubItem(selectedSubItem === subItem.id ? null : subItem.id);\n    if (subItem.route) {\n      console.log(`[v0] Navigating to: ${subItem.route}`);\n    }\n  };\n\n  const handleBackToMain = () => {\n    setActiveItem(null);\n    setSelectedSubItem(null);\n  };\n\n  return (\n    <div className=\"flex h-dvh bg-background\">\n      <Sidebar\n        className=\"w-64 border-r\"\n        collapsible=\"none\"\n        side=\"left\"\n        variant=\"sidebar\"\n      >\n        {activeItem ? (\n          activeItemData?.subItems && (\n            <>\n              <SidebarHeader className=\"flex flex-row items-center justify-between border-b px-4\">\n                <button\n                  className=\"flex h-8 w-8 items-center justify-center rounded-md p-0 hover:bg-sidebar-accent\"\n                  onClick={handleBackToMain}\n                >\n                  <IconArrowLeft className=\"h-4 w-4\" />\n                </button>\n                <h3 className=\"flex-1 text-center font-medium\">\n                  {activeItemData.label}\n                </h3>\n                <div className=\"w-8\" />\n              </SidebarHeader>\n\n              <SidebarContent>\n                <SidebarGroup>\n                  <SidebarGroupContent>\n                    <SidebarMenu>\n                      {activeItemData.subItems.map((subItem) => {\n                        const SubIcon = subItem.icon;\n                        const isSelected = selectedSubItem === subItem.id;\n\n                        return (\n                          <SidebarMenuItem key={subItem.id}>\n                            <SidebarMenuButton\n                              className=\"h-10 w-full px-3\"\n                              isActive={isSelected}\n                              onClick={() => handleSubItemClick(subItem)}\n                            >\n                              <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n                                <SubIcon className=\"h-4 w-4 shrink-0\" />\n                                <span className=\"truncate\">\n                                  {subItem.label}\n                                </span>\n                              </div>\n                            </SidebarMenuButton>\n                          </SidebarMenuItem>\n                        );\n                      })}\n                    </SidebarMenu>\n                  </SidebarGroupContent>\n                </SidebarGroup>\n              </SidebarContent>\n            </>\n          )\n        ) : (\n          <>\n            <SidebarHeader>\n              <TeamSwitcher teams={data.teams} />\n            </SidebarHeader>\n\n            <SidebarContent>\n              <SidebarGroup>\n                <SidebarGroupContent>\n                  <SidebarMenu>\n                    {sidebarItems.map((item) => {\n                      const Icon = item.icon;\n                      const chevronIndicator = (\n                        <IconChevronRight className=\"h-4 w-4 shrink-0 transition-transform\" />\n                      );\n\n                      return (\n                        <SidebarMenuItem key={item.id}>\n                          <SidebarMenuButton\n                            className=\"h-10 w-full px-3\"\n                            onClick={() => handleItemClick(item)}\n                          >\n                            <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n                              <Icon className=\"h-4 w-4 shrink-0\" />\n                              <span className=\"truncate\">{item.label}</span>\n                            </div>\n                            <div className=\"ml-auto flex min-w-fit shrink-0 items-center gap-1\">\n                              {(item.badge || item.subItems) &&\n                                (item.badge ? (\n                                  <SidebarMenuBadge\n                                    className={cn(\n                                      'min-w-fit',\n                                      item.subItems && 'gap-x-3'\n                                    )}\n                                  >\n                                    {item.badge}\n                                    {item.subItems && chevronIndicator}\n                                  </SidebarMenuBadge>\n                                ) : (\n                                  chevronIndicator\n                                ))}\n                            </div>\n                          </SidebarMenuButton>\n                        </SidebarMenuItem>\n                      );\n                    })}\n                  </SidebarMenu>\n                </SidebarGroupContent>\n              </SidebarGroup>\n            </SidebarContent>\n\n            <SidebarFooter>\n              <SidebarMenu>\n                <SidebarMenuItem>\n                  <SidebarMenuButton className=\"h-12 w-full px-3\">\n                    <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n                      <Avatar className=\"h-8 w-8 rounded-full\">\n                        <AvatarImage alt=\"ephraim\" src=\"https://uiblocks.org/avatar-01.png\" />\n                        <AvatarFallback className=\"rounded-full\">\n                          E\n                        </AvatarFallback>\n                      </Avatar>\n                      <div className=\"min-w-0 flex-1 text-left\">\n                        <div className=\"truncate font-medium text-sm\">\n                          ephraim\n                        </div>\n                        <div className=\"truncate text-muted-foreground text-xs\">\n                          ephraim@blocks.so\n                        </div>\n                      </div>\n                    </div>\n                    <IconLogout className=\"h-4 w-4 shrink-0\" />\n                  </SidebarMenuButton>\n                </SidebarMenuItem>\n              </SidebarMenu>\n            </SidebarFooter>\n          </>\n        )}\n      </Sidebar>\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/sidebar/sidebar-06/app/page.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/app/page.tsx",
          "content": "'use client';\n\nimport { SidebarProvider } from \"../shared/components/ui/sidebar\";\nimport { AppSidebar } from \"../app-sidebar\";\n\nexport default function Page() {\n  return (\n    <SidebarProvider>\n      <div className=\"flex h-dvh w-full\">\n        <AppSidebar />\n      </div>\n    </SidebarProvider>\n  );\n}\n"
        },
        {
          "path": "licenses/sidebar-06/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/sidebar-06/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/separator.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/separator.tsx",
          "content": "'use client';\n\nimport { Separator as SeparatorPrimitive } from '@base-ui/react/separator';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Separator({\n  className,\n  orientation = 'horizontal',\n  ...props\n}: SeparatorPrimitive.Props) {\n  return (\n    <SeparatorPrimitive\n      className={cn(\n        'shrink-0 self-center bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px',\n        className\n      )}\n      data-slot=\"separator\"\n      orientation={orientation}\n      {...props}\n    />\n  );\n}\n\nexport { Separator };\n"
        },
        {
          "path": "components/ui/sheet.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/sheet.tsx",
          "content": "'use client';\n\nimport { Dialog as SheetPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Sheet({ ...props }: SheetPrimitive.Root.Props) {\n  return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nfunction SheetTrigger({ ...props }: SheetPrimitive.Trigger.Props) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: SheetPrimitive.Close.Props) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: SheetPrimitive.Portal.Props) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        'fixed inset-0 z-50 bg-black/10 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"sheet-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetContent({\n  className,\n  children,\n  side = 'right',\n  showCloseButton = true,\n  ...props\n}: SheetPrimitive.Popup.Props & {\n  side?: 'top' | 'right' | 'bottom' | 'left';\n  showCloseButton?: boolean;\n}) {\n  return (\n    <SheetPortal>\n      <SheetOverlay />\n      <SheetPrimitive.Popup\n        className={cn(\n          'fixed z-50 flex flex-col gap-4 bg-popover bg-clip-padding text-popover-foreground text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:data-ending-style:translate-x-[-2.5rem] data-[side=left]:data-starting-style:translate-x-[-2.5rem] data-[side=right]:data-ending-style:translate-x-[2.5rem] data-[side=right]:data-starting-style:translate-x-[2.5rem] data-[side=bottom]:data-ending-style:translate-y-[2.5rem] data-[side=bottom]:data-starting-style:translate-y-[2.5rem] data-[side=top]:data-ending-style:translate-y-[-2.5rem] data-[side=top]:data-starting-style:translate-y-[-2.5rem] data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=right]:right-0 data-[side=bottom]:bottom-0 data-[side=left]:left-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-ending-style:opacity-0 data-starting-style:opacity-0 data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sheet-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <SheetPrimitive.Close\n            data-slot=\"sheet-close\"\n            render={\n              <Button\n                className=\"absolute top-3 right-3\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </SheetPrimitive.Close>\n        )}\n      </SheetPrimitive.Popup>\n    </SheetPortal>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-0.5 p-4', className)}\n      data-slot=\"sheet-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('mt-auto flex flex-col gap-2 p-4', className)}\n      data-slot=\"sheet-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {\n  return (\n    <SheetPrimitive.Title\n      className={cn('font-medium text-base text-foreground', className)}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({\n  className,\n  ...props\n}: SheetPrimitive.Description.Props) {\n  return (\n    <SheetPrimitive.Description\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
        },
        {
          "path": "components/ui/sidebar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/sidebar.tsx",
          "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { PanelLeftIcon } from 'lucide-react';\nimport * as React from 'react';\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { Separator } from \"./separator\";\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetHeader,\n  SheetTitle,\n} from \"./sheet\";\nimport { Skeleton } from \"./skeleton\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from \"./tooltip\";\nimport { useIsMobile } from \"../../hooks/use-mobile\";\nimport { cn } from \"../../lib/utils\";\n\nconst SIDEBAR_COOKIE_NAME = 'sidebar_state';\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = '16rem';\nconst SIDEBAR_WIDTH_MOBILE = '18rem';\nconst SIDEBAR_WIDTH_ICON = '3rem';\nconst SIDEBAR_KEYBOARD_SHORTCUT = 'b';\n\ntype SidebarContextProps = {\n  state: 'expanded' | 'collapsed';\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  openMobile: boolean;\n  setOpenMobile: (open: boolean) => void;\n  isMobile: boolean;\n  toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n  const context = React.useContext(SidebarContext);\n  if (!context) {\n    throw new Error('useSidebar must be used within a SidebarProvider.');\n  }\n\n  return context;\n}\n\nfunction SidebarProvider({\n  defaultOpen = true,\n  open: openProp,\n  onOpenChange: setOpenProp,\n  className,\n  style,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}) {\n  const isMobile = useIsMobile();\n  const [openMobile, setOpenMobile] = React.useState(false);\n\n  // This is the internal state of the sidebar.\n  // We use openProp and setOpenProp for control from outside the component.\n  const [_open, _setOpen] = React.useState(defaultOpen);\n  const open = openProp ?? _open;\n  const setOpen = React.useCallback(\n    (value: boolean | ((value: boolean) => boolean)) => {\n      const openState = typeof value === 'function' ? value(open) : value;\n      if (setOpenProp) {\n        setOpenProp(openState);\n      } else {\n        _setOpen(openState);\n      }\n\n      // This sets the cookie to keep the sidebar state.\n      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n    },\n    [setOpenProp, open]\n  );\n\n  // Helper to toggle the sidebar.\n  const toggleSidebar = React.useCallback(() => {\n    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n  }, [isMobile, setOpen]);\n\n  // Adds a keyboard shortcut to toggle the sidebar.\n  React.useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (\n        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n        (event.metaKey || event.ctrlKey)\n      ) {\n        event.preventDefault();\n        toggleSidebar();\n      }\n    };\n\n    window.addEventListener('keydown', handleKeyDown);\n    return () => window.removeEventListener('keydown', handleKeyDown);\n  }, [toggleSidebar]);\n\n  // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n  // This makes it easier to style the sidebar with Tailwind classes.\n  const state = open ? 'expanded' : 'collapsed';\n\n  const contextValue = React.useMemo<SidebarContextProps>(\n    () => ({\n      state,\n      open,\n      setOpen,\n      isMobile,\n      openMobile,\n      setOpenMobile,\n      toggleSidebar,\n    }),\n    [state, open, setOpen, isMobile, openMobile, toggleSidebar]\n  );\n\n  return (\n    <SidebarContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',\n          className\n        )}\n        data-slot=\"sidebar-wrapper\"\n        style={\n          {\n            '--sidebar-width': SIDEBAR_WIDTH,\n            '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,\n            ...style,\n          } as React.CSSProperties\n        }\n        {...props}\n      >\n        {children}\n      </div>\n    </SidebarContext.Provider>\n  );\n}\n\nfunction Sidebar({\n  side = 'left',\n  variant = 'sidebar',\n  collapsible = 'offcanvas',\n  className,\n  children,\n  dir,\n  ...props\n}: React.ComponentProps<'div'> & {\n  side?: 'left' | 'right';\n  variant?: 'sidebar' | 'floating' | 'inset';\n  collapsible?: 'offcanvas' | 'icon' | 'none';\n}) {\n  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n  if (collapsible === 'none') {\n    return (\n      <div\n        className={cn(\n          'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',\n          className\n        )}\n        data-slot=\"sidebar\"\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (isMobile) {\n    return (\n      <Sheet onOpenChange={setOpenMobile} open={openMobile} {...props}>\n        <SheetContent\n          className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n          data-mobile=\"true\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar\"\n          dir={dir}\n          side={side}\n          style={\n            {\n              '--sidebar-width': SIDEBAR_WIDTH_MOBILE,\n            } as React.CSSProperties\n          }\n        >\n          <SheetHeader className=\"sr-only\">\n            <SheetTitle>Sidebar</SheetTitle>\n            <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n          </SheetHeader>\n          <div className=\"flex h-full w-full flex-col\">{children}</div>\n        </SheetContent>\n      </Sheet>\n    );\n  }\n\n  return (\n    <div\n      className=\"group peer hidden text-sidebar-foreground md:block\"\n      data-collapsible={state === 'collapsed' ? collapsible : ''}\n      data-side={side}\n      data-slot=\"sidebar\"\n      data-state={state}\n      data-variant={variant}\n    >\n      {/* This is what handles the sidebar gap on desktop */}\n      <div\n        className={cn(\n          'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',\n          'group-data-[collapsible=offcanvas]:w-0',\n          'group-data-[side=right]:rotate-180',\n          variant === 'floating' || variant === 'inset'\n            ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'\n        )}\n        data-slot=\"sidebar-gap\"\n      />\n      <div\n        className={cn(\n          'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',\n          // Adjust the padding for floating and inset variants.\n          variant === 'floating' || variant === 'inset'\n            ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'\n            : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',\n          className\n        )}\n        data-side={side}\n        data-slot=\"sidebar-container\"\n        {...props}\n      >\n        <div\n          className=\"flex size-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border\"\n          data-sidebar=\"sidebar\"\n          data-slot=\"sidebar-inner\"\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction SidebarTrigger({\n  className,\n  onClick,\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <Button\n      className={cn(className)}\n      data-sidebar=\"trigger\"\n      data-slot=\"sidebar-trigger\"\n      onClick={(event) => {\n        onClick?.(event);\n        toggleSidebar();\n      }}\n      size=\"icon-sm\"\n      variant=\"ghost\"\n      {...props}\n    >\n      <PanelLeftIcon />\n      <span className=\"sr-only\">Toggle Sidebar</span>\n    </Button>\n  );\n}\n\nfunction SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {\n  const { toggleSidebar } = useSidebar();\n\n  return (\n    <button\n      aria-label=\"Toggle Sidebar\"\n      className={cn(\n        'group-data-[side=left]:-right-4 ltr:-translate-x-1/2 rtl:-translate-x-1/2 absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:left-0 sm:flex',\n        'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',\n        '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',\n        'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',\n        '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',\n        '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',\n        className\n      )}\n      data-sidebar=\"rail\"\n      data-slot=\"sidebar-rail\"\n      onClick={toggleSidebar}\n      tabIndex={-1}\n      title=\"Toggle Sidebar\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {\n  return (\n    <main\n      className={cn(\n        'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',\n        className\n      )}\n      data-slot=\"sidebar-inset\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarInput({\n  className,\n  ...props\n}: React.ComponentProps<typeof Input>) {\n  return (\n    <Input\n      className={cn('h-8 w-full bg-background shadow-none', className)}\n      data-sidebar=\"input\"\n      data-slot=\"sidebar-input\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"header\"\n      data-slot=\"sidebar-header\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2 p-2', className)}\n      data-sidebar=\"footer\"\n      data-slot=\"sidebar-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof Separator>) {\n  return (\n    <Separator\n      className={cn('mx-2 w-auto bg-sidebar-border', className)}\n      data-sidebar=\"separator\"\n      data-slot=\"sidebar-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'no-scrollbar flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',\n        className\n      )}\n      data-sidebar=\"content\"\n      data-slot=\"sidebar-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('relative flex w-full min-w-0 flex-col p-2', className)}\n      data-sidebar=\"group\"\n      data-slot=\"sidebar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'div'> & React.ComponentProps<'div'>) {\n  return useRender({\n    defaultTagName: 'div',\n    props: mergeProps<'div'>(\n      {\n        className: cn(\n          'group-data-[collapsible=icon]:-mt-8 flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-label',\n      sidebar: 'group-label',\n    },\n  });\n}\n\nfunction SidebarGroupAction({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<'button'> & React.ComponentProps<'button'>) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-group-action',\n      sidebar: 'group-action',\n    },\n  });\n}\n\nfunction SidebarGroupContent({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('w-full text-sm', className)}\n      data-sidebar=\"group-content\"\n      data-slot=\"sidebar-group-content\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn('flex w-full min-w-0 flex-col gap-0', className)}\n      data-sidebar=\"menu\"\n      data-slot=\"sidebar-menu\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-item relative', className)}\n      data-sidebar=\"menu-item\"\n      data-slot=\"sidebar-menu-item\"\n      {...props}\n    />\n  );\n}\n\nconst sidebarMenuButtonVariants = cva(\n  'peer/menu-button group/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',\n        outline:\n          'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]',\n      },\n      size: {\n        default: 'h-8 text-sm',\n        sm: 'h-7 text-xs',\n        lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction SidebarMenuButton({\n  render,\n  isActive = false,\n  variant = 'default',\n  size = 'default',\n  tooltip,\n  className,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    isActive?: boolean;\n    tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n  } & VariantProps<typeof sidebarMenuButtonVariants>) {\n  const { isMobile, state } = useSidebar();\n  const comp = useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(sidebarMenuButtonVariants({ variant, size }), className),\n      },\n      props\n    ),\n    render: tooltip ? <TooltipTrigger render={render} /> : render,\n    state: {\n      slot: 'sidebar-menu-button',\n      sidebar: 'menu-button',\n      size,\n      active: isActive,\n    },\n  });\n\n  if (!tooltip) {\n    return comp;\n  }\n\n  if (typeof tooltip === 'string') {\n    tooltip = {\n      children: tooltip,\n    };\n  }\n\n  return (\n    <Tooltip>\n      {comp}\n      <TooltipContent\n        align=\"center\"\n        hidden={state !== 'collapsed' || isMobile}\n        side=\"right\"\n        {...tooltip}\n      />\n    </Tooltip>\n  );\n}\n\nfunction SidebarMenuAction({\n  className,\n  render,\n  showOnHover = false,\n  ...props\n}: useRender.ComponentProps<'button'> &\n  React.ComponentProps<'button'> & {\n    showOnHover?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'button',\n    props: mergeProps<'button'>(\n      {\n        className: cn(\n          'after:-inset-2 absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',\n          showOnHover &&\n            'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-action',\n      sidebar: 'menu-action',\n    },\n  });\n}\n\nfunction SidebarMenuBadge({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',\n        className\n      )}\n      data-sidebar=\"menu-badge\"\n      data-slot=\"sidebar-menu-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSkeleton({\n  className,\n  showIcon = false,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showIcon?: boolean;\n}) {\n  // Random width between 50 to 90%.\n  const [width] = React.useState(() => {\n    return `${Math.floor(Math.random() * 40) + 50}%`;\n  });\n\n  return (\n    <div\n      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}\n      data-sidebar=\"menu-skeleton\"\n      data-slot=\"sidebar-menu-skeleton\"\n      {...props}\n    >\n      {showIcon && (\n        <Skeleton\n          className=\"size-4 rounded-md\"\n          data-sidebar=\"menu-skeleton-icon\"\n        />\n      )}\n      <Skeleton\n        className=\"h-4 max-w-(--skeleton-width) flex-1\"\n        data-sidebar=\"menu-skeleton-text\"\n        style={\n          {\n            '--skeleton-width': width,\n          } as React.CSSProperties\n        }\n      />\n    </div>\n  );\n}\n\nfunction SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {\n  return (\n    <ul\n      className={cn(\n        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-sidebar-border border-l px-2.5 py-0.5 group-data-[collapsible=icon]:hidden',\n        className\n      )}\n      data-sidebar=\"menu-sub\"\n      data-slot=\"sidebar-menu-sub\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubItem({\n  className,\n  ...props\n}: React.ComponentProps<'li'>) {\n  return (\n    <li\n      className={cn('group/menu-sub-item relative', className)}\n      data-sidebar=\"menu-sub-item\"\n      data-slot=\"sidebar-menu-sub-item\"\n      {...props}\n    />\n  );\n}\n\nfunction SidebarMenuSubButton({\n  render,\n  size = 'md',\n  isActive = false,\n  className,\n  ...props\n}: useRender.ComponentProps<'a'> &\n  React.ComponentProps<'a'> & {\n    size?: 'sm' | 'md';\n    isActive?: boolean;\n  }) {\n  return useRender({\n    defaultTagName: 'a',\n    props: mergeProps<'a'>(\n      {\n        className: cn(\n          '-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',\n          className\n        ),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'sidebar-menu-sub-button',\n      sidebar: 'menu-sub-button',\n      size,\n      active: isActive,\n    },\n  });\n}\n\nexport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,\n  SidebarGroupAction,\n  SidebarGroupContent,\n  SidebarGroupLabel,\n  SidebarHeader,\n  SidebarInput,\n  SidebarInset,\n  SidebarMenu,\n  SidebarMenuAction,\n  SidebarMenuBadge,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  SidebarMenuSkeleton,\n  SidebarMenuSub,\n  SidebarMenuSubButton,\n  SidebarMenuSubItem,\n  SidebarProvider,\n  SidebarRail,\n  SidebarSeparator,\n  SidebarTrigger,\n  useSidebar,\n};\n"
        },
        {
          "path": "components/ui/skeleton.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/skeleton.tsx",
          "content": "import { cn } from \"../../lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('animate-pulse rounded-md bg-muted', className)}\n      data-slot=\"skeleton\"\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "hooks/use-mobile.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/hooks/use-mobile.ts",
          "content": "import * as React from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(\n    undefined\n  );\n\n  React.useEffect(() => {\n    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n    const onChange = () => {\n      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    };\n    mql.addEventListener('change', onChange);\n    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n    return () => mql.removeEventListener('change', onChange);\n  }, []);\n\n  return !!isMobile;\n}\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/sidebar/sidebar-06/team-switcher.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/sidebar-06/team-switcher.tsx",
          "content": "'use client';\n\nimport { ChevronsUpDown, Plus } from 'lucide-react';\nimport * as React from 'react';\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport {\n  SidebarMenu,\n  SidebarMenuButton,\n  SidebarMenuItem,\n  useSidebar,\n} from \"./shared/components/ui/sidebar\";\n\nexport function TeamSwitcher({\n  teams,\n}: {\n  teams: {\n    name: string;\n    logo: React.ElementType;\n    plan: string;\n  }[];\n}) {\n  const { isMobile } = useSidebar();\n  const [activeTeam, setActiveTeam] = React.useState(teams[0]);\n\n  if (!activeTeam) {\n    return null;\n  }\n\n  return (\n    <SidebarMenu>\n      <SidebarMenuItem>\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={\n              <SidebarMenuButton\n                className=\"data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground\"\n                size=\"lg\"\n              />\n            }\n          >\n            <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-accent text-sidebar-accent-foreground\">\n              <activeTeam.logo className=\"size-4\" />\n            </div>\n            <div className=\"grid flex-1 text-left text-sm leading-tight\">\n              <span className=\"truncate font-medium\">{activeTeam.name}</span>\n              <span className=\"truncate text-xs\">{activeTeam.plan}</span>\n            </div>\n            <ChevronsUpDown className=\"ml-auto\" />\n          </DropdownMenuTrigger>\n          <DropdownMenuContent\n            align=\"start\"\n            className=\"w-(--anchor-width) min-w-56 rounded-lg\"\n            side={isMobile ? 'bottom' : 'right'}\n            sideOffset={4}\n          >\n            <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n              Teams\n            </DropdownMenuLabel>\n            {teams.map((team, index) => (\n              <DropdownMenuItem\n                className=\"gap-2 p-2\"\n                key={team.name}\n                onClick={() => setActiveTeam(team)}\n              >\n                <div className=\"flex size-6 items-center justify-center rounded-md border\">\n                  <team.logo className=\"size-3.5 shrink-0\" />\n                </div>\n                {team.name}\n                <DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>\n              </DropdownMenuItem>\n            ))}\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className=\"gap-2 p-2\">\n              <div className=\"flex size-6 items-center justify-center rounded-md border bg-transparent\">\n                <Plus className=\"size-4\" />\n              </div>\n              <div className=\"font-medium text-muted-foreground\">Add team</div>\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </SidebarMenuItem>\n    </SidebarMenu>\n  );\n}\n"
        }
      ],
      "categories": [
        "sidebar",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/sidebar-06/app/page.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/sidebar/sidebar-06 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "sidebar",
          "navigation",
          "shell",
          "dashboard",
          "侧栏",
          "导航",
          "布局",
          "后台"
        ],
        "contentGuidelines": [
          "Replace sample destinations and resource data with real routes.",
          "Keep top-level navigation short and verify the mobile drawer."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/sidebar-06/app/page.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/sidebar-06"
      }
    },
    {
      "name": "stats-01",
      "type": "registry:block",
      "title": "Stats with Trending",
      "description": "Stats with Trending. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/stats-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-01/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-01/stats-01.tsx",
          "content": "import { Card, CardContent } from \"./shared/components/ui/card\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst data = [\n  {\n    name: 'Profit',\n    value: '$287,654.00',\n    change: '+8.32%',\n    changeType: 'positive',\n  },\n  {\n    name: 'Late payments',\n    value: '$9,435.00',\n    change: '-12.64%',\n    changeType: 'negative',\n  },\n  {\n    name: 'Pending orders',\n    value: '$173,229.00',\n    change: '+2.87%',\n    changeType: 'positive',\n  },\n  {\n    name: 'Operating costs',\n    value: '$52,891.00',\n    change: '-5.73%',\n    changeType: 'negative',\n  },\n];\n\nexport default function Stats01() {\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <div className=\"mx-auto grid grid-cols-1 gap-px rounded-xl bg-border sm:grid-cols-2 lg:grid-cols-4\">\n        {data.map((stat, index) => (\n          <Card\n            className={cn(\n              'rounded-none border-0 py-0 shadow-none',\n              index === 0 && 'rounded-l-xl',\n              index === data.length - 1 && 'rounded-r-xl'\n            )}\n            key={stat.name}\n          >\n            <CardContent className=\"flex flex-wrap items-baseline justify-between gap-x-4 gap-y-2 p-4 sm:p-6\">\n              <div className=\"font-medium text-muted-foreground text-sm\">\n                {stat.name}\n              </div>\n              <div\n                className={cn(\n                  'font-medium text-xs tabular-nums',\n                  stat.changeType === 'positive'\n                    ? 'text-green-800 dark:text-green-400'\n                    : 'text-red-800 dark:text-red-400'\n                )}\n              >\n                {stat.change}\n              </div>\n              <div className=\"w-full flex-none font-medium text-3xl text-foreground tabular-nums tracking-tight\">\n                {stat.value}\n              </div>\n            </CardContent>\n          </Card>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-01/stats-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-01/stats-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-01"
      }
    },
    {
      "name": "stats-02",
      "type": "registry:block",
      "title": "Stats with Borders",
      "description": "Stats with Borders. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/stats-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-02/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-02/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-02/stats-02.tsx",
          "content": "import { TrendingDown, TrendingUp } from 'lucide-react';\nimport { Badge } from \"./shared/components/ui/badge\";\nimport { Card, CardContent, CardTitle } from \"./shared/components/ui/card\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst stats = [\n  {\n    metric: 'Active Users',\n    current: '128,456',\n    previous: '115,789',\n    difference: '10.9%',\n    trend: 'up',\n  },\n  {\n    metric: 'Conversion Rate',\n    current: '5.32%',\n    previous: '6.18%',\n    difference: '0.86%',\n    trend: 'down',\n  },\n  {\n    metric: 'Avg. Session Duration',\n    current: '3m 42s',\n    previous: '3m 15s',\n    difference: '13.8%',\n    trend: 'up',\n  },\n];\n\nexport default function Stats02() {\n  return (\n    <div className=\"flex items-center justify-center p-10\">\n      <div className=\"grid grid-cols-1 divide-y divide-border overflow-hidden rounded-lg bg-border md:grid-cols-3 md:divide-x md:divide-y-0\">\n        {stats.map((item) => (\n          <Card\n            className=\"rounded-none border-0 py-0 shadow-sm\"\n            key={item.metric}\n          >\n            <CardContent className=\"p-4 sm:p-6\">\n              <CardTitle className=\"font-normal text-base\">\n                {item.metric}\n              </CardTitle>\n              <div className=\"mt-1 flex items-baseline gap-2 md:block lg:flex\">\n                <div className=\"flex items-baseline font-semibold text-2xl text-primary tabular-nums\">\n                  {item.current}\n                  <span className=\"ml-2 font-medium text-muted-foreground text-sm tabular-nums\">\n                    from {item.previous}\n                  </span>\n                </div>\n\n                <Badge\n                  className={cn(\n                    'inline-flex h-auto items-center border-transparent px-1.5 py-0.5 ps-2.5 font-medium text-xs tabular-nums md:mt-2 lg:mt-0 [&>svg]:size-5!',\n                    item.trend === 'up'\n                      ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400'\n                      : 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'\n                  )}\n                  variant=\"outline\"\n                >\n                  {item.trend === 'up' ? (\n                    <TrendingUp className=\"-ml-1 mr-0.5 h-5 w-5 shrink-0 self-center text-green-500\" />\n                  ) : (\n                    <TrendingDown className=\"-ml-1 mr-0.5 h-5 w-5 shrink-0 self-center text-red-500\" />\n                  )}\n\n                  <span className=\"sr-only\">\n                    {' '}\n                    {item.trend === 'up' ? 'Increased' : 'Decreased'} by{' '}\n                  </span>\n                  {item.difference}\n                </Badge>\n              </div>\n            </CardContent>\n          </Card>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-02/stats-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-02/stats-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-02"
      }
    },
    {
      "name": "stats-03",
      "type": "registry:block",
      "title": "Stats with Card Layout",
      "description": "Stats with Card Layout. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/stats-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-03/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-03/stats-03.tsx",
          "content": "'use client';\n\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst data = [\n  {\n    name: 'Unique visitors',\n    stat: '10,450',\n    change: '-12.5%',\n    changeType: 'negative',\n  },\n  {\n    name: 'Bounce rate',\n    stat: '56.1%',\n    change: '+1.8%',\n    changeType: 'positive',\n  },\n  {\n    name: 'Visit duration',\n    stat: '5.2min',\n    change: '+19.7%',\n    changeType: 'positive',\n  },\n  {\n    name: 'Conversion rate',\n    stat: '3.2%',\n    change: '-2.4%',\n    changeType: 'negative',\n  },\n];\n\nexport default function Stats03() {\n  return (\n    <div className=\"flex w-full items-center justify-center p-10\">\n      <dl className=\"grid w-full grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4\">\n        {data.map((item) => (\n          <Card className=\"p-6 py-4 shadow-2xs\" key={item.name}>\n            <CardContent className=\"p-0\">\n              <dt className=\"font-medium text-muted-foreground text-sm\">\n                {item.name}\n              </dt>\n              <dd className=\"mt-2 flex items-baseline space-x-2.5\">\n                <span className=\"font-semibold text-3xl text-foreground tabular-nums\">\n                  {item.stat}\n                </span>\n                <span\n                  className={cn(\n                    item.changeType === 'positive'\n                      ? 'text-green-800 dark:text-green-400'\n                      : 'text-red-800 dark:text-red-400',\n                    'font-medium text-sm'\n                  )}\n                >\n                  {item.change}\n                </span>\n              </dd>\n            </CardContent>\n          </Card>\n        ))}\n      </dl>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-03/stats-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-03/stats-03.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-03"
      }
    },
    {
      "name": "stats-04",
      "type": "registry:block",
      "title": "Stats with Badges",
      "description": "Stats with Badges. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/stats-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-04/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-04/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-04/stats-04.tsx",
          "content": "'use client';\n\nimport { TrendingDown, TrendingUp } from 'lucide-react';\nimport { Badge } from \"./shared/components/ui/badge\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst data = [\n  {\n    name: 'Daily active users',\n    stat: '3,450',\n    change: '+12.1%',\n    changeType: 'positive',\n  },\n  {\n    name: 'Weekly sessions',\n    stat: '1,342',\n    change: '-9.8%',\n    changeType: 'negative',\n  },\n  {\n    name: 'Duration',\n    stat: '5.2min',\n    change: '+7.7%',\n    changeType: 'positive',\n  },\n];\n\nexport default function Stats04() {\n  return (\n    <div className=\"flex w-full items-center justify-center p-10\">\n      <dl className=\"grid w-full grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3\">\n        {data.map((item) => (\n          <Card className=\"w-full p-6 py-4 shadow-2xs\" key={item.name}>\n            <CardContent className=\"p-0\">\n              <div className=\"flex items-center justify-between\">\n                <dt className=\"font-medium text-muted-foreground text-sm\">\n                  {item.name}\n                </dt>\n                <Badge\n                  className={cn(\n                    'tabular-nums',\n                    item.changeType === 'positive'\n                      ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400'\n                      : 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'\n                  )}\n                >\n                  {item.changeType === 'positive' ? (\n                    <TrendingUp aria-hidden={true} data-icon=\"inline-start\" />\n                  ) : (\n                    <TrendingDown aria-hidden={true} data-icon=\"inline-start\" />\n                  )}\n                  <span className=\"sr-only\">\n                    {item.changeType === 'positive' ? 'Increased' : 'Decreased'}{' '}\n                    by{' '}\n                  </span>\n                  {item.change}\n                </Badge>\n              </div>\n              <dd className=\"mt-2 font-semibold text-3xl text-foreground tabular-nums\">\n                {item.stat}\n              </dd>\n            </CardContent>\n          </Card>\n        ))}\n      </dl>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-04/stats-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-04/stats-04.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-04"
      }
    },
    {
      "name": "stats-05",
      "type": "registry:block",
      "title": "Stats with Links",
      "description": "Stats with Links. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/stats-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-05/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-05.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-05/stats-05.tsx",
          "content": "'use client';\n\nimport { Card, CardContent, CardFooter } from \"./shared/components/ui/card\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst data = [\n  {\n    name: 'Monthly recurring revenue',\n    value: '$34.1K',\n    change: '+6.1%',\n    changeType: 'positive',\n    href: '#',\n  },\n  {\n    name: 'Users',\n    value: '500.1K',\n    change: '+19.2%',\n    changeType: 'positive',\n    href: '#',\n  },\n  {\n    name: 'User growth',\n    value: '11.3%',\n    change: '-1.2%',\n    changeType: 'negative',\n    href: '#',\n  },\n];\n\nexport default function Stats05() {\n  return (\n    <div className=\"flex w-full items-center justify-center p-10\">\n      <dl className=\"grid w-full grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3\">\n        {data.map((item) => (\n          <Card className=\"gap-0 p-0 shadow-2xs\" key={item.name}>\n            <CardContent className=\"p-6\">\n              <dd className=\"flex items-start justify-between space-x-2\">\n                <span className=\"truncate text-muted-foreground text-sm\">\n                  {item.name}\n                </span>\n                <span\n                  className={cn(\n                    'font-medium text-sm',\n                    item.changeType === 'positive'\n                      ? 'text-emerald-700 dark:text-emerald-500'\n                      : 'text-red-700 dark:text-red-500'\n                  )}\n                >\n                  {item.change}\n                </span>\n              </dd>\n              <dd className=\"mt-1 font-semibold text-3xl text-foreground tabular-nums\">\n                {item.value}\n              </dd>\n            </CardContent>\n            <CardFooter className=\"flex justify-end border-border border-t p-0!\">\n              <a\n                className=\"px-6 py-3 font-medium text-primary text-sm hover:text-primary/90\"\n                href={item.href}\n              >\n                View more &#8594;\n              </a>\n            </CardFooter>\n          </Card>\n        ))}\n      </dl>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-05/stats-05.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-05/stats-05.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-05"
      }
    },
    {
      "name": "stats-06",
      "type": "registry:block",
      "title": "Stats with Status",
      "description": "Stats with Status. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/stats-06/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-06/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.2.0):\n- Replaced Next.js Link with standard anchors; destinations remain controlled by the host application.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-06/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-06.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-06/stats-06.tsx",
          "content": "\"use client\";\n\nimport { ArrowRight } from \"lucide-react\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst statuses = {\n  within: { label: \"On track\", color: \"bg-emerald-500\" },\n  observe: { label: \"Needs attention\", color: \"bg-amber-500\" },\n  critical: { label: \"At risk\", color: \"bg-red-500\" },\n};\n\nconst data = [\n  {\n    name: \"Europe\",\n    stat: \"$10,023\",\n    goalsAchieved: 3,\n    status: \"observe\",\n    href: \"#\",\n  },\n  {\n    name: \"North America\",\n    stat: \"$14,092\",\n    goalsAchieved: 5,\n    status: \"within\",\n    href: \"#\",\n  },\n  {\n    name: \"Asia\",\n    stat: \"$113,232\",\n    goalsAchieved: 1,\n    status: \"critical\",\n    href: \"#\",\n  },\n] as {\n  name: string;\n  stat: string;\n  goalsAchieved: number;\n  status: keyof typeof statuses;\n  href: string;\n}[];\n\nconst goals = [1, 2, 3, 4, 5];\n\nexport default function Stats06() {\n  return (\n    <div className=\"@container flex w-full items-center justify-center p-10\">\n      <dl className=\"grid w-full @2xl:grid-cols-3 grid-cols-1 @2xl:divide-x divide-y @2xl:divide-y-0 divide-foreground/10 overflow-hidden rounded-xl bg-background shadow-[0_0_0_1px_oklch(0_0_0/0.06),0_1px_2px_-1px_oklch(0_0_0/0.06),0_2px_4px_0_oklch(0_0_0/0.04)] dark:shadow-[0_0_0_1px_oklch(1_0_0/0.08)]\">\n        {data.map((item) => {\n          const status = statuses[item.status];\n          return (\n            <div\n              className=\"group relative flex flex-col gap-6 p-6 hover:bg-muted/40\"\n              key={item.name}\n            >\n              <div className=\"flex items-center justify-between gap-3\">\n                <dt className=\"truncate font-medium text-foreground text-sm\">\n                  {item.name}\n                </dt>\n                <dd className=\"flex shrink-0 items-center gap-2 text-muted-foreground text-sm\">\n                  <span\n                    aria-hidden={true}\n                    className={cn(\"size-1.5 rounded-full\", status.color)}\n                  />\n                  {status.label}\n                </dd>\n              </div>\n              <dd className=\"font-semibold text-3xl text-foreground tabular-nums tracking-tight\">\n                {item.stat}\n              </dd>\n              <dd className=\"flex flex-col gap-2\">\n                <div className=\"flex items-center justify-between text-sm\">\n                  <span className=\"text-muted-foreground\">Goals achieved</span>\n                  <span className=\"font-medium text-foreground tabular-nums\">\n                    {item.goalsAchieved}/{goals.length}\n                  </span>\n                </div>\n                <div aria-hidden={true} className=\"flex gap-1\">\n                  {goals.map((goal) => (\n                    <span\n                      className={cn(\n                        \"h-1.5 flex-1 rounded-full\",\n                        goal <= item.goalsAchieved\n                          ? status.color\n                          : \"bg-foreground/10\"\n                      )}\n                      key={goal}\n                    />\n                  ))}\n                </div>\n              </dd>\n              <dd className=\"text-sm\">\n                <a\n                  className=\"flex items-center gap-1 font-medium text-foreground focus:outline-none\"\n                  href={item.href}\n                >\n                  <span aria-hidden={true} className=\"absolute inset-0\" />\n                  View region\n                  <ArrowRight\n                    aria-hidden={true}\n                    className=\"size-4 shrink-0 transition-transform group-hover:translate-x-0.5\"\n                  />\n                </a>\n              </dd>\n            </div>\n          );\n        })}\n      </dl>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-06/stats-06.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-06 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.2.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f",
          "modifications": [
            "Replaced Next.js Link with standard anchors; destinations remain controlled by the host application."
          ]
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-06/stats-06.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-06"
      }
    },
    {
      "name": "stats-07",
      "type": "registry:block",
      "title": "Stats with Circular Progress",
      "description": "Stats with Circular Progress. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6",
        "lucide-react@0.577.0",
        "recharts@3.8.0"
      ],
      "files": [
        {
          "path": "licenses/stats-07/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-07/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\nUIBlocks modifications (block v0.2.0):\n- Replaced Next.js Link with standard anchors; destinations remain controlled by the host application.\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-07/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/chart.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-07/shared/components/ui/chart.tsx",
          "content": "'use client';\n\nimport * as React from 'react';\nimport type { TooltipValueType } from 'recharts';\nimport * as RechartsPrimitive from 'recharts';\n\nimport { cn } from \"../../lib/utils\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: '', dark: '.dark' } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n  string,\n  {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n>;\n\ntype ChartContextProps = {\n  config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error('useChart must be used within a <ChartContainer />');\n  }\n\n  return context;\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  initialDimension = INITIAL_DIMENSION,\n  ...props\n}: React.ComponentProps<'div'> & {\n  config: ChartConfig;\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >['children'];\n  initialDimension?: {\n    width: number;\n    height: number;\n  };\n}) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`;\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        className={cn(\n          \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        data-chart={chartId}\n        data-slot=\"chart\"\n        {...props}\n      >\n        <ChartStyle config={config} id={chartId} />\n        <RechartsPrimitive.ResponsiveContainer\n          initialDimension={initialDimension}\n        >\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme ?? config.color\n  );\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??\n      itemConfig.color;\n    return color ? `  --color-${key}: ${color};` : null;\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n'),\n      }}\n    />\n  );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<'div'> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: 'line' | 'dot' | 'dashed';\n    nameKey?: string;\n    labelKey?: string;\n  } & Omit<\n    RechartsPrimitive.DefaultTooltipContentProps<\n      TooltipValueType,\n      TooltipNameType\n    >,\n    'accessibilityLayer'\n  >) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? 'value'}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === 'string'\n        ? (config[label]?.label ?? label)\n        : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn('font-medium', labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn('font-medium', labelClassName)}>{value}</div>;\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey,\n  ]);\n\n  if (!(active && payload?.length)) {\n    return null;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot';\n\n  return (\n    <div\n      className={cn(\n        'grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl',\n        className\n      )}\n    >\n      {nestLabel ? null : tooltipLabel}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== 'none')\n          .map((item, index) => {\n            const key = `${nameKey ?? item.name ?? item.dataKey ?? 'value'}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n            const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n            return (\n              <div\n                className={cn(\n                  'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground',\n                  indicator === 'dot' && 'items-center'\n                )}\n                key={index}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',\n                            {\n                              'h-2.5 w-2.5': indicator === 'dot',\n                              'w-1': indicator === 'line',\n                              'w-0 border-[1.5px] border-dashed bg-transparent':\n                                indicator === 'dashed',\n                              'my-0.5': nestLabel && indicator === 'dashed',\n                            }\n                          )}\n                          style={\n                            {\n                              '--color-bg': indicatorColor,\n                              '--color-border': indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        'flex flex-1 justify-between leading-none',\n                        nestLabel ? 'items-end' : 'items-center'\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label ?? item.name}\n                        </span>\n                      </div>\n                      {item.value != null && (\n                        <span className=\"font-medium font-mono text-foreground tabular-nums\">\n                          {typeof item.value === 'number'\n                            ? item.value.toLocaleString()\n                            : String(item.value)}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = 'bottom',\n  nameKey,\n}: React.ComponentProps<'div'> & {\n  hideIcon?: boolean;\n  nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-center justify-center gap-4',\n        verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== 'none')\n        .map((item, index) => {\n          const key = `${nameKey ?? item.dataKey ?? 'value'}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n          return (\n            <div\n              className={cn(\n                'flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground'\n              )}\n              key={index}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== 'object' || payload === null) {\n    return;\n  }\n\n  const payloadPayload =\n    'payload' in payload &&\n    typeof payload.payload === 'object' &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === 'string'\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string;\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-07/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-07.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-07/stats-07.tsx",
          "content": "\"use client\";\n\nimport { ExternalLink } from \"lucide-react\";\nimport { PolarAngleAxis, RadialBar, RadialBarChart } from \"recharts\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { type ChartConfig, ChartContainer } from \"./shared/components/ui/chart\";\n\nconst data = [\n  {\n    name: \"Workspaces\",\n    capacity: 20,\n    current: 1,\n    allowed: 5,\n    fill: \"var(--chart-1)\",\n  },\n  {\n    name: \"Dashboards\",\n    capacity: 10,\n    current: 2,\n    allowed: 20,\n    fill: \"var(--chart-2)\",\n  },\n  {\n    name: \"Chart widgets\",\n    capacity: 30,\n    current: 15,\n    allowed: 50,\n    fill: \"var(--chart-3)\",\n  },\n  {\n    name: \"Storage\",\n    capacity: 50,\n    current: 25,\n    allowed: 100,\n    fill: \"var(--chart-4)\",\n  },\n];\n\nconst chartConfig = {\n  capacity: {\n    label: \"Capacity\",\n    color: \"hsl(var(--primary))\",\n  },\n} satisfies ChartConfig;\n\nexport default function Stats07() {\n  return (\n    <div className=\"flex w-full items-center justify-center p-10\">\n      <div className=\"w-full\">\n        <h2 className=\"text-balance font-medium text-foreground text-xl\">\n          Plan overview\n        </h2>\n        <p className=\"mt-1 text-pretty text-muted-foreground text-sm leading-6\">\n          You are currently on the{\" \"}\n          <span className=\"font-medium text-foreground\">starter plan</span>.{\" \"}\n          <a\n            className=\"inline-flex items-center gap-1 text-primary hover:underline hover:underline-offset-4\"\n            href=\"#\"\n          >\n            View other plans\n            <ExternalLink aria-hidden={true} className=\"size-4\" />\n          </a>\n        </p>\n        <dl className=\"mt-6 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4\">\n          {data.map((item) => (\n            <Card className=\"p-4 shadow-2xs\" key={item.name}>\n              <CardContent className=\"flex items-center space-x-4 p-0\">\n                <div className=\"relative flex items-center justify-center\">\n                  <ChartContainer\n                    className=\"h-[80px] w-[80px]\"\n                    config={chartConfig}\n                  >\n                    <RadialBarChart\n                      barSize={6}\n                      data={[item]}\n                      endAngle={-270}\n                      innerRadius={30}\n                      outerRadius={40}\n                      startAngle={90}\n                    >\n                      <PolarAngleAxis\n                        angleAxisId={0}\n                        axisLine={false}\n                        domain={[0, 100]}\n                        tick={false}\n                        type=\"number\"\n                      />\n                      <RadialBar\n                        angleAxisId={0}\n                        background\n                        cornerRadius={10}\n                        dataKey=\"capacity\"\n                        fill=\"var(--primary)\"\n                      />\n                    </RadialBarChart>\n                  </ChartContainer>\n                  <div className=\"absolute inset-0 flex items-center justify-center\">\n                    <span className=\"font-medium text-base text-foreground\">\n                      {item.capacity}%\n                    </span>\n                  </div>\n                </div>\n                <div>\n                  <dt className=\"font-medium text-foreground text-sm\">\n                    {item.name}\n                  </dt>\n                  <dd className=\"text-muted-foreground text-sm\">\n                    {item.current} of {item.allowed} used\n                  </dd>\n                </div>\n              </CardContent>\n            </Card>\n          ))}\n        </dl>\n      </div>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-07/stats-07.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-07 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.2.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f",
          "modifications": [
            "Replaced Next.js Link with standard anchors; destinations remain controlled by the host application."
          ]
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-07/stats-07.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-07"
      }
    },
    {
      "name": "stats-08",
      "type": "registry:block",
      "title": "Stats with Circular Progress and Links",
      "description": "Stats with Circular Progress and Links. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6",
        "recharts@3.8.0"
      ],
      "files": [
        {
          "path": "licenses/stats-08/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-08/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-08/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/chart.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-08/shared/components/ui/chart.tsx",
          "content": "'use client';\n\nimport * as React from 'react';\nimport type { TooltipValueType } from 'recharts';\nimport * as RechartsPrimitive from 'recharts';\n\nimport { cn } from \"../../lib/utils\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: '', dark: '.dark' } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n  string,\n  {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n>;\n\ntype ChartContextProps = {\n  config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error('useChart must be used within a <ChartContainer />');\n  }\n\n  return context;\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  initialDimension = INITIAL_DIMENSION,\n  ...props\n}: React.ComponentProps<'div'> & {\n  config: ChartConfig;\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >['children'];\n  initialDimension?: {\n    width: number;\n    height: number;\n  };\n}) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`;\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        className={cn(\n          \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        data-chart={chartId}\n        data-slot=\"chart\"\n        {...props}\n      >\n        <ChartStyle config={config} id={chartId} />\n        <RechartsPrimitive.ResponsiveContainer\n          initialDimension={initialDimension}\n        >\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme ?? config.color\n  );\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??\n      itemConfig.color;\n    return color ? `  --color-${key}: ${color};` : null;\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n'),\n      }}\n    />\n  );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<'div'> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: 'line' | 'dot' | 'dashed';\n    nameKey?: string;\n    labelKey?: string;\n  } & Omit<\n    RechartsPrimitive.DefaultTooltipContentProps<\n      TooltipValueType,\n      TooltipNameType\n    >,\n    'accessibilityLayer'\n  >) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? 'value'}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === 'string'\n        ? (config[label]?.label ?? label)\n        : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn('font-medium', labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn('font-medium', labelClassName)}>{value}</div>;\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey,\n  ]);\n\n  if (!(active && payload?.length)) {\n    return null;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot';\n\n  return (\n    <div\n      className={cn(\n        'grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl',\n        className\n      )}\n    >\n      {nestLabel ? null : tooltipLabel}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== 'none')\n          .map((item, index) => {\n            const key = `${nameKey ?? item.name ?? item.dataKey ?? 'value'}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n            const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n            return (\n              <div\n                className={cn(\n                  'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground',\n                  indicator === 'dot' && 'items-center'\n                )}\n                key={index}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',\n                            {\n                              'h-2.5 w-2.5': indicator === 'dot',\n                              'w-1': indicator === 'line',\n                              'w-0 border-[1.5px] border-dashed bg-transparent':\n                                indicator === 'dashed',\n                              'my-0.5': nestLabel && indicator === 'dashed',\n                            }\n                          )}\n                          style={\n                            {\n                              '--color-bg': indicatorColor,\n                              '--color-border': indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        'flex flex-1 justify-between leading-none',\n                        nestLabel ? 'items-end' : 'items-center'\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label ?? item.name}\n                        </span>\n                      </div>\n                      {item.value != null && (\n                        <span className=\"font-medium font-mono text-foreground tabular-nums\">\n                          {typeof item.value === 'number'\n                            ? item.value.toLocaleString()\n                            : String(item.value)}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = 'bottom',\n  nameKey,\n}: React.ComponentProps<'div'> & {\n  hideIcon?: boolean;\n  nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-center justify-center gap-4',\n        verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== 'none')\n        .map((item, index) => {\n          const key = `${nameKey ?? item.dataKey ?? 'value'}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n          return (\n            <div\n              className={cn(\n                'flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground'\n              )}\n              key={index}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== 'object' || payload === null) {\n    return;\n  }\n\n  const payloadPayload =\n    'payload' in payload &&\n    typeof payload.payload === 'object' &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === 'string'\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string;\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-08/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-08.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-08/stats-08.tsx",
          "content": "'use client';\n\nimport { PolarAngleAxis, RadialBar, RadialBarChart } from 'recharts';\nimport { Card, CardContent, CardFooter } from \"./shared/components/ui/card\";\nimport { type ChartConfig, ChartContainer } from \"./shared/components/ui/chart\";\n\nconst data = [\n  {\n    name: 'HR',\n    progress: 25,\n    budget: '$1,000',\n    current: '$250',\n    href: '#',\n    fill: 'var(--chart-1)',\n  },\n  {\n    name: 'Marketing',\n    progress: 55,\n    budget: '$1,000',\n    current: '$550',\n    href: '#',\n    fill: 'var(--chart-2)',\n  },\n  {\n    name: 'Finance',\n    progress: 85,\n    budget: '$1,000',\n    current: '$850',\n    href: '#',\n    fill: 'var(--chart-3)',\n  },\n  {\n    name: 'Engineering',\n    progress: 70,\n    budget: '$2,000',\n    current: '$1,400',\n    href: '#',\n    fill: 'var(--chart-4)',\n  },\n];\n\nconst chartConfig = {\n  progress: {\n    label: 'Progress',\n    color: 'var(--primary)',\n  },\n} satisfies ChartConfig;\n\nexport default function Stats08() {\n  return (\n    <div className=\"flex w-full items-center justify-center p-10\">\n      <dl className=\"grid w-full grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4\">\n        {data.map((item) => (\n          <Card className=\"gap-0 p-0 shadow-2xs\" key={item.name}>\n            <CardContent className=\"p-4\">\n              <div className=\"flex items-center space-x-3\">\n                <div className=\"relative flex items-center justify-center\">\n                  <ChartContainer\n                    className=\"h-[80px] w-[80px]\"\n                    config={chartConfig}\n                  >\n                    <RadialBarChart\n                      barSize={6}\n                      data={[item]}\n                      endAngle={-270}\n                      innerRadius={30}\n                      outerRadius={40}\n                      startAngle={90}\n                    >\n                      <PolarAngleAxis\n                        angleAxisId={0}\n                        axisLine={false}\n                        domain={[0, 100]}\n                        tick={false}\n                        type=\"number\"\n                      />\n                      <RadialBar\n                        angleAxisId={0}\n                        background\n                        cornerRadius={10}\n                        dataKey=\"progress\"\n                        fill={item.fill}\n                      />\n                    </RadialBarChart>\n                  </ChartContainer>\n                  <div className=\"absolute inset-0 flex items-center justify-center\">\n                    <span className=\"font-medium text-base text-foreground\">\n                      {item.progress}%\n                    </span>\n                  </div>\n                </div>\n                <div>\n                  <dd className=\"font-medium text-base text-foreground\">\n                    {item.current} / {item.budget}\n                  </dd>\n                  <dt className=\"text-muted-foreground text-sm\">\n                    Budget {item.name}\n                  </dt>\n                </div>\n              </div>\n            </CardContent>\n            <CardFooter className=\"flex items-center justify-end border-border border-t p-0!\">\n              <a\n                className=\"px-6 py-3 font-medium text-primary text-sm hover:text-primary/90\"\n                href={item.href}\n              >\n                View more &#8594;\n              </a>\n            </CardFooter>\n          </Card>\n        ))}\n      </dl>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-08/stats-08.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-08 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-08/stats-08.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-08"
      }
    },
    {
      "name": "stats-09",
      "type": "registry:block",
      "title": "Stats with Progress",
      "description": "Stats with Progress. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/stats-09/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-09/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-09/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/progress.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-09/shared/components/ui/progress.tsx",
          "content": "'use client';\n\nimport { Progress as ProgressPrimitive } from '@base-ui/react/progress';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Progress({\n  className,\n  children,\n  value,\n  ...props\n}: ProgressPrimitive.Root.Props) {\n  return (\n    <ProgressPrimitive.Root\n      className={cn('flex flex-wrap gap-3', className)}\n      data-slot=\"progress\"\n      value={value}\n      {...props}\n    >\n      {children}\n      <ProgressTrack>\n        <ProgressIndicator />\n      </ProgressTrack>\n    </ProgressPrimitive.Root>\n  );\n}\n\nfunction ProgressTrack({ className, ...props }: ProgressPrimitive.Track.Props) {\n  return (\n    <ProgressPrimitive.Track\n      className={cn(\n        'relative flex h-1 w-full items-center overflow-x-hidden rounded-full bg-muted',\n        className\n      )}\n      data-slot=\"progress-track\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressIndicator({\n  className,\n  ...props\n}: ProgressPrimitive.Indicator.Props) {\n  return (\n    <ProgressPrimitive.Indicator\n      className={cn('h-full bg-primary transition-all', className)}\n      data-slot=\"progress-indicator\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressLabel({ className, ...props }: ProgressPrimitive.Label.Props) {\n  return (\n    <ProgressPrimitive.Label\n      className={cn('font-medium text-sm', className)}\n      data-slot=\"progress-label\"\n      {...props}\n    />\n  );\n}\n\nfunction ProgressValue({ className, ...props }: ProgressPrimitive.Value.Props) {\n  return (\n    <ProgressPrimitive.Value\n      className={cn(\n        'ml-auto text-muted-foreground text-sm tabular-nums',\n        className\n      )}\n      data-slot=\"progress-value\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Progress,\n  ProgressTrack,\n  ProgressIndicator,\n  ProgressLabel,\n  ProgressValue,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-09/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-09.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-09/stats-09.tsx",
          "content": "'use client';\n\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { Progress } from \"./shared/components/ui/progress\";\n\nconst data = [\n  {\n    name: 'Requests',\n    stat: '996',\n    limit: '10,000',\n    percentage: 9.96,\n  },\n  {\n    name: 'Credits',\n    stat: '$672',\n    limit: '$1,000',\n    percentage: 67.2,\n  },\n  {\n    name: 'Storage',\n    stat: '1.85',\n    limit: '10GB',\n    percentage: 18.5,\n  },\n  {\n    name: 'API Calls',\n    stat: '4,328',\n    limit: '5,000',\n    percentage: 86.56,\n  },\n];\n\nexport default function Stats09() {\n  return (\n    <div className=\"flex w-full items-center justify-center p-10\">\n      <dl className=\"grid w-full grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4\">\n        {data.map((item) => (\n          <Card className=\"py-4 shadow-2xs\" key={item.name}>\n            <CardContent className=\"\">\n              <dt className=\"text-muted-foreground text-sm\">{item.name}</dt>\n              <dd className=\"font-semibold text-2xl text-foreground tabular-nums\">\n                {item.stat}\n              </dd>\n              <Progress className=\"mt-6 h-2\" value={item.percentage} />\n              <dd className=\"mt-2 flex items-center justify-between text-sm\">\n                <span className=\"text-primary\">{item.percentage}&#37;</span>\n                <span className=\"text-muted-foreground\">\n                  {item.stat} of {item.limit}\n                </span>\n              </dd>\n            </CardContent>\n          </Card>\n        ))}\n      </dl>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-09/stats-09.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-09 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-09/stats-09.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-09"
      }
    },
    {
      "name": "stats-10",
      "type": "registry:block",
      "title": "Stats with Area Chart",
      "description": "Stats with Area Chart. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6",
        "recharts@3.8.0"
      ],
      "files": [
        {
          "path": "licenses/stats-10/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-10/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-10/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/chart.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-10/shared/components/ui/chart.tsx",
          "content": "'use client';\n\nimport * as React from 'react';\nimport type { TooltipValueType } from 'recharts';\nimport * as RechartsPrimitive from 'recharts';\n\nimport { cn } from \"../../lib/utils\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: '', dark: '.dark' } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n  string,\n  {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n>;\n\ntype ChartContextProps = {\n  config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error('useChart must be used within a <ChartContainer />');\n  }\n\n  return context;\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  initialDimension = INITIAL_DIMENSION,\n  ...props\n}: React.ComponentProps<'div'> & {\n  config: ChartConfig;\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >['children'];\n  initialDimension?: {\n    width: number;\n    height: number;\n  };\n}) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`;\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        className={cn(\n          \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        data-chart={chartId}\n        data-slot=\"chart\"\n        {...props}\n      >\n        <ChartStyle config={config} id={chartId} />\n        <RechartsPrimitive.ResponsiveContainer\n          initialDimension={initialDimension}\n        >\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme ?? config.color\n  );\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??\n      itemConfig.color;\n    return color ? `  --color-${key}: ${color};` : null;\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n'),\n      }}\n    />\n  );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<'div'> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: 'line' | 'dot' | 'dashed';\n    nameKey?: string;\n    labelKey?: string;\n  } & Omit<\n    RechartsPrimitive.DefaultTooltipContentProps<\n      TooltipValueType,\n      TooltipNameType\n    >,\n    'accessibilityLayer'\n  >) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? 'value'}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === 'string'\n        ? (config[label]?.label ?? label)\n        : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn('font-medium', labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn('font-medium', labelClassName)}>{value}</div>;\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey,\n  ]);\n\n  if (!(active && payload?.length)) {\n    return null;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot';\n\n  return (\n    <div\n      className={cn(\n        'grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl',\n        className\n      )}\n    >\n      {nestLabel ? null : tooltipLabel}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== 'none')\n          .map((item, index) => {\n            const key = `${nameKey ?? item.name ?? item.dataKey ?? 'value'}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n            const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n            return (\n              <div\n                className={cn(\n                  'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground',\n                  indicator === 'dot' && 'items-center'\n                )}\n                key={index}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',\n                            {\n                              'h-2.5 w-2.5': indicator === 'dot',\n                              'w-1': indicator === 'line',\n                              'w-0 border-[1.5px] border-dashed bg-transparent':\n                                indicator === 'dashed',\n                              'my-0.5': nestLabel && indicator === 'dashed',\n                            }\n                          )}\n                          style={\n                            {\n                              '--color-bg': indicatorColor,\n                              '--color-border': indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        'flex flex-1 justify-between leading-none',\n                        nestLabel ? 'items-end' : 'items-center'\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label ?? item.name}\n                        </span>\n                      </div>\n                      {item.value != null && (\n                        <span className=\"font-medium font-mono text-foreground tabular-nums\">\n                          {typeof item.value === 'number'\n                            ? item.value.toLocaleString()\n                            : String(item.value)}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = 'bottom',\n  nameKey,\n}: React.ComponentProps<'div'> & {\n  hideIcon?: boolean;\n  nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-center justify-center gap-4',\n        verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== 'none')\n        .map((item, index) => {\n          const key = `${nameKey ?? item.dataKey ?? 'value'}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n          return (\n            <div\n              className={cn(\n                'flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground'\n              )}\n              key={index}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== 'object' || payload === null) {\n    return;\n  }\n\n  const payloadPayload =\n    'payload' in payload &&\n    typeof payload.payload === 'object' &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === 'string'\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string;\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-10/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-10.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-10/stats-10.tsx",
          "content": "'use client';\n\nimport { Area, AreaChart, XAxis } from 'recharts';\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { ChartContainer } from \"./shared/components/ui/chart\";\nimport { cn } from \"./shared/lib/utils\";\n\nconst data = [\n  {\n    date: 'Nov 24, 2023',\n    'Alpha Corp': 142.87,\n    'Beta Solutions': 65.32,\n    'Gamma Industries': 83.25,\n  },\n  {\n    date: 'Nov 25, 2023',\n    'Alpha Corp': 151.43,\n    'Beta Solutions': 59.78,\n    'Gamma Industries': 79.64,\n  },\n  {\n    date: 'Nov 26, 2023',\n    'Alpha Corp': 157.28,\n    'Beta Solutions': 64.21,\n    'Gamma Industries': 76.19,\n  },\n  {\n    date: 'Nov 27, 2023',\n    'Alpha Corp': 162.94,\n    'Beta Solutions': 57.46,\n    'Gamma Industries': 72.84,\n  },\n  {\n    date: 'Nov 28, 2023',\n    'Alpha Corp': 148.37,\n    'Beta Solutions': 49.82,\n    'Gamma Industries': 81.56,\n  },\n  {\n    date: 'Nov 29, 2023',\n    'Alpha Corp': 139.56,\n    'Beta Solutions': 55.63,\n    'Gamma Industries': 92.38,\n  },\n  {\n    date: 'Nov 30, 2023',\n    'Alpha Corp': 145.83,\n    'Beta Solutions': 61.27,\n    'Gamma Industries': 88.75,\n  },\n  {\n    date: 'Dec 01, 2023',\n    'Alpha Corp': 138.29,\n    'Beta Solutions': 68.94,\n    'Gamma Industries': 93.42,\n  },\n  {\n    date: 'Dec 02, 2023',\n    'Alpha Corp': 129.64,\n    'Beta Solutions': 74.56,\n    'Gamma Industries': 97.18,\n  },\n  {\n    date: 'Dec 03, 2023',\n    'Alpha Corp': 119.82,\n    'Beta Solutions': 71.38,\n    'Gamma Industries': 89.43,\n  },\n  {\n    date: 'Dec 04, 2023',\n    'Alpha Corp': 128.54,\n    'Beta Solutions': 63.95,\n    'Gamma Industries': 92.76,\n  },\n  {\n    date: 'Dec 05, 2023',\n    'Alpha Corp': 137.21,\n    'Beta Solutions': 58.47,\n    'Gamma Industries': 84.29,\n  },\n  {\n    date: 'Dec 06, 2023',\n    'Alpha Corp': 134.68,\n    'Beta Solutions': 69.12,\n    'Gamma Industries': 79.38,\n  },\n  {\n    date: 'Dec 07, 2023',\n    'Alpha Corp': 152.73,\n    'Beta Solutions': 73.89,\n    'Gamma Industries': 81.42,\n  },\n  {\n    date: 'Dec 08, 2023',\n    'Alpha Corp': 168.59,\n    'Beta Solutions': 78.54,\n    'Gamma Industries': 75.68,\n  },\n];\n\nconst summary = [\n  {\n    name: 'Alpha Corp',\n    tickerSymbol: 'ACP',\n    value: '$168.59',\n    change: '+15.86',\n    percentageChange: '+10.4%',\n    changeType: 'positive',\n  },\n  {\n    name: 'Beta Solutions',\n    tickerSymbol: 'BTS',\n    value: '$78.54',\n    change: '+4.65',\n    percentageChange: '+6.3%',\n    changeType: 'positive',\n  },\n  {\n    name: 'Gamma Industries',\n    tickerSymbol: 'GMI',\n    value: '$75.68',\n    change: '-5.74',\n    percentageChange: '-7.1%',\n    changeType: 'negative',\n  },\n];\n\nconst sanitizeName = (name: string) => {\n  return name\n    .replace(/\\\\s+/g, '-')\n    .replace(/[^a-zA-Z0-9-]/g, '_')\n    .toLowerCase();\n};\n\nexport default function Stats10() {\n  return (\n    <div className=\"flex w-full items-center justify-center p-10\">\n      <dl className=\"grid w-full grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3\">\n        {summary.map((item) => {\n          const sanitizedName = sanitizeName(item.name);\n          const gradientId = `gradient-${sanitizedName}`;\n\n          const color =\n            item.changeType === 'positive'\n              ? 'hsl(142.1 76.2% 36.3%)'\n              : 'hsl(0 72.2% 50.6%)';\n\n          return (\n            <Card className=\"overflow-hidden p-0 shadow-2xs\" key={item.name}>\n              <CardContent className=\"p-0\">\n                <div className=\"p-4 pb-0\">\n                  <dt className=\"font-medium text-foreground text-sm\">\n                    {item.name}{' '}\n                    <span className=\"font-normal text-muted-foreground\">\n                      ({item.tickerSymbol})\n                    </span>\n                  </dt>\n                  <div className=\"flex items-baseline justify-between\">\n                    <dd\n                      className={cn(\n                        item.changeType === 'positive'\n                          ? 'text-green-600 dark:text-green-500'\n                          : 'text-red-600 dark:text-red-500',\n                        'font-semibold text-lg'\n                      )}\n                    >\n                      {item.value}\n                    </dd>\n                    <dd className=\"flex items-center space-x-1 text-sm\">\n                      <span className=\"font-medium text-foreground\">\n                        {item.change}\n                      </span>\n                      <span\n                        className={cn(\n                          item.changeType === 'positive'\n                            ? 'text-green-600 dark:text-green-500'\n                            : 'text-red-600 dark:text-red-500'\n                        )}\n                      >\n                        ({item.percentageChange})\n                      </span>\n                    </dd>\n                  </div>\n                </div>\n\n                <div className=\"mt-2 h-16 overflow-hidden\">\n                  <ChartContainer\n                    className=\"h-full w-full\"\n                    config={{\n                      [item.name]: {\n                        label: item.name,\n                        color,\n                      },\n                    }}\n                  >\n                    <AreaChart\n                      data={data}\n                      margin={{ top: 0, right: 0, bottom: 0, left: 0 }}\n                    >\n                      <defs>\n                        <linearGradient\n                          id={gradientId}\n                          x1=\"0\"\n                          x2=\"0\"\n                          y1=\"0\"\n                          y2=\"1\"\n                        >\n                          <stop\n                            offset=\"5%\"\n                            stopColor={color}\n                            stopOpacity={0.3}\n                          />\n                          <stop\n                            offset=\"95%\"\n                            stopColor={color}\n                            stopOpacity={0}\n                          />\n                        </linearGradient>\n                      </defs>\n                      <XAxis dataKey=\"date\" hide={true} />\n                      <Area\n                        dataKey={item.name}\n                        fill={`url(#${gradientId})`}\n                        fillOpacity={0.4}\n                        stroke={color}\n                        strokeWidth={1.5}\n                        type=\"monotone\"\n                      />\n                    </AreaChart>\n                  </ChartContainer>\n                </div>\n              </CardContent>\n            </Card>\n          );\n        })}\n      </dl>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-10/stats-10.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-10 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-10/stats-10.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-10"
      }
    },
    {
      "name": "stats-11",
      "type": "registry:block",
      "title": "Stats Dashboard with Progress Bars",
      "description": "Stats Dashboard with Progress Bars. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/stats-11/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-11/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-11/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-11/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/dialog.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-11/shared/components/ui/dialog.tsx",
          "content": "'use client';\n\nimport { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { XIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { Button } from \"./button\";\nimport { cn } from \"../../lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n  className,\n  ...props\n}: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      className={cn(\n        'data-open:fade-in-0 data-closed:fade-out-0 fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-open:animate-in supports-backdrop-filter:backdrop-blur-xs',\n        className\n      )}\n      data-slot=\"dialog-overlay\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        className={cn(\n          '-translate-x-1/2 -translate-y-1/2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] gap-4 rounded-xl bg-popover p-4 text-popover-foreground text-sm outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in sm:max-w-sm',\n          className\n        )}\n        data-slot=\"dialog-content\"\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={\n              <Button\n                className=\"absolute top-2 right-2\"\n                size=\"icon-sm\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('flex flex-col gap-2', className)}\n      data-slot=\"dialog-header\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<'div'> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      className={cn(\n        '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',\n        className\n      )}\n      data-slot=\"dialog-footer\"\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>\n          Close\n        </DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      className={cn('font-medium text-base leading-none', className)}\n      data-slot=\"dialog-title\"\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({\n  className,\n  ...props\n}: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      className={cn(\n        'text-muted-foreground text-sm *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n        className\n      )}\n      data-slot=\"dialog-description\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-11/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/label.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-11/shared/components/ui/label.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Label({ className, ...props }: React.ComponentProps<'label'>) {\n  return (\n    <label\n      className={cn(\n        'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',\n        className\n      )}\n      data-slot=\"label\"\n      {...props}\n    />\n  );\n}\n\nexport { Label };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-11/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-11.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-11/stats-11.tsx",
          "content": "'use client';\n\nimport { Box, Edit } from 'lucide-react';\nimport type React from 'react';\nimport { useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"./shared/components/ui/dialog\";\nimport { Input } from \"./shared/components/ui/input\";\nimport { Label } from \"./shared/components/ui/label\";\n\ninterface MetricCardProps {\n  title: string;\n  value: string;\n  limit: string;\n  percentage: number;\n  status?: string;\n  statusColor?: string;\n  progressColor: string;\n  details?: Array<{ label: string; value: string; color: string }>;\n  actionLabel: string;\n  actionIcon: React.ReactNode;\n  warningMessage?: string;\n  onActionClick?: () => void;\n}\n\nfunction MetricCard({\n  title,\n  value,\n  limit,\n  percentage,\n  status,\n  statusColor = 'text-emerald-600 dark:text-emerald-400',\n  progressColor,\n  details,\n  actionLabel,\n  actionIcon,\n  warningMessage,\n  onActionClick,\n}: MetricCardProps) {\n  const renderProgressBar = () => {\n    if (details && title === 'Commands') {\n      const writes = Number.parseInt(details[0].value.replace(/,/g, ''));\n      const reads = Number.parseInt(details[1].value.replace(/,/g, ''));\n      const total = writes + reads;\n      const writesPercentage = (writes / total) * 100;\n      const readsPercentage = (reads / total) * 100;\n\n      return (\n        <div className=\"relative h-1 w-full overflow-hidden rounded-full bg-muted\">\n          <div\n            className=\"absolute left-0 h-full w-full origin-left bg-emerald-500 transition-transform duration-200 ease-out\"\n            style={{ transform: `scaleX(${writesPercentage / 100})` }}\n          />\n          <div\n            className=\"absolute left-0 h-full w-full origin-left bg-blue-500 transition-transform duration-200 ease-out\"\n            style={{\n              transform: `translateX(${writesPercentage}%) scaleX(${readsPercentage / 100})`,\n            }}\n          />\n        </div>\n      );\n    }\n\n    return (\n      <div className=\"relative h-1 w-full overflow-hidden rounded-full bg-muted\">\n        <div\n          className={`h-full w-full origin-left transition-transform duration-200 ease-out ${progressColor}`}\n          style={{ transform: `scaleX(${Math.min(percentage, 100) / 100})` }}\n        />\n      </div>\n    );\n  };\n\n  return (\n    <Card className=\"relative max-w-[280px] overflow-hidden shadow-2xs\">\n      <CardContent className=\"p-4 py-0\">\n        <h5 className=\"font-normal text-muted-foreground text-xs uppercase leading-none tracking-wide dark:text-foreground/80\">\n          {title}\n        </h5>\n\n        <div className=\"mt-2 flex items-baseline gap-1\">\n          <div className=\"font-medium text-[1.2rem] text-foreground tabular-nums leading-none\">\n            {value}\n          </div>\n          <div className=\"text-muted-foreground text-xs leading-none\">\n            / {limit}\n          </div>\n        </div>\n\n        <div className=\"mt-3\">\n          {renderProgressBar()}\n\n          {details && (\n            <div className=\"my-6 mb-8\">\n              <div className=\"flex flex-col gap-3\">\n                {details.map((detail, index) => (\n                  <div\n                    className=\"flex w-full items-center text-muted-foreground text-xs leading-none dark:text-foreground/70\"\n                    key={index}\n                  >\n                    <div\n                      className={`mr-[6px] h-2 w-2 rounded-full ${detail.color}`}\n                    />\n                    <div className=\"mr-1\">{detail.label}</div>\n                    <div className=\"h-[9px] flex-1 border-border border-b-2 border-dotted\" />\n                    <div className=\"ml-1 tabular-nums\">{detail.value}</div>\n                  </div>\n                ))}\n              </div>\n            </div>\n          )}\n\n          {status && (\n            <div className=\"pt-2\">\n              <div className={statusColor}>{status}</div>\n            </div>\n          )}\n\n          {warningMessage && (\n            <div className=\"pt-2\">\n              <div className=\"text-amber-700 text-sm dark:text-amber-400\">\n                {warningMessage}\n              </div>\n            </div>\n          )}\n        </div>\n\n        <div className=\"absolute right-0 bottom-0 left-0\">\n          <Button\n            className=\"h-8 w-full justify-start gap-0 rounded-none bg-muted/50 text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300\"\n            onClick={onActionClick}\n            variant=\"ghost\"\n          >\n            {actionIcon}\n            <span className=\"ml-1 text-xs\">{actionLabel}</span>\n          </Button>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\nfunction BudgetDialog({\n  open,\n  onOpenChange,\n}: {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n}) {\n  const [budget, setBudget] = useState('150');\n\n  const handleUpdate = () => {\n    onOpenChange(false);\n  };\n\n  return (\n    <Dialog onOpenChange={onOpenChange} open={open}>\n      <DialogContent className=\"sm:max-w-md\">\n        <DialogHeader>\n          <DialogTitle>Update budget</DialogTitle>\n          <DialogDescription>\n            When your monthly cost reaches the max budget, we send an email and\n            throttle your database. You will not be charged beyond your set\n            budget for this database.\n          </DialogDescription>\n        </DialogHeader>\n\n        <div className=\"space-y-2\">\n          <Label htmlFor=\"budget\">Max budget per month</Label>\n          <Input\n            id=\"budget\"\n            onChange={(e) => setBudget(e.target.value)}\n            placeholder=\"150\"\n            type=\"number\"\n            value={budget}\n          />\n        </div>\n\n        <DialogFooter className=\"pt-2\">\n          <Button onClick={() => onOpenChange(false)} variant=\"outline\">\n            Cancel\n          </Button>\n          <Button onClick={handleUpdate}>Update</Button>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n\nexport default function Stats11() {\n  const [budgetDialogOpen, setBudgetDialogOpen] = useState(false);\n\n  return (\n    <>\n      <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-4\">\n        <MetricCard\n          actionIcon={<Box className=\"h-4 w-4\" />}\n          actionLabel=\"Upgrade\"\n          details={[\n            { label: 'Writes', value: '11,276,493', color: 'bg-emerald-500' },\n            { label: 'Reads', value: '2,548,921', color: 'bg-blue-500' },\n          ]}\n          limit=\"Unlimited\"\n          percentage={67}\n          progressColor=\"bg-blue-500\"\n          title=\"Commands\"\n          value=\"13.8M\"\n        />\n\n        <MetricCard\n          actionIcon={<Box className=\"h-4 w-4\" />}\n          actionLabel=\"Upgrade\"\n          limit=\"150 GB\"\n          percentage={94}\n          progressColor=\"bg-orange-500\"\n          title=\"Bandwidth\"\n          value=\"141 GB\"\n          warningMessage=\"There will be a charge for the excessive bandwidth over the limit.\"\n        />\n\n        <MetricCard\n          actionIcon={<Box className=\"h-4 w-4\" />}\n          actionLabel=\"Upgrade\"\n          limit=\"500 GB\"\n          percentage={7.4}\n          progressColor=\"bg-emerald-500\"\n          status=\"It's all right.\"\n          title=\"Storage\"\n          value=\"37 GB\"\n        />\n\n        <MetricCard\n          actionIcon={<Edit className=\"h-4 w-4\" />}\n          actionLabel=\"Change Budget\"\n          limit=\"$150 Budget\"\n          onActionClick={() => setBudgetDialogOpen(true)}\n          percentage={48.95}\n          progressColor=\"bg-emerald-500\"\n          status=\"It's all right.\"\n          title=\"Cost\"\n          value=\"$73.42\"\n        />\n      </div>\n\n      <BudgetDialog\n        onOpenChange={setBudgetDialogOpen}\n        open={budgetDialogOpen}\n      />\n    </>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-11/stats-11.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-11 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-11/stats-11.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-11"
      }
    },
    {
      "name": "stats-12",
      "type": "registry:block",
      "title": "Stats Usage Dashboard",
      "description": "Stats Usage Dashboard. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "recharts@3.8.0"
      ],
      "files": [
        {
          "path": "licenses/stats-12/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-12/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-12/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-12/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "components/ui/chart.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-12/shared/components/ui/chart.tsx",
          "content": "'use client';\n\nimport * as React from 'react';\nimport type { TooltipValueType } from 'recharts';\nimport * as RechartsPrimitive from 'recharts';\n\nimport { cn } from \"../../lib/utils\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: '', dark: '.dark' } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n  string,\n  {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n  } & (\n    | { color?: string; theme?: never }\n    | { color?: never; theme: Record<keyof typeof THEMES, string> }\n  )\n>;\n\ntype ChartContextProps = {\n  config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error('useChart must be used within a <ChartContainer />');\n  }\n\n  return context;\n}\n\nfunction ChartContainer({\n  id,\n  className,\n  children,\n  config,\n  initialDimension = INITIAL_DIMENSION,\n  ...props\n}: React.ComponentProps<'div'> & {\n  config: ChartConfig;\n  children: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >['children'];\n  initialDimension?: {\n    width: number;\n    height: number;\n  };\n}) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id ?? uniqueId.replace(/:/g, '')}`;\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        className={cn(\n          \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-hidden [&_.recharts-surface]:outline-hidden\",\n          className\n        )}\n        data-chart={chartId}\n        data-slot=\"chart\"\n        {...props}\n      >\n        <ChartStyle config={config} id={chartId} />\n        <RechartsPrimitive.ResponsiveContainer\n          initialDimension={initialDimension}\n        >\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(\n    ([, config]) => config.theme ?? config.color\n  );\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  return (\n    <style\n      dangerouslySetInnerHTML={{\n        __html: Object.entries(THEMES)\n          .map(\n            ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color =\n      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??\n      itemConfig.color;\n    return color ? `  --color-${key}: ${color};` : null;\n  })\n  .join('\\n')}\n}\n`\n          )\n          .join('\\n'),\n      }}\n    />\n  );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = 'dot',\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  color,\n  nameKey,\n  labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<'div'> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: 'line' | 'dot' | 'dashed';\n    nameKey?: string;\n    labelKey?: string;\n  } & Omit<\n    RechartsPrimitive.DefaultTooltipContentProps<\n      TooltipValueType,\n      TooltipNameType\n    >,\n    'accessibilityLayer'\n  >) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? 'value'}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === 'string'\n        ? (config[label]?.label ?? label)\n        : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn('font-medium', labelClassName)}>\n          {labelFormatter(value, payload)}\n        </div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn('font-medium', labelClassName)}>{value}</div>;\n  }, [\n    label,\n    labelFormatter,\n    payload,\n    hideLabel,\n    labelClassName,\n    config,\n    labelKey,\n  ]);\n\n  if (!(active && payload?.length)) {\n    return null;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== 'dot';\n\n  return (\n    <div\n      className={cn(\n        'grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl',\n        className\n      )}\n    >\n      {nestLabel ? null : tooltipLabel}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== 'none')\n          .map((item, index) => {\n            const key = `${nameKey ?? item.name ?? item.dataKey ?? 'value'}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n            const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n            return (\n              <div\n                className={cn(\n                  'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground',\n                  indicator === 'dot' && 'items-center'\n                )}\n                key={index}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\n                            'shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)',\n                            {\n                              'h-2.5 w-2.5': indicator === 'dot',\n                              'w-1': indicator === 'line',\n                              'w-0 border-[1.5px] border-dashed bg-transparent':\n                                indicator === 'dashed',\n                              'my-0.5': nestLabel && indicator === 'dashed',\n                            }\n                          )}\n                          style={\n                            {\n                              '--color-bg': indicatorColor,\n                              '--color-border': indicatorColor,\n                            } as React.CSSProperties\n                          }\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        'flex flex-1 justify-between leading-none',\n                        nestLabel ? 'items-end' : 'items-center'\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label ?? item.name}\n                        </span>\n                      </div>\n                      {item.value != null && (\n                        <span className=\"font-medium font-mono text-foreground tabular-nums\">\n                          {typeof item.value === 'number'\n                            ? item.value.toLocaleString()\n                            : String(item.value)}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  payload,\n  verticalAlign = 'bottom',\n  nameKey,\n}: React.ComponentProps<'div'> & {\n  hideIcon?: boolean;\n  nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        'flex items-center justify-center gap-4',\n        verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n        className\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== 'none')\n        .map((item, index) => {\n          const key = `${nameKey ?? item.dataKey ?? 'value'}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n          return (\n            <div\n              className={cn(\n                'flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground'\n              )}\n              key={index}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <div\n                  className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n                  style={{\n                    backgroundColor: item.color,\n                  }}\n                />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\nfunction getPayloadConfigFromPayload(\n  config: ChartConfig,\n  payload: unknown,\n  key: string\n) {\n  if (typeof payload !== 'object' || payload === null) {\n    return;\n  }\n\n  const payloadPayload =\n    'payload' in payload &&\n    typeof payload.payload === 'object' &&\n    payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (\n    key in payload &&\n    typeof payload[key as keyof typeof payload] === 'string'\n  ) {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === 'string'\n  ) {\n    configLabelKey = payloadPayload[\n      key as keyof typeof payloadPayload\n    ] as string;\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n  ChartContainer,\n  ChartTooltip,\n  ChartTooltipContent,\n  ChartLegend,\n  ChartLegendContent,\n  ChartStyle,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-12/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-12.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-12/stats-12.tsx",
          "content": "'use client';\n\nimport { Cell, Pie, PieChart } from 'recharts';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Card, CardContent, CardHeader } from \"./shared/components/ui/card\";\nimport { type ChartConfig, ChartContainer } from \"./shared/components/ui/chart\";\n\ninterface UsageItem {\n  name: string;\n  current: string;\n  limit: string;\n  percentage: number;\n  href?: string;\n}\n\nconst usageData: UsageItem[] = [\n  { name: 'ISR Reads', current: '358K', limit: '1M', percentage: 35.8 },\n  { name: 'Edge Requests', current: '317K', limit: '1M', percentage: 31.7 },\n  {\n    name: 'Fast Origin Transfer',\n    current: '3.07 GB',\n    limit: '10 GB',\n    percentage: 30.7,\n  },\n  {\n    name: 'Speed Insights Data Points',\n    current: '791',\n    limit: '10K',\n    percentage: 7.9,\n  },\n  {\n    name: 'Fast Data Transfer',\n    current: '4.98 GB',\n    limit: '100 GB',\n    percentage: 5.0,\n  },\n  {\n    name: 'Function Duration',\n    current: '3.1 GB-Hrs',\n    limit: '100 GB-Hrs',\n    percentage: 3.1,\n  },\n  {\n    name: 'Web Analytics Events',\n    current: '1.3K',\n    limit: '50K',\n    percentage: 2.6,\n  },\n  { name: 'ISR Writes', current: '4.8K', limit: '200K', percentage: 2.4 },\n  {\n    name: 'Function Invocations',\n    current: '19K',\n    limit: '1M',\n    percentage: 1.9,\n  },\n  {\n    name: 'Image Optimization - Cache Reads',\n    current: '4.3K',\n    limit: '300K',\n    percentage: 1.4,\n  },\n];\n\nconst chartConfig = {\n  used: {\n    label: 'Used',\n    color: 'hsl(var(--primary))',\n  },\n  remaining: {\n    label: 'Remaining',\n    color: 'hsl(var(--muted))',\n  },\n} satisfies ChartConfig;\n\nfunction DonutChart({ percentage }: { percentage: number }) {\n  const backgroundData = [{ name: 'background', value: 100, fill: '#E5E7EB' }];\n  const foregroundData = [\n    {\n      name: 'used',\n      value: Math.max(0, Math.min(100, Number(percentage))),\n      fill: '#3B82F6',\n    },\n    {\n      name: 'empty',\n      value: 100 - Math.max(0, Math.min(100, Number(percentage))),\n      fill: 'transparent',\n    },\n  ];\n\n  return (\n    <ChartContainer\n      className=\"aspect-square h-6 w-6 shrink-0\"\n      config={chartConfig}\n    >\n      <PieChart>\n        <Pie\n          cx=\"50%\"\n          cy=\"50%\"\n          data={backgroundData}\n          dataKey=\"value\"\n          innerRadius={6}\n          isAnimationActive={false}\n          nameKey=\"name\"\n          outerRadius={10}\n        >\n          {backgroundData.map((entry, index) => (\n            <Cell fill={entry.fill} key={`bg-cell-${index}`} />\n          ))}\n        </Pie>\n        <Pie\n          cx=\"50%\"\n          cy=\"50%\"\n          data={foregroundData}\n          dataKey=\"value\"\n          endAngle={-270}\n          innerRadius={6}\n          nameKey=\"name\"\n          outerRadius={10}\n          startAngle={90}\n        >\n          {foregroundData.map((entry, index) => (\n            <Cell fill={entry.fill} key={`fg-cell-${index}`} />\n          ))}\n        </Pie>\n      </PieChart>\n    </ChartContainer>\n  );\n}\n\nexport default function Stats12() {\n  return (\n    <Card className=\"w-full max-w-md gap-3 py-5 shadow-2xs\">\n      <CardHeader className=\"px-5\">\n        <div className=\"flex items-center justify-between\">\n          <div className=\"flex flex-col\">\n            <h3 className=\"text-balance font-medium text-sm\">Last 30 days</h3>\n            <p className=\"text-pretty font-medium text-muted-foreground text-xs\">\n              Updated just now\n            </p>\n          </div>\n          <Button className=\"h-6 font-medium text-xs\" size=\"sm\">\n            Upgrade\n          </Button>\n        </div>\n      </CardHeader>\n\n      <CardContent className=\"px-3 pt-0\">\n        <div className=\"space-y-0\">\n          {usageData.map((item, index) => (\n            <div\n              className={`flex items-center gap-3 rounded-sm p-2 transition-colors hover:bg-muted/50 ${\n                index % 2 === 1 ? 'bg-muted/20' : ''\n              }`}\n              key={item.name}\n            >\n              <DonutChart percentage={item.percentage} />\n              <span className=\"flex-1 truncate text-sm leading-4\">\n                {item.name}\n              </span>\n              <span className=\"font-medium text-muted-foreground text-xs tabular-nums tracking-tighter\">\n                {item.current} /{' '}\n                <span className=\"text-foreground\">{item.limit}</span>\n              </span>\n            </div>\n          ))}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-12/stats-12.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-12 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-12/stats-12.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-12"
      }
    },
    {
      "name": "stats-13",
      "type": "registry:block",
      "title": "Stats with Segmented Progress",
      "description": "Stats with Segmented Progress. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/stats-13/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-13/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-13/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-13/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-13.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-13/stats-13.tsx",
          "content": "'use client';\n\nimport { Card, CardContent } from \"./shared/components/ui/card\";\nimport { cn } from \"./shared/lib/utils\";\n\ninterface Props {\n  title?: string;\n  used?: number;\n  total?: number;\n  usedLabel?: string;\n  totalLabel?: string;\n  segments?: {\n    label: string;\n    value: number;\n    color: string;\n  }[];\n  className?: string;\n}\n\nconst defaultSegments: NonNullable<Props['segments']> = [\n  { label: 'Documents', value: 2400, color: 'bg-blue-500' },\n  { label: 'Photos', value: 1800, color: 'bg-emerald-500' },\n  { label: 'Videos', value: 3200, color: 'bg-amber-500' },\n  { label: 'Music', value: 900, color: 'bg-purple-500' },\n];\n\nexport default function Stats13({\n  title = 'Using Storage',\n  used = 8300,\n  total = 15,\n  usedLabel = 'MB',\n  totalLabel = 'GB',\n  segments = defaultSegments,\n  className,\n}: Props) {\n  const totalValue = total * 1000;\n  const freeValue = totalValue - used;\n\n  return (\n    <Card className={cn('w-full max-w-4xl shadow-sm', className)}>\n      <CardContent className=\"py-0\">\n        <p className=\"mb-4 text-pretty text-base text-muted-foreground\">\n          {title}{' '}\n          <span className=\"font-semibold text-foreground tabular-nums\">\n            {used.toLocaleString(undefined, {\n              minimumFractionDigits: 0,\n              maximumFractionDigits: 2,\n            })}{' '}\n            {usedLabel}\n          </span>{' '}\n          of {total} {totalLabel}\n        </p>\n\n        <div className=\"mb-4 flex h-2.5 w-full overflow-hidden rounded-full bg-muted\">\n          {segments.map((segment) => {\n            const percentage = (segment.value / totalValue) * 100;\n            return (\n              <div\n                aria-label={segment.label}\n                aria-valuemax={totalValue}\n                aria-valuemin={0}\n                aria-valuenow={segment.value}\n                className={cn('h-full', segment.color)}\n                key={segment.label}\n                role=\"progressbar\"\n                style={{ width: `${percentage}%` }}\n              />\n            );\n          })}\n        </div>\n\n        <div className=\"flex flex-wrap items-center gap-x-8 gap-y-2\">\n          {segments.map((segment) => (\n            <div className=\"flex items-center gap-2\" key={segment.label}>\n              <span\n                aria-hidden=\"true\"\n                className={cn('size-3 shrink-0 rounded', segment.color)}\n              />\n              <span className=\"text-muted-foreground text-sm\">\n                {segment.label}\n              </span>\n              <span className=\"text-muted-foreground text-sm tabular-nums\">\n                {Math.round(segment.value)}\n                {usedLabel}\n              </span>\n            </div>\n          ))}\n          <div className=\"flex items-center gap-2\">\n            <span\n              aria-hidden=\"true\"\n              className=\"size-3 shrink-0 rounded-sm bg-muted\"\n            />\n            <span className=\"text-muted-foreground text-sm\">Free</span>\n            <span className=\"text-muted-foreground text-sm tabular-nums\">\n              {Math.round(freeValue)}\n              {usedLabel}\n            </span>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/stats-13/stats-13.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-13 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-13/stats-13.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-13"
      }
    },
    {
      "name": "stats-14",
      "type": "registry:block",
      "title": "Stats with Usage Breakdown",
      "description": "Stats with Usage Breakdown. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/stats-14/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-14/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-14/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/card.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-14/shared/components/ui/card.tsx",
          "content": "import type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Card({\n  className,\n  size = 'default',\n  ...props\n}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {\n  return (\n    <div\n      className={cn(\n        'group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-card-foreground text-sm ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 has-data-[slot=card-footer]:pb-0 data-[size=sm]:has-data-[slot=card-footer]:pb-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl',\n        className\n      )}\n      data-size={size}\n      data-slot=\"card\"\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-header\"\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'font-medium text-base leading-snug group-data-[size=sm]/card:text-sm',\n        className\n      )}\n      data-slot=\"card-title\"\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('text-muted-foreground text-sm', className)}\n      data-slot=\"card-description\"\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className\n      )}\n      data-slot=\"card-action\"\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn('px-(--card-spacing)', className)}\n      data-slot=\"card-content\"\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)',\n        className\n      )}\n      data-slot=\"card-footer\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-14/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-14.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-14/stats-14.tsx",
          "content": "import { Badge } from \"./shared/components/ui/badge\";\nimport { Card, CardContent } from \"./shared/components/ui/card\";\n\ninterface UsageItem {\n  label: string;\n  amount: number;\n  percentage: number;\n  color: 'emerald' | 'amber' | 'rose';\n}\n\nconst data: UsageItem[] = [\n  { label: 'Compute', amount: 450, percentage: 52.3, color: 'emerald' },\n  { label: 'Storage', amount: 285, percentage: 33.1, color: 'amber' },\n  { label: 'Bandwidth', amount: 125, percentage: 14.6, color: 'rose' },\n];\n\nconst colorClasses = {\n  emerald: 'bg-emerald-500 dark:bg-emerald-400',\n  amber: 'bg-amber-500 dark:bg-amber-400',\n  rose: 'bg-rose-500 dark:bg-rose-400',\n};\n\nexport function Stats14() {\n  return (\n    <Card className=\"w-full max-w-sm shadow-none\">\n      <CardContent className=\"flex flex-col justify-between pt-0\">\n        <div>\n          <div className=\"flex items-center gap-2\">\n            <h3 className=\"text-balance font-bold text-foreground text-sm\">\n              Usage\n            </h3>\n            <Badge\n              className=\"bg-amber-50 text-amber-700 ring-1 ring-amber-500/30 dark:bg-amber-400/10 dark:text-amber-300 dark:ring-amber-400/20\"\n              variant=\"secondary\"\n            >\n              +12.5%\n            </Badge>\n          </div>\n\n          <p className=\"mt-2 flex items-baseline gap-2 text-pretty\">\n            <span className=\"text-foreground text-xl\">$860</span>\n            <span className=\"text-muted-foreground text-sm\">this month</span>\n          </p>\n\n          <div className=\"mt-4\">\n            <p className=\"text-pretty font-medium text-foreground text-sm\">\n              Resource breakdown\n            </p>\n            <div className=\"mt-2 flex items-center gap-0.5\">\n              {data.map((item, index) => (\n                <div\n                  className={`${colorClasses[item.color]} h-1.5 rounded-xs`}\n                  key={index}\n                  style={{ width: `${item.percentage}%` }}\n                />\n              ))}\n            </div>\n          </div>\n\n          <ul className=\"mt-5 space-y-2\" role=\"list\">\n            {data.map((item, index) => (\n              <li className=\"flex items-center gap-2 text-xs\" key={index}>\n                <span\n                  aria-hidden=\"true\"\n                  className={`${colorClasses[item.color]} size-2.5 rounded-xs`}\n                />\n                <span className=\"text-foreground\">{item.label}</span>\n                <span className=\"text-muted-foreground\">\n                  (${item.amount} / {item.percentage}%)\n                </span>\n              </li>\n            ))}\n          </ul>\n        </div>\n\n        <p className=\"mt-6 text-pretty text-muted-foreground text-xs\">\n          Configure limits in{' '}\n          <a\n            className=\"text-emerald-600 hover:underline dark:text-emerald-400\"\n            href=\"#\"\n          >\n            resource settings.\n          </a>\n        </p>\n      </CardContent>\n    </Card>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Stats14 from @components/uiblocks/stats-14/stats-14.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-14 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-14/stats-14.tsx",
        "exportName": "Stats14",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-14"
      }
    },
    {
      "name": "stats-15",
      "type": "registry:block",
      "title": "Stats with Value Breakdown",
      "description": "Stats with Value Breakdown. Product metrics, usage summaries, and dashboard statistics with clear visual hierarchy.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/stats-15/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/stats-15/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-15/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/stats/stats-15.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/stats-15/stats-15.tsx",
          "content": "import { cn } from \"./shared/lib/utils\";\n\nconst data = [\n  {\n    label: 'After 1 year',\n    value: '$2,400',\n    percentage: '+8.2%',\n  },\n  {\n    label: 'After 5 years',\n    value: '$14,800',\n    percentage: '+24.6%',\n  },\n  {\n    label: 'After 10 years',\n    value: '$38,500',\n    percentage: '+52.1%',\n  },\n];\n\nexport function Stats15() {\n  return (\n    <div className=\"w-full max-w-2xs\">\n      <h3 className=\"text-balance font-medium text-foreground text-sm\">\n        Investment growth projection\n      </h3>\n      <ul className=\"mt-2 divide-y divide-border text-sm\" role=\"list\">\n        {data.map((item, index) => (\n          <li className=\"flex items-center justify-between py-3\" key={index}>\n            <span className=\"text-muted-foreground\">{item.label}</span>\n            <span className=\"flex items-center gap-3 tabular-nums\">\n              <span className=\"text-right font-medium text-foreground\">\n                {item.value}\n              </span>\n              <span aria-hidden=\"true\" className=\"h-5 w-px bg-border\" />\n              <span\n                className={cn(\n                  'w-15 rounded px-1.5 py-1 text-center font-semibold text-xs',\n                  'bg-emerald-50 text-emerald-600 dark:bg-emerald-400/10 dark:text-emerald-400'\n                )}\n              >\n                {item.percentage}\n              </span>\n            </span>\n          </li>\n        ))}\n      </ul>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "stats",
        "application",
        "clean-saas"
      ],
      "docs": "Import the named export Stats15 from @components/uiblocks/stats-15/stats-15.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/stats/stats-15 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "stats",
          "analytics",
          "metrics",
          "dashboard",
          "usage",
          "统计",
          "数据",
          "仪表盘",
          "用量"
        ],
        "contentGuidelines": [
          "Label the time window and units for every value.",
          "Test large values, negative trends, and missing data."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/stats-15/stats-15.tsx",
        "exportName": "Stats15",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/stats-15"
      }
    },
    {
      "name": "table-01",
      "type": "registry:block",
      "title": "Table with Accordion",
      "description": "Table with Accordion. Product data tables with row actions, grouping, filters, and useful data density.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/table-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/table-01/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-01/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/collapsible.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-01/shared/components/ui/collapsible.tsx",
          "content": "'use client';\n\nimport { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible';\n\nfunction Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {\n  return <CollapsiblePrimitive.Root data-slot=\"collapsible\" {...props} />;\n}\n\nfunction CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {\n  return (\n    <CollapsiblePrimitive.Trigger data-slot=\"collapsible-trigger\" {...props} />\n  );\n}\n\nfunction CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {\n  return (\n    <CollapsiblePrimitive.Panel data-slot=\"collapsible-content\" {...props} />\n  );\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n"
        },
        {
          "path": "components/ui/table.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-01/shared/components/ui/table.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div\n      className=\"relative w-full overflow-x-auto\"\n      data-slot=\"table-container\"\n    >\n      <table\n        className={cn('w-full caption-bottom text-sm', className)}\n        data-slot=\"table\"\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return (\n    <thead\n      className={cn('[&_tr]:border-b', className)}\n      data-slot=\"table-header\"\n      {...props}\n    />\n  );\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      className={cn('[&_tr:last-child]:border-0', className)}\n      data-slot=\"table-body\"\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      className={cn(\n        'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',\n        className\n      )}\n      data-slot=\"table-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      className={cn(\n        'border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',\n        className\n      )}\n      data-slot=\"table-row\"\n      {...props}\n    />\n  );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      className={cn(\n        'h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-head\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      className={cn(\n        'whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-cell\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      className={cn('mt-4 text-muted-foreground text-sm', className)}\n      data-slot=\"table-caption\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/table-01/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/tables/table-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-01/table-01.tsx",
          "content": "'use client';\n\nimport { ChevronDown, ChevronRight } from 'lucide-react';\nimport { useState } from 'react';\nimport { Button } from \"./shared/components/ui/button\";\nimport { Collapsible, CollapsibleContent } from \"./shared/components/ui/collapsible\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"./shared/components/ui/table\";\nimport { cn } from \"./shared/lib/utils\";\n\ninterface TableRowData {\n  id: string;\n  name: string;\n  category: string;\n  value: number;\n  date: string;\n  children?: TableRowData[];\n}\n\nconst sampleData: TableRowData[] = [\n  {\n    id: '001',\n    name: 'Project Alpha',\n    category: 'Development',\n    value: 45_000,\n    date: '2024-01-15',\n    children: [\n      {\n        id: '001-01',\n        name: 'Frontend Module',\n        category: 'Development',\n        value: 15_000,\n        date: '2024-01-16',\n      },\n      {\n        id: '001-02',\n        name: 'Backend Module',\n        category: 'Development',\n        value: 20_000,\n        date: '2024-01-21',\n      },\n      {\n        id: '001-03',\n        name: 'Testing Suite',\n        category: 'Development',\n        value: 10_000,\n        date: '2024-01-24',\n      },\n    ],\n  },\n  {\n    id: '002',\n    name: 'Marketing Campaign',\n    category: 'Marketing',\n    value: 28_500,\n    date: '2024-01-18',\n    children: [\n      {\n        id: '002-01',\n        name: 'Social Media',\n        category: 'Marketing',\n        value: 12_000,\n        date: '2024-01-19',\n      },\n      {\n        id: '002-02',\n        name: 'Email Marketing',\n        category: 'Marketing',\n        value: 8500,\n        date: '2024-01-22',\n      },\n      {\n        id: '002-03',\n        name: 'SEO Optimization',\n        category: 'Marketing',\n        value: 8000,\n        date: '2024-01-23',\n      },\n    ],\n  },\n  {\n    id: '003',\n    name: 'Infrastructure Upgrade',\n    category: 'Operations',\n    value: 67_200,\n    date: '2024-01-20',\n    children: [\n      {\n        id: '003-01',\n        name: 'Cloud Migration',\n        category: 'Operations',\n        value: 35_000,\n        date: '2024-01-21',\n      },\n      {\n        id: '003-02',\n        name: 'Security Enhancement',\n        category: 'Operations',\n        value: 32_200,\n        date: '2024-01-24',\n      },\n    ],\n  },\n  {\n    id: '004',\n    name: 'Customer Support',\n    category: 'Service',\n    value: 19_800,\n    date: '2024-01-25',\n  },\n];\n\ninterface AccordionRowProps {\n  row: TableRowData;\n  defaultOpen?: boolean;\n}\n\nfunction AccordionRow({ row, defaultOpen = false }: AccordionRowProps) {\n  const [isOpen, setIsOpen] = useState(defaultOpen);\n  const hasChildren = row.children && row.children.length > 0;\n\n  return (\n    <Collapsible\n      onOpenChange={setIsOpen}\n      open={isOpen}\n      render={\n        <TableBody className=\"[&_tr:last-child]:border-b last:[&_tr:last-child]:border-0\" />\n      }\n    >\n      <TableRow\n        className={cn(\n          'grid grid-cols-[40px_80px_180px_110px_100px_110px] bg-muted/50 hover:bg-muted/50',\n          isOpen && 'border-b-0'\n        )}\n      >\n        <TableCell className=\"p-0\">\n          <Button\n            aria-label={isOpen ? 'Collapse row' : 'Expand row'}\n            className={cn(\n              'h-full w-full rounded-none p-3 text-muted-foreground transition-colors',\n              hasChildren && 'hover:bg-transparent hover:text-foreground',\n              !hasChildren && 'cursor-default opacity-30'\n            )}\n            disabled={!hasChildren}\n            onClick={() => setIsOpen(!isOpen)}\n            size=\"icon\"\n            variant=\"ghost\"\n          >\n            {hasChildren ? (\n              isOpen ? (\n                <ChevronDown className=\"h-4 w-4 transition-transform duration-200\" />\n              ) : (\n                <ChevronRight className=\"h-4 w-4 transition-transform duration-200\" />\n              )\n            ) : (\n              <div className=\"h-4 w-4\" />\n            )}\n          </Button>\n        </TableCell>\n        <TableCell className=\"p-3 font-medium font-mono text-muted-foreground text-sm\">\n          {row.id}\n        </TableCell>\n        <TableCell className=\"p-3 font-medium text-sm\">{row.name}</TableCell>\n        <TableCell className=\"p-3 text-muted-foreground text-sm\">\n          {row.category}\n        </TableCell>\n        <TableCell className=\"p-3 text-right font-mono font-semibold text-sm tabular-nums\">\n          ${row.value.toLocaleString()}\n        </TableCell>\n        <TableCell className=\"p-3 text-muted-foreground text-sm\">\n          {row.date}\n        </TableCell>\n      </TableRow>\n\n      {hasChildren && (\n        <TableRow className=\"grid grid-cols-[40px_80px_180px_110px_100px_110px] border-b-0 hover:bg-transparent\">\n          <TableCell className=\"col-span-6 p-0\" colSpan={6}>\n            <CollapsibleContent>\n              <div className=\"w-full border-border border-b bg-muted/20\">\n                <Table>\n                  <TableHeader>\n                    <TableRow className=\"grid grid-cols-[40px_80px_180px_110px_100px_110px] border-b-0 bg-muted/30\">\n                      <TableHead className=\"flex h-7 items-center border-border border-y px-3 py-1.5\" />\n                      <TableHead className=\"flex h-7 items-center border-border border-y px-3 py-1.5 text-xs\">\n                        ID\n                      </TableHead>\n                      <TableHead className=\"flex h-7 items-center border-border border-y px-3 py-1.5 text-xs\">\n                        Name\n                      </TableHead>\n                      <TableHead className=\"flex h-7 items-center border-border border-y px-3 py-1.5 text-xs\">\n                        Category\n                      </TableHead>\n                      <TableHead className=\"flex h-7 items-center justify-end border-border border-y px-3 py-1.5 text-right text-xs\">\n                        Value\n                      </TableHead>\n                      <TableHead className=\"flex h-7 items-center border-border border-y px-3 py-1.5 text-xs\">\n                        Date\n                      </TableHead>\n                    </TableRow>\n                  </TableHeader>\n                  <TableBody>\n                    {row.children?.map((childRow) => (\n                      <TableRow\n                        className=\"grid grid-cols-[40px_80px_180px_110px_100px_110px]\"\n                        key={childRow.id}\n                      >\n                        <TableCell className=\"px-3 py-2\" />\n                        <TableCell className=\"px-3 py-2 font-mono text-muted-foreground text-xs tabular-nums\">\n                          {childRow.id}\n                        </TableCell>\n                        <TableCell className=\"px-3 py-2 font-medium text-xs\">\n                          {childRow.name}\n                        </TableCell>\n                        <TableCell className=\"px-3 py-2 text-muted-foreground text-xs\">\n                          {childRow.category}\n                        </TableCell>\n                        <TableCell className=\"px-3 py-2 text-right font-mono font-semibold text-xs tabular-nums\">\n                          ${childRow.value.toLocaleString()}\n                        </TableCell>\n                        <TableCell className=\"px-3 py-2 text-muted-foreground text-xs\">\n                          {childRow.date}\n                        </TableCell>\n                      </TableRow>\n                    ))}\n                  </TableBody>\n                </Table>\n              </div>\n            </CollapsibleContent>\n          </TableCell>\n        </TableRow>\n      )}\n    </Collapsible>\n  );\n}\n\nexport default function Table01() {\n  return (\n    <div className=\"max-w-fit overflow-hidden rounded-lg border border-border bg-card shadow-sm\">\n      <div className=\"overflow-x-auto\">\n        <Table>\n          <TableHeader>\n            <TableRow className=\"grid grid-cols-[40px_80px_180px_110px_100px_110px] bg-muted/50\">\n              <TableHead className=\"p-3\" />\n              <TableHead className=\"p-3 font-semibold text-foreground text-sm\">\n                ID\n              </TableHead>\n              <TableHead className=\"p-3 font-semibold text-foreground text-sm\">\n                Name\n              </TableHead>\n              <TableHead className=\"p-3 font-semibold text-foreground text-sm\">\n                Category\n              </TableHead>\n              <TableHead className=\"p-3 text-right font-semibold text-foreground text-sm\">\n                Value\n              </TableHead>\n              <TableHead className=\"p-3 font-semibold text-foreground text-sm\">\n                Date\n              </TableHead>\n            </TableRow>\n          </TableHeader>\n          {sampleData.map((row, index) => (\n            <AccordionRow defaultOpen={index === 0} key={row.id} row={row} />\n          ))}\n        </Table>\n      </div>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "tables",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/table-01/table-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/tables/table-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "table",
          "data",
          "filter",
          "rows",
          "表格",
          "数据",
          "筛选",
          "列表"
        ],
        "contentGuidelines": [
          "Use stable row identifiers and label row actions.",
          "Test long cells, no results, and horizontal overflow on mobile."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/table-01/table-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/table-01"
      }
    },
    {
      "name": "table-02",
      "type": "registry:block",
      "title": "Table with Actions",
      "description": "Table with Actions. Product data tables with row actions, grouping, filters, and useful data density.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/table-02/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/table-02/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-02/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-02/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/table.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-02/shared/components/ui/table.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div\n      className=\"relative w-full overflow-x-auto\"\n      data-slot=\"table-container\"\n    >\n      <table\n        className={cn('w-full caption-bottom text-sm', className)}\n        data-slot=\"table\"\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return (\n    <thead\n      className={cn('[&_tr]:border-b', className)}\n      data-slot=\"table-header\"\n      {...props}\n    />\n  );\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      className={cn('[&_tr:last-child]:border-0', className)}\n      data-slot=\"table-body\"\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      className={cn(\n        'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',\n        className\n      )}\n      data-slot=\"table-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      className={cn(\n        'border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',\n        className\n      )}\n      data-slot=\"table-row\"\n      {...props}\n    />\n  );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      className={cn(\n        'h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-head\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      className={cn(\n        'whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-cell\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      className={cn('mt-4 text-muted-foreground text-sm', className)}\n      data-slot=\"table-caption\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n};\n"
        },
        {
          "path": "components/ui/tooltip.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-02/shared/components/ui/tooltip.tsx",
          "content": "'use client';\n\nimport { Tooltip as TooltipPrimitive } from '@base-ui/react/tooltip';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction TooltipProvider({\n  delay = 0,\n  ...props\n}: TooltipPrimitive.Provider.Props) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delay={delay}\n      {...props}\n    />\n  );\n}\n\nfunction Tooltip({ ...props }: TooltipPrimitive.Root.Props) {\n  return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = 'top',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<\n    TooltipPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <TooltipPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-background text-xs has-data-[slot=kbd]:pr-1.5 data-[state=delayed-open]:animate-in data-closed:animate-out data-open:animate-in **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm',\n            className\n          )}\n          data-slot=\"tooltip-content\"\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5 z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-start]:top-1/2! data-[side=left]:top-1/2! data-[side=right]:top-1/2!\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/table-02/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/tables/table-02.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-02/table-02.tsx",
          "content": "'use client';\n\nimport {\n  CheckCircle,\n  FileTextIcon,\n  Loader2,\n  PauseIcon,\n  PlayIcon,\n  Trash2Icon,\n} from 'lucide-react';\nimport { useState } from 'react';\n\nimport { Badge } from \"./shared/components/ui/badge\";\nimport { Button } from \"./shared/components/ui/button\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"./shared/components/ui/table\";\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"./shared/components/ui/tooltip\";\n\ninterface Task {\n  id: string;\n  title: string;\n  assignee: string;\n  status: 'pending' | 'in-progress' | 'completed' | 'blocked';\n  priority: 'low' | 'medium' | 'high' | 'urgent';\n  dueDate: string;\n  notes: string;\n}\n\ntype TaskActionType = 'start' | 'pause' | 'complete' | 'delete' | 'view';\n\nconst tasks: Task[] = [\n  {\n    id: 'TASK-001',\n    title: 'Implement User Authentication',\n    assignee: 'Sarah Chen',\n    status: 'in-progress',\n    priority: 'high',\n    dueDate: '2024-03-25',\n    notes: 'OAuth 2.0 integration with Google and GitHub providers',\n  },\n  {\n    id: 'TASK-002',\n    title: 'Design Dashboard UI',\n    assignee: 'Michael Torres',\n    status: 'completed',\n    priority: 'medium',\n    dueDate: '2024-03-20',\n    notes: 'Finalize dashboard layout with responsive grid system',\n  },\n  {\n    id: 'TASK-003',\n    title: 'API Performance Optimization',\n    assignee: 'Emma Rodriguez',\n    status: 'pending',\n    priority: 'urgent',\n    dueDate: '2024-03-22',\n    notes: 'Reduce API response time by implementing caching strategy',\n  },\n  {\n    id: 'TASK-004',\n    title: 'Write Unit Tests',\n    assignee: 'James Wilson',\n    status: 'in-progress',\n    priority: 'medium',\n    dueDate: '2024-03-28',\n    notes: 'Achieve 80% code coverage for authentication module',\n  },\n  {\n    id: 'TASK-005',\n    title: 'Database Migration',\n    assignee: 'Olivia Martinez',\n    status: 'blocked',\n    priority: 'high',\n    dueDate: '2024-03-24',\n    notes: 'Waiting for infrastructure team approval before proceeding',\n  },\n  {\n    id: 'TASK-006',\n    title: 'Update Documentation',\n    assignee: 'Lucas Anderson',\n    status: 'pending',\n    priority: 'low',\n    dueDate: '2024-03-30',\n    notes: 'Document new API endpoints and authentication flow',\n  },\n  {\n    id: 'TASK-007',\n    title: 'Security Audit',\n    assignee: 'Sophia Taylor',\n    status: 'completed',\n    priority: 'urgent',\n    dueDate: '2024-03-19',\n    notes:\n      'Conducted comprehensive security review and vulnerability assessment',\n  },\n];\n\nfunction getStatusBadge(status: Task['status']) {\n  switch (status) {\n    case 'pending':\n      return (\n        <Badge\n          className=\"border-0 bg-amber-500/15 text-amber-700 hover:bg-amber-500/25 dark:bg-amber-500/10 dark:text-amber-300 dark:hover:bg-amber-500/20\"\n          variant=\"outline\"\n        >\n          Pending\n        </Badge>\n      );\n    case 'in-progress':\n      return (\n        <Badge\n          className=\"border-0 bg-blue-500/15 text-blue-700 hover:bg-blue-500/25 dark:bg-blue-500/10 dark:text-blue-400 dark:hover:bg-blue-500/20\"\n          variant=\"outline\"\n        >\n          In Progress\n        </Badge>\n      );\n    case 'completed':\n      return (\n        <Badge\n          className=\"border-0 bg-green-500/15 text-green-700 hover:bg-green-500/25 dark:bg-green-500/10 dark:text-green-400 dark:hover:bg-green-500/20\"\n          variant=\"outline\"\n        >\n          Completed\n        </Badge>\n      );\n    case 'blocked':\n      return (\n        <Badge\n          className=\"border-0 bg-rose-500/15 text-rose-700 hover:bg-rose-500/25 dark:bg-rose-500/10 dark:text-rose-400 dark:hover:bg-rose-500/20\"\n          variant=\"outline\"\n        >\n          Blocked\n        </Badge>\n      );\n    default:\n      return <Badge variant=\"secondary\">{status}</Badge>;\n  }\n}\n\nexport default function Table02() {\n  const [pendingAction, setPendingAction] = useState<{\n    id: string;\n    type: TaskActionType;\n  } | null>(null);\n\n  const isTaskActionPending = (action: TaskActionType, taskId: string) =>\n    pendingAction?.id === taskId && pendingAction.type === action;\n\n  const isTaskBusy = (taskId: string) => pendingAction?.id === taskId;\n\n  const handleAction = (task: Task, actionType: TaskActionType) => {\n    setPendingAction({ id: task.id, type: actionType });\n    setTimeout(() => {\n      setPendingAction(null);\n      console.log(`Action \"${actionType}\" completed for task:`, task.title);\n    }, 1000);\n  };\n\n  const renderTaskRow = (task: Task) => {\n    const busy = isTaskBusy(task.id);\n    const startPending = isTaskActionPending('start', task.id);\n    const pausePending = isTaskActionPending('pause', task.id);\n    const completePending = isTaskActionPending('complete', task.id);\n    const deletePending = isTaskActionPending('delete', task.id);\n\n    return (\n      <TableRow className=\"hover:bg-muted/50\" key={task.id}>\n        <TableCell className=\"h-16 px-4 font-medium\">{task.title}</TableCell>\n        <TableCell className=\"h-16 px-4 text-muted-foreground text-sm\">\n          {task.assignee}\n        </TableCell>\n        <TableCell className=\"h-16 px-4\">\n          {getStatusBadge(task.status)}\n        </TableCell>\n\n        <TableCell className=\"h-16 px-4 text-muted-foreground text-sm\">\n          {task.dueDate}\n        </TableCell>\n        <TableCell className=\"h-16 max-w-[300px] px-4 text-muted-foreground text-sm\">\n          <TooltipProvider>\n            <Tooltip>\n              <TooltipTrigger\n                render={<span className=\"block cursor-help truncate\" />}\n              >\n                {task.notes}\n              </TooltipTrigger>\n              <TooltipContent className=\"max-w-md\">{task.notes}</TooltipContent>\n            </Tooltip>\n          </TooltipProvider>\n        </TableCell>\n        <TableCell className=\"h-16 px-4\">\n          <TooltipProvider>\n            <div className=\"flex items-center gap-1\">\n              {(task.status === 'pending' || task.status === 'blocked') && (\n                <Tooltip>\n                  <TooltipTrigger\n                    render={\n                      <Button\n                        aria-label=\"Start\"\n                        className=\"h-8 w-8\"\n                        disabled={busy}\n                        onClick={() => handleAction(task, 'start')}\n                        size=\"icon\"\n                        variant=\"outline\"\n                      />\n                    }\n                  >\n                    {startPending ? (\n                      <Loader2 className=\"size-4 animate-spin\" />\n                    ) : (\n                      <PlayIcon className=\"size-4\" />\n                    )}\n                  </TooltipTrigger>\n                  <TooltipContent>Start</TooltipContent>\n                </Tooltip>\n              )}\n              {task.status === 'in-progress' && (\n                <>\n                  <Tooltip>\n                    <TooltipTrigger\n                      render={\n                        <Button\n                          aria-label=\"Pause\"\n                          className=\"h-8 w-8\"\n                          disabled={busy}\n                          onClick={() => handleAction(task, 'pause')}\n                          size=\"icon\"\n                          variant=\"outline\"\n                        />\n                      }\n                    >\n                      {pausePending ? (\n                        <Loader2 className=\"size-4 animate-spin\" />\n                      ) : (\n                        <PauseIcon className=\"size-4\" />\n                      )}\n                    </TooltipTrigger>\n                    <TooltipContent>Pause</TooltipContent>\n                  </Tooltip>\n                  <Tooltip>\n                    <TooltipTrigger\n                      render={\n                        <Button\n                          aria-label=\"Complete\"\n                          className=\"h-8 w-8\"\n                          disabled={busy}\n                          onClick={() => handleAction(task, 'complete')}\n                          size=\"icon\"\n                          variant=\"outline\"\n                        />\n                      }\n                    >\n                      {completePending ? (\n                        <Loader2 className=\"size-4 animate-spin\" />\n                      ) : (\n                        <CheckCircle className=\"size-4\" />\n                      )}\n                    </TooltipTrigger>\n                    <TooltipContent>Complete</TooltipContent>\n                  </Tooltip>\n                </>\n              )}\n              <Tooltip>\n                <TooltipTrigger\n                  render={\n                    <Button\n                      aria-label=\"Delete\"\n                      className=\"h-8 w-8 text-destructive hover:bg-destructive hover:text-white\"\n                      disabled={busy}\n                      onClick={() => handleAction(task, 'delete')}\n                      size=\"icon\"\n                      variant=\"outline\"\n                    />\n                  }\n                >\n                  {deletePending ? (\n                    <Loader2 className=\"size-4 animate-spin\" />\n                  ) : (\n                    <Trash2Icon className=\"size-4\" />\n                  )}\n                </TooltipTrigger>\n                <TooltipContent>Delete</TooltipContent>\n              </Tooltip>\n              <Tooltip>\n                <TooltipTrigger\n                  render={\n                    <Button\n                      aria-label=\"View details\"\n                      className=\"h-8 w-8\"\n                      disabled={busy}\n                      onClick={() => handleAction(task, 'view')}\n                      size=\"icon\"\n                      variant=\"outline\"\n                    />\n                  }\n                >\n                  <FileTextIcon className=\"size-4\" />\n                </TooltipTrigger>\n                <TooltipContent>View Details</TooltipContent>\n              </Tooltip>\n            </div>\n          </TooltipProvider>\n        </TableCell>\n      </TableRow>\n    );\n  };\n\n  return (\n    <div className=\"w-[95%] rounded-lg border bg-card\">\n      <Table>\n        <TableHeader>\n          <TableRow className=\"border-b hover:bg-transparent\">\n            <TableHead className=\"h-12 px-4 font-medium\">Title</TableHead>\n            <TableHead className=\"h-12 px-4 font-medium\">Assignee</TableHead>\n            <TableHead className=\"h-12 w-[120px] px-4 font-medium\">\n              Status\n            </TableHead>\n\n            <TableHead className=\"h-12 px-4 font-medium\">Due Date</TableHead>\n            <TableHead className=\"h-12 px-4 font-medium\">Notes</TableHead>\n            <TableHead className=\"h-12 w-[180px] px-4 font-medium\">\n              Actions\n            </TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>{tasks.map(renderTaskRow)}</TableBody>\n      </Table>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "tables",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/table-02/table-02.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/tables/table-02 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "table",
          "data",
          "filter",
          "rows",
          "表格",
          "数据",
          "筛选",
          "列表"
        ],
        "contentGuidelines": [
          "Use stable row identifiers and label row actions.",
          "Test long cells, no results, and horizontal overflow on mobile."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/table-02/table-02.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/table-02"
      }
    },
    {
      "name": "table-03",
      "type": "registry:block",
      "title": "Table with Filters",
      "description": "Table with Filters. Product data tables with row actions, grouping, filters, and useful data density.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/table-03/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/table-03/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-03/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-03/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/table.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-03/shared/components/ui/table.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div\n      className=\"relative w-full overflow-x-auto\"\n      data-slot=\"table-container\"\n    >\n      <table\n        className={cn('w-full caption-bottom text-sm', className)}\n        data-slot=\"table\"\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return (\n    <thead\n      className={cn('[&_tr]:border-b', className)}\n      data-slot=\"table-header\"\n      {...props}\n    />\n  );\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      className={cn('[&_tr:last-child]:border-0', className)}\n      data-slot=\"table-body\"\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      className={cn(\n        'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',\n        className\n      )}\n      data-slot=\"table-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      className={cn(\n        'border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',\n        className\n      )}\n      data-slot=\"table-row\"\n      {...props}\n    />\n  );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      className={cn(\n        'h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-head\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      className={cn(\n        'whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-cell\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      className={cn('mt-4 text-muted-foreground text-sm', className)}\n      data-slot=\"table-caption\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/table-03/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/tables/table-03.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-03/table-03.tsx",
          "content": "'use client';\n\nimport { useState } from 'react';\nimport { Badge } from \"./shared/components/ui/badge\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"./shared/components/ui/table\";\nimport { cn } from \"./shared/lib/utils\";\n\ntype Status = 'active' | 'pending' | 'discontinued' | 'on-hold';\n\ninterface Product {\n  sku: string;\n  productName: string;\n  stockLevel: number;\n  category: string;\n  status: Status;\n  unitPrice: string;\n  lastRestocked: string;\n}\n\nconst data: Product[] = [\n  {\n    sku: 'SKU-8472',\n    productName: 'Wireless Mouse Pro',\n    stockLevel: 245,\n    category: 'Electronics',\n    status: 'active',\n    unitPrice: '$24.99',\n    lastRestocked: 'Oct 15, 2024',\n  },\n  {\n    sku: 'SKU-3391',\n    productName: 'Ergonomic Keyboard',\n    stockLevel: 89,\n    category: 'Electronics',\n    status: 'active',\n    unitPrice: '$79.99',\n    lastRestocked: 'Oct 18, 2024',\n  },\n  {\n    sku: 'SKU-7156',\n    productName: 'Office Chair Deluxe',\n    stockLevel: 12,\n    category: 'Furniture',\n    status: 'pending',\n    unitPrice: '$299.99',\n    lastRestocked: 'Oct 12, 2024',\n  },\n  {\n    sku: 'SKU-9204',\n    productName: 'USB-C Hub Adapter',\n    stockLevel: 456,\n    category: 'Accessories',\n    status: 'active',\n    unitPrice: '$34.50',\n    lastRestocked: 'Oct 19, 2024',\n  },\n  {\n    sku: 'SKU-1638',\n    productName: 'Standing Desk Frame',\n    stockLevel: 5,\n    category: 'Furniture',\n    status: 'on-hold',\n    unitPrice: '$449.00',\n    lastRestocked: 'Oct 10, 2024',\n  },\n  {\n    sku: 'SKU-5529',\n    productName: 'Laptop Stand Aluminum',\n    stockLevel: 178,\n    category: 'Accessories',\n    status: 'active',\n    unitPrice: '$45.99',\n    lastRestocked: 'Oct 17, 2024',\n  },\n  {\n    sku: 'SKU-4817',\n    productName: 'Mechanical Keyboard RGB',\n    stockLevel: 0,\n    category: 'Electronics',\n    status: 'discontinued',\n    unitPrice: '$129.99',\n    lastRestocked: 'Oct 05, 2024',\n  },\n];\n\nfunction getStatusBadge(status: Status) {\n  switch (status) {\n    case 'active':\n      return (\n        <Badge\n          className=\"border-0 bg-green-500/15 text-green-700 hover:bg-green-500/25 dark:bg-green-500/10 dark:text-green-400 dark:hover:bg-green-500/20\"\n          variant=\"outline\"\n        >\n          Active\n        </Badge>\n      );\n    case 'pending':\n      return (\n        <Badge\n          className=\"border-0 bg-amber-500/15 text-amber-700 hover:bg-amber-500/25 dark:bg-amber-500/10 dark:text-amber-300 dark:hover:bg-amber-500/20\"\n          variant=\"outline\"\n        >\n          Pending\n        </Badge>\n      );\n    case 'discontinued':\n      return (\n        <Badge\n          className=\"border-0 bg-rose-500/15 text-rose-700 hover:bg-rose-500/25 dark:bg-rose-500/10 dark:text-rose-400 dark:hover:bg-rose-500/20\"\n          variant=\"outline\"\n        >\n          Discontinued\n        </Badge>\n      );\n    case 'on-hold':\n      return (\n        <Badge\n          className=\"border-0 bg-blue-500/15 text-blue-700 hover:bg-blue-500/25 dark:bg-blue-500/10 dark:text-blue-400 dark:hover:bg-blue-500/20\"\n          variant=\"outline\"\n        >\n          On Hold\n        </Badge>\n      );\n    default:\n      return null;\n  }\n}\n\nexport default function Table03() {\n  const [selectedCategory, setSelectedCategory] = useState<string>('all');\n\n  const filteredData = data.filter((item) => {\n    const categoryMatch =\n      selectedCategory === 'all' || item.category === selectedCategory;\n    return categoryMatch;\n  });\n\n  const uniqueCategories = Array.from(\n    new Set(data.map((item) => item.category))\n  );\n\n  return (\n    <div className=\"space-y-6\">\n      <div className=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n        <div>\n          <h3 className=\"text-balance font-semibold text-foreground text-lg\">\n            Product Inventory\n          </h3>\n          <p className=\"mt-1 text-pretty text-muted-foreground text-sm\">\n            Track and manage product stock levels across all warehouse\n            locations.\n          </p>\n        </div>\n        <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n          <Select\n            items={[\n              { value: 'all', label: 'All Categories' },\n              ...uniqueCategories.map((c) => ({ value: c, label: c })),\n            ]}\n            onValueChange={(value) => {\n              if (value) {\n                setSelectedCategory(value);\n              }\n            }}\n            value={selectedCategory}\n          >\n            <SelectTrigger className=\"w-full sm:w-[180px]\">\n              <SelectValue placeholder=\"Filter by category\" />\n            </SelectTrigger>\n            <SelectContent>\n              <SelectItem value=\"all\">All Categories</SelectItem>\n              {uniqueCategories.map((category) => (\n                <SelectItem key={category} value={category}>\n                  {category}\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n        </div>\n      </div>\n\n      <div className=\"rounded-lg border bg-card\">\n        <Table>\n          <TableHeader>\n            <TableRow className=\"border-b hover:bg-transparent\">\n              <TableHead className=\"h-12 px-4 font-medium\">SKU</TableHead>\n              <TableHead className=\"h-12 px-4 font-medium\">\n                Product Name\n              </TableHead>\n              <TableHead className=\"h-12 px-4 font-medium\">Category</TableHead>\n              <TableHead className=\"h-12 px-4 font-medium\">Status</TableHead>\n              <TableHead className=\"h-12 px-4 text-right font-medium\">\n                Unit Price\n              </TableHead>\n              <TableHead className=\"h-12 px-4 text-right font-medium\">\n                Last Restocked\n              </TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {filteredData.length > 0 ? (\n              filteredData.map((item) => (\n                <TableRow className=\"hover:bg-muted/50\" key={item.sku}>\n                  <TableCell className=\"h-14 px-4 font-medium font-mono text-sm tabular-nums\">\n                    {item.sku}\n                  </TableCell>\n                  <TableCell className=\"h-14 px-4 font-medium\">\n                    {item.productName}\n                  </TableCell>\n                  <TableCell className=\"h-14 px-4 text-muted-foreground text-sm\">\n                    {item.category}\n                  </TableCell>\n                  <TableCell className=\"h-14 px-4\">\n                    {getStatusBadge(item.status)}\n                  </TableCell>\n                  <TableCell className=\"h-14 px-4 text-right font-mono font-semibold text-sm tabular-nums\">\n                    {item.unitPrice}\n                  </TableCell>\n                  <TableCell className=\"h-14 px-4 text-right text-muted-foreground text-sm\">\n                    {item.lastRestocked}\n                  </TableCell>\n                </TableRow>\n              ))\n            ) : (\n              <TableRow>\n                <TableCell\n                  className=\"h-24 text-center text-muted-foreground\"\n                  colSpan={6}\n                >\n                  No products found matching the selected filters.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </div>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "tables",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/table-03/table-03.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/tables/table-03 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "table",
          "data",
          "filter",
          "rows",
          "表格",
          "数据",
          "筛选",
          "列表"
        ],
        "contentGuidelines": [
          "Use stable row identifiers and label row actions.",
          "Test long cells, no results, and horizontal overflow on mobile."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/table-03/table-03.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/table-03"
      }
    },
    {
      "name": "table-04",
      "type": "registry:block",
      "title": "Table with Grouped Rows",
      "description": "Table with Grouped Rows. Product data tables with row actions, grouping, filters, and useful data density.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "class-variance-authority@0.7.1",
        "cn@0.2.6"
      ],
      "files": [
        {
          "path": "licenses/table-04/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/table-04/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/avatar.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-04/shared/components/ui/avatar.tsx",
          "content": "'use client';\n\nimport { Avatar as AvatarPrimitive } from '@base-ui/react/avatar';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Avatar({\n  className,\n  size = 'default',\n  ...props\n}: AvatarPrimitive.Root.Props & {\n  size?: 'default' | 'sm' | 'lg';\n}) {\n  return (\n    <AvatarPrimitive.Root\n      className={cn(\n        'group/avatar relative flex size-8 shrink-0 select-none rounded-full after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',\n        className\n      )}\n      data-size={size}\n      data-slot=\"avatar\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n  return (\n    <AvatarPrimitive.Image\n      className={cn(\n        'aspect-square size-full rounded-full object-cover',\n        className\n      )}\n      data-slot=\"avatar-image\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarFallback({\n  className,\n  ...props\n}: AvatarPrimitive.Fallback.Props) {\n  return (\n    <AvatarPrimitive.Fallback\n      className={cn(\n        'flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground text-sm group-data-[size=sm]/avatar:text-xs',\n        className\n      )}\n      data-slot=\"avatar-fallback\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'absolute right-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background',\n        'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden',\n        'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',\n        'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',\n        className\n      )}\n      data-slot=\"avatar-badge\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'group/avatar-group -space-x-2 flex *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background',\n        className\n      )}\n      data-slot=\"avatar-group\"\n      {...props}\n    />\n  );\n}\n\nfunction AvatarGroupCount({\n  className,\n  ...props\n}: React.ComponentProps<'div'>) {\n  return (\n    <div\n      className={cn(\n        'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',\n        className\n      )}\n      data-slot=\"avatar-group-count\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Avatar,\n  AvatarImage,\n  AvatarFallback,\n  AvatarGroup,\n  AvatarGroupCount,\n  AvatarBadge,\n};\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-04/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/table.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-04/shared/components/ui/table.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div\n      className=\"relative w-full overflow-x-auto\"\n      data-slot=\"table-container\"\n    >\n      <table\n        className={cn('w-full caption-bottom text-sm', className)}\n        data-slot=\"table\"\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return (\n    <thead\n      className={cn('[&_tr]:border-b', className)}\n      data-slot=\"table-header\"\n      {...props}\n    />\n  );\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      className={cn('[&_tr:last-child]:border-0', className)}\n      data-slot=\"table-body\"\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      className={cn(\n        'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',\n        className\n      )}\n      data-slot=\"table-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      className={cn(\n        'border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',\n        className\n      )}\n      data-slot=\"table-row\"\n      {...props}\n    />\n  );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      className={cn(\n        'h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-head\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      className={cn(\n        'whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-cell\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      className={cn('mt-4 text-muted-foreground text-sm', className)}\n      data-slot=\"table-caption\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/table-04/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/tables/table-04.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-04/table-04.tsx",
          "content": "'use client';\n\nimport { Fragment } from 'react';\nimport { Avatar, AvatarFallback } from \"./shared/components/ui/avatar\";\nimport { Badge } from \"./shared/components/ui/badge\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"./shared/components/ui/table\";\nimport { cn } from \"./shared/lib/utils\";\n\ninterface AssignedPerson {\n  name: string;\n  initials: string;\n}\n\ninterface TaskItem {\n  id: string;\n  task: string;\n  budget: string;\n  deadline: string;\n  assigned: AssignedPerson[];\n  status: string;\n}\n\ninterface TaskGroup {\n  name: string;\n  items: TaskItem[];\n}\n\ntype StatusVariant = 'success' | 'warning' | 'default' | 'secondary';\n\nconst avatarColors = [\n  'bg-blue-500',\n  'bg-purple-500',\n  'bg-emerald-500',\n  'bg-cyan-500',\n  'bg-rose-500',\n  'bg-indigo-500',\n];\n\nfunction getAvatarColor(initials: string) {\n  const seed = initials\n    .split('')\n    .reduce((acc, char) => acc + char.charCodeAt(0), 0);\n  return avatarColors[seed % avatarColors.length];\n}\n\nfunction AvatarStack({ people }: { people: AssignedPerson[] }) {\n  return (\n    <div className=\"-space-x-2 flex\">\n      {people.map((person, index) => (\n        <Avatar\n          className={cn(\n            'h-6 w-6 border-2 border-background text-[10px]',\n            getAvatarColor(person.initials)\n          )}\n          key={index}\n        >\n          <AvatarFallback\n            className={cn(\n              'font-medium text-white',\n              getAvatarColor(person.initials)\n            )}\n          >\n            {person.initials}\n          </AvatarFallback>\n        </Avatar>\n      ))}\n    </div>\n  );\n}\n\nconst statusStyles: Record<StatusVariant, { badge: string; dot: string }> = {\n  success: {\n    badge:\n      'bg-emerald-50 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300',\n    dot: 'bg-emerald-500',\n  },\n  warning: {\n    badge: 'bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300',\n    dot: 'bg-amber-500',\n  },\n  default: {\n    badge: 'bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300',\n    dot: 'bg-blue-500',\n  },\n  secondary: {\n    badge: 'bg-muted text-muted-foreground',\n    dot: 'bg-muted-foreground',\n  },\n};\n\nfunction StatusBadge({\n  status,\n  variant = 'default',\n}: {\n  status: string;\n  variant?: StatusVariant;\n}) {\n  const styles = statusStyles[variant];\n  return (\n    <Badge\n      className={cn('gap-1.5 rounded-full', styles.badge)}\n      variant=\"outline\"\n    >\n      <span\n        aria-hidden=\"true\"\n        className={cn('size-1.5 rounded-full', styles.dot)}\n      />\n      {status}\n    </Badge>\n  );\n}\n\nconst data: TaskGroup[] = [\n  {\n    name: 'Engineering',\n    items: [\n      {\n        id: '1',\n        task: 'API Integration Overhaul',\n        budget: '$32,000',\n        deadline: 'Dec 15, 2024',\n        assigned: [\n          { name: 'Marcus Chen', initials: 'MC' },\n          { name: 'Priya Sharma', initials: 'PS' },\n        ],\n        status: 'In Progress',\n      },\n      {\n        id: '2',\n        task: 'Database Migration',\n        budget: '$18,500',\n        deadline: 'Jan 20, 2025',\n        assigned: [{ name: 'David Kim', initials: 'DK' }],\n        status: 'Completed',\n      },\n      {\n        id: '3',\n        task: 'Mobile App Redesign',\n        budget: '$55,000',\n        deadline: 'Feb 28, 2025',\n        assigned: [\n          { name: 'Lena Rodriguez', initials: 'LR' },\n          { name: 'Tom Anderson', initials: 'TA' },\n          { name: 'Nina Patel', initials: 'NP' },\n        ],\n        status: 'Planning',\n      },\n    ],\n  },\n  {\n    name: 'Marketing',\n    items: [\n      {\n        id: '4',\n        task: 'Q1 Campaign Launch',\n        budget: '$24,000',\n        deadline: 'Jan 5, 2025',\n        assigned: [\n          { name: 'Rachel Green', initials: 'RG' },\n          { name: 'Chris Evans', initials: 'CE' },\n        ],\n        status: 'In Progress',\n      },\n      {\n        id: '5',\n        task: 'Brand Refresh Project',\n        budget: '$42,000',\n        deadline: 'Mar 1, 2025',\n        assigned: [{ name: 'Monica Geller', initials: 'MG' }],\n        status: 'Planning',\n      },\n    ],\n  },\n  {\n    name: 'Operations',\n    items: [\n      {\n        id: '6',\n        task: 'Vendor Contract Review',\n        budget: '$8,000',\n        deadline: 'Dec 30, 2024',\n        assigned: [\n          { name: 'Joe Tribbiani', initials: 'JT' },\n          { name: 'Phoebe Buffay', initials: 'PB' },\n        ],\n        status: 'Completed',\n      },\n      {\n        id: '7',\n        task: 'Office Expansion Setup',\n        budget: '$120,000',\n        deadline: 'Apr 15, 2025',\n        assigned: [{ name: 'Ross Geller', initials: 'RG' }],\n        status: 'On Hold',\n      },\n    ],\n  },\n];\n\nfunction getStatusVariant(status: string): StatusVariant {\n  switch (status) {\n    case 'Completed':\n      return 'success';\n    case 'In Progress':\n      return 'default';\n    case 'Planning':\n      return 'secondary';\n    case 'On Hold':\n      return 'warning';\n    default:\n      return 'default';\n  }\n}\n\nexport default function Table04() {\n  return (\n    <div className=\"rounded-lg border\">\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead className=\"w-48 font-medium\">Task</TableHead>\n            <TableHead className=\"font-medium\">Budget</TableHead>\n            <TableHead className=\"font-medium\">Deadline</TableHead>\n            <TableHead className=\"font-medium\">Assigned</TableHead>\n            <TableHead className=\"w-28 font-medium\">Status</TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {data.map((group) => (\n            <Fragment key={group.name}>\n              <TableRow className=\"bg-muted/50 hover:bg-muted/50\">\n                <TableCell className=\"py-2 font-semibold\" colSpan={5}>\n                  {group.name}\n                  <span className=\"ml-2 font-normal text-muted-foreground\">\n                    {group.items.length}\n                  </span>\n                </TableCell>\n              </TableRow>\n              {group.items.map((item) => (\n                <TableRow key={item.id}>\n                  <TableCell className=\"font-medium\">{item.task}</TableCell>\n                  <TableCell>{item.budget}</TableCell>\n                  <TableCell>{item.deadline}</TableCell>\n                  <TableCell>\n                    <AvatarStack people={item.assigned} />\n                  </TableCell>\n                  <TableCell>\n                    <StatusBadge\n                      status={item.status}\n                      variant={getStatusVariant(item.status)}\n                    />\n                  </TableCell>\n                </TableRow>\n              ))}\n            </Fragment>\n          ))}\n        </TableBody>\n      </Table>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "tables",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/table-04/table-04.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/tables/table-04 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "table",
          "data",
          "filter",
          "rows",
          "表格",
          "数据",
          "筛选",
          "列表"
        ],
        "contentGuidelines": [
          "Use stable row identifiers and label row actions.",
          "Test long cells, no results, and horizontal overflow on mobile."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/table-04/table-04.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/table-04"
      }
    },
    {
      "name": "table-05",
      "type": "registry:block",
      "title": "Data Table",
      "description": "Data Table. Product data tables with row actions, grouping, filters, and useful data density.",
      "author": "Ephraim Duncan",
      "registryDependencies": [],
      "dependencies": [
        "@base-ui/react@1.9.0",
        "@tanstack/react-table@8.21.3",
        "class-variance-authority@0.7.1",
        "cn@0.2.6",
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/table-05/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/table-05/LICENSE.txt",
          "content": "Includes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "components/ui/badge.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/shared/components/ui/badge.tsx",
          "content": "import { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst badgeVariants = cva(\n  'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-4xl border border-transparent px-2 py-0.5 font-medium text-xs transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',\n        secondary:\n          'bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80',\n        destructive:\n          'bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20',\n        outline:\n          'border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground',\n        ghost:\n          'hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n    },\n  }\n);\n\nfunction Badge({\n  className,\n  variant = 'default',\n  render,\n  ...props\n}: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {\n  return useRender({\n    defaultTagName: 'span',\n    props: mergeProps<'span'>(\n      {\n        className: cn(badgeVariants({ variant }), className),\n      },\n      props\n    ),\n    render,\n    state: {\n      slot: 'badge',\n      variant,\n    },\n  });\n}\n\nexport { Badge, badgeVariants };\n"
        },
        {
          "path": "components/ui/button.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/shared/components/ui/button.tsx",
          "content": "import { Button as ButtonPrimitive } from '@base-ui/react/button';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from \"../../lib/utils\";\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-lg border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/80',\n        outline:\n          'border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',\n        secondary:\n          'bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',\n        ghost:\n          'hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50',\n        destructive:\n          'bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30',\n        link: 'text-primary underline-offset-4 hover:underline',\n      },\n      size: {\n        default:\n          'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        xs: \"h-6 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',\n        icon: 'size-8',\n        'icon-xs':\n          \"size-6 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),10px)] [&_svg:not([class*='size-'])]:size-3\",\n        'icon-sm':\n          'size-7 in-data-[slot=button-group]:rounded-lg rounded-[min(var(--radius-md),12px)]',\n        'icon-lg': 'size-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  }\n);\n\nfunction Button({\n  className,\n  variant = 'default',\n  size = 'default',\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      className={cn(buttonVariants({ variant, size, className }))}\n      data-slot=\"button\"\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
        },
        {
          "path": "components/ui/checkbox.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/shared/components/ui/checkbox.tsx",
          "content": "'use client';\n\nimport { Checkbox as CheckboxPrimitive } from '@base-ui/react/checkbox';\nimport { CheckIcon } from 'lucide-react';\nimport { cn } from \"../../lib/utils\";\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      className={cn(\n        'peer after:-inset-x-3 after:-inset-y-2 relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input outline-none transition-colors after:absolute focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 group-has-[:focus-visible]/field-label:not-data-checked:border-input group-has-disabled/field:opacity-50 group-has-[:focus-visible]/field-label:ring-0 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground group-has-[:focus-visible]/field-label:data-checked:border-primary dark:bg-input/30 dark:data-checked:bg-primary dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',\n        className\n      )}\n      data-slot=\"checkbox\"\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n        data-slot=\"checkbox-indicator\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n\nexport { Checkbox };\n"
        },
        {
          "path": "components/ui/dropdown-menu.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/shared/components/ui/dropdown-menu.tsx",
          "content": "'use client';\n\nimport { Menu as MenuPrimitive } from '@base-ui/react/menu';\nimport { CheckIcon, ChevronRightIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = 'start',\n  alignOffset = 0,\n  side = 'bottom',\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset'\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        className=\"isolate z-50 outline-none\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md outline-none ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in data-closed:overflow-hidden',\n            className\n          )}\n          data-slot=\"dropdown-menu-content\"\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      className={cn(\n        'px-1.5 py-1 font-medium text-muted-foreground text-xs data-inset:pl-7',\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-label\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = 'default',\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: 'default' | 'destructive';\n}) {\n  return (\n    <MenuPrimitive.Item\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-item\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      className={cn(\n        \"flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-popup-open:bg-accent data-inset:pl-7 data-open:text-accent-foreground data-popup-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-sub-trigger\"\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = 'start',\n  alignOffset = -3,\n  side = 'right',\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      align={align}\n      alignOffset={alignOffset}\n      className={cn(\n        'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 w-auto min-w-[96px] rounded-lg bg-popover p-1 text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-open:animate-in',\n        className\n      )}\n      data-slot=\"dropdown-menu-sub-content\"\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      checked={checked}\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-checkbox-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      className={cn(\n        \"relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-7 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-inset={inset}\n      data-slot=\"dropdown-menu-radio-item\"\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      className={cn('-mx-1 my-1 h-px bg-border', className)}\n      data-slot=\"dropdown-menu-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<'span'>) {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',\n        className\n      )}\n      data-slot=\"dropdown-menu-shortcut\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
        },
        {
          "path": "components/ui/input.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/shared/components/ui/input.tsx",
          "content": "import { Input as InputPrimitive } from '@base-ui/react/input';\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n  return (\n    <InputPrimitive\n      className={cn(\n        'h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',\n        className\n      )}\n      data-slot=\"input\"\n      type={type}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
        },
        {
          "path": "components/ui/select.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/shared/components/ui/select.tsx",
          "content": "'use client';\n\nimport { Select as SelectPrimitive } from '@base-ui/react/select';\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';\nimport type * as React from 'react';\nimport { cn } from \"../../lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      className={cn('scroll-my-1 p-1', className)}\n      data-slot=\"select-group\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      className={cn('flex flex-1 text-left', className)}\n      data-slot=\"select-value\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = 'default',\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      className={cn(\n        \"flex w-fit select-none items-center justify-between gap-1.5 whitespace-nowrap rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className\n      )}\n      data-size={size}\n      data-slot=\"select-trigger\"\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={\n          <ChevronDownIcon className=\"pointer-events-none size-4 text-muted-foreground\" />\n        }\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = 'bottom',\n  sideOffset = 4,\n  align = 'center',\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    'align' | 'alignOffset' | 'side' | 'sideOffset' | 'alignItemWithTrigger'\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        align={align}\n        alignItemWithTrigger={alignItemWithTrigger}\n        alignOffset={alignOffset}\n        className=\"isolate z-50\"\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <SelectPrimitive.Popup\n          className={cn(\n            'data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-y-auto overflow-x-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-closed:animate-out data-open:animate-in',\n            className\n          )}\n          data-align-trigger={alignItemWithTrigger}\n          data-slot=\"select-content\"\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({\n  className,\n  ...props\n}: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      className={cn('px-1.5 py-1 text-muted-foreground text-xs', className)}\n      data-slot=\"select-label\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({\n  className,\n  children,\n  ...props\n}: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      className={cn(\n        \"relative flex w-full cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n        className\n      )}\n      data-slot=\"select-item\"\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 shrink-0 gap-2 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({\n  className,\n  ...props\n}: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      className={cn('-mx-1 pointer-events-none my-1 h-px bg-border', className)}\n      data-slot=\"select-separator\"\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      className={cn(\n        \"top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-up-button\"\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      className={cn(\n        \"bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      data-slot=\"select-scroll-down-button\"\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
        },
        {
          "path": "components/ui/table.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/shared/components/ui/table.tsx",
          "content": "'use client';\n\nimport type * as React from 'react';\n\nimport { cn } from \"../../lib/utils\";\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n  return (\n    <div\n      className=\"relative w-full overflow-x-auto\"\n      data-slot=\"table-container\"\n    >\n      <table\n        className={cn('w-full caption-bottom text-sm', className)}\n        data-slot=\"table\"\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n  return (\n    <thead\n      className={cn('[&_tr]:border-b', className)}\n      data-slot=\"table-header\"\n      {...props}\n    />\n  );\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n  return (\n    <tbody\n      className={cn('[&_tr:last-child]:border-0', className)}\n      data-slot=\"table-body\"\n      {...props}\n    />\n  );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n  return (\n    <tfoot\n      className={cn(\n        'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',\n        className\n      )}\n      data-slot=\"table-footer\"\n      {...props}\n    />\n  );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n  return (\n    <tr\n      className={cn(\n        'border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',\n        className\n      )}\n      data-slot=\"table-row\"\n      {...props}\n    />\n  );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n  return (\n    <th\n      className={cn(\n        'h-10 whitespace-nowrap px-2 text-left align-middle font-medium text-foreground [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-head\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n  return (\n    <td\n      className={cn(\n        'whitespace-nowrap p-2 align-middle [&:has([role=checkbox])]:pr-0',\n        className\n      )}\n      data-slot=\"table-cell\"\n      {...props}\n    />\n  );\n}\n\nfunction TableCaption({\n  className,\n  ...props\n}: React.ComponentProps<'caption'>) {\n  return (\n    <caption\n      className={cn('mt-4 text-muted-foreground text-sm', className)}\n      data-slot=\"table-caption\"\n      {...props}\n    />\n  );\n}\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n};\n"
        },
        {
          "path": "lib/utils.ts",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/shared/lib/utils.ts",
          "content": "export { cn } from \"cn\";\n"
        },
        {
          "path": "content/components/tables/table-05.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/table-05/table-05.tsx",
          "content": "'use client';\n\nimport {\n  type ColumnDef,\n  flexRender,\n  getCoreRowModel,\n  getFilteredRowModel,\n  getPaginationRowModel,\n  getSortedRowModel,\n  useReactTable,\n} from '@tanstack/react-table';\nimport {\n  ChevronLeft,\n  ChevronRight,\n  Eye,\n  MoreHorizontal,\n  Pencil,\n  Trash2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { Badge } from \"./shared/components/ui/badge\";\nimport { Button } from \"./shared/components/ui/button\";\nimport { Checkbox } from \"./shared/components/ui/checkbox\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"./shared/components/ui/dropdown-menu\";\nimport { Input } from \"./shared/components/ui/input\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"./shared/components/ui/select\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"./shared/components/ui/table\";\nimport { cn } from \"./shared/lib/utils\";\n\ntype Status = 'completed' | 'pending' | 'processing' | 'cancelled';\n\ninterface Item {\n  id: string;\n  name: string;\n  date: string;\n  status: Status;\n  amount: string;\n}\n\nconst statusConfig: Record<Status, { label: string; className: string }> = {\n  completed: {\n    label: 'Completed',\n    className:\n      'bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400',\n  },\n  pending: {\n    label: 'Pending',\n    className:\n      'bg-amber-500/15 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400',\n  },\n  processing: {\n    label: 'Processing',\n    className:\n      'bg-blue-500/15 text-blue-700 dark:bg-blue-500/10 dark:text-blue-400',\n  },\n  cancelled: {\n    label: 'Cancelled',\n    className:\n      'bg-rose-500/15 text-rose-700 dark:bg-rose-500/10 dark:text-rose-400',\n  },\n};\n\nfunction StatusBadge({ status }: { status: Status }) {\n  const config = statusConfig[status];\n  return (\n    <Badge className={cn('border-0', config.className)} variant=\"outline\">\n      {config.label}\n    </Badge>\n  );\n}\n\nconst columns: ColumnDef<Item>[] = [\n  {\n    id: 'select',\n    header: ({ table }) => (\n      <Checkbox\n        aria-label=\"Select all\"\n        checked={table.getIsAllPageRowsSelected()}\n        indeterminate={\n          table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected()\n        }\n        onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}\n      />\n    ),\n    cell: ({ row }) => (\n      <Checkbox\n        aria-label=\"Select row\"\n        checked={row.getIsSelected()}\n        onCheckedChange={(value) => row.toggleSelected(!!value)}\n      />\n    ),\n    enableSorting: false,\n    enableHiding: false,\n  },\n  {\n    accessorKey: 'name',\n    header: 'Name',\n    cell: ({ row }) => (\n      <span className=\"font-medium\">{row.getValue('name')}</span>\n    ),\n  },\n  {\n    accessorKey: 'date',\n    header: 'Date',\n  },\n  {\n    accessorKey: 'status',\n    header: 'Status',\n    cell: ({ row }) => <StatusBadge status={row.getValue('status')} />,\n  },\n  {\n    accessorKey: 'amount',\n    header: () => <div className=\"text-right\">Amount</div>,\n    cell: ({ row }) => (\n      <div className=\"text-right font-medium\">{row.getValue('amount')}</div>\n    ),\n  },\n  {\n    id: 'actions',\n    cell: () => (\n      <div className=\"text-right\">\n        <DropdownMenu>\n          <DropdownMenuTrigger\n            render={<Button className=\"h-8 w-8\" size=\"icon\" variant=\"ghost\" />}\n          >\n            <MoreHorizontal className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Open menu</span>\n          </DropdownMenuTrigger>\n          <DropdownMenuContent align=\"end\">\n            <DropdownMenuItem>\n              <Eye className=\"mr-2 h-4 w-4\" />\n              View details\n            </DropdownMenuItem>\n            <DropdownMenuItem>\n              <Pencil className=\"mr-2 h-4 w-4\" />\n              Edit\n            </DropdownMenuItem>\n            <DropdownMenuSeparator />\n            <DropdownMenuItem className=\"text-destructive\">\n              <Trash2 className=\"mr-2 h-4 w-4\" />\n              Delete\n            </DropdownMenuItem>\n          </DropdownMenuContent>\n        </DropdownMenu>\n      </div>\n    ),\n  },\n];\n\nconst data: Item[] = [\n  {\n    id: '1',\n    name: 'Project Alpha',\n    date: 'Jan 15, 2024',\n    status: 'completed',\n    amount: '$2,500',\n  },\n  {\n    id: '2',\n    name: 'Website Redesign',\n    date: 'Feb 3, 2024',\n    status: 'processing',\n    amount: '$4,200',\n  },\n  {\n    id: '3',\n    name: 'Mobile App MVP',\n    date: 'Feb 18, 2024',\n    status: 'pending',\n    amount: '$8,750',\n  },\n  {\n    id: '4',\n    name: 'Brand Identity',\n    date: 'Mar 5, 2024',\n    status: 'completed',\n    amount: '$1,800',\n  },\n  {\n    id: '5',\n    name: 'Marketing Campaign',\n    date: 'Mar 22, 2024',\n    status: 'cancelled',\n    amount: '$3,400',\n  },\n  {\n    id: '6',\n    name: 'Analytics Dashboard',\n    date: 'Apr 8, 2024',\n    status: 'processing',\n    amount: '$5,600',\n  },\n  {\n    id: '7',\n    name: 'E-commerce Platform',\n    date: 'Apr 25, 2024',\n    status: 'pending',\n    amount: '$12,000',\n  },\n  {\n    id: '8',\n    name: 'API Integration',\n    date: 'May 10, 2024',\n    status: 'completed',\n    amount: '$3,200',\n  },\n];\n\nexport default function Table05() {\n  const [globalFilter, setGlobalFilter] = useState('');\n\n  const table = useReactTable({\n    data,\n    columns,\n    getCoreRowModel: getCoreRowModel(),\n    getPaginationRowModel: getPaginationRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    onGlobalFilterChange: setGlobalFilter,\n    globalFilterFn: 'includesString',\n    state: {\n      globalFilter,\n    },\n    initialState: {\n      pagination: { pageSize: 5 },\n    },\n  });\n\n  const pageCount = table.getPageCount();\n  const currentPage = table.getState().pagination.pageIndex + 1;\n\n  return (\n    <div className=\"w-full max-w-3xl space-y-4\">\n      <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n        <div className=\"flex items-center gap-2\">\n          <span className=\"text-muted-foreground text-sm\">Show</span>\n          <Select\n            onValueChange={(value) => table.setPageSize(Number(value))}\n            value={String(table.getState().pagination.pageSize)}\n          >\n            <SelectTrigger className=\"h-8 w-16\">\n              <SelectValue />\n            </SelectTrigger>\n            <SelectContent>\n              {[5, 10, 20].map((size) => (\n                <SelectItem key={size} value={String(size)}>\n                  {size}\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n          <span className=\"text-muted-foreground text-sm\">entries</span>\n        </div>\n        <Input\n          className=\"h-8 w-full sm:w-64\"\n          onChange={(e) => setGlobalFilter(e.target.value)}\n          placeholder=\"Search...\"\n          value={globalFilter}\n        />\n      </div>\n\n      <div className=\"rounded-lg border\">\n        <Table>\n          <TableHeader>\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow key={headerGroup.id}>\n                {headerGroup.headers.map((header) => (\n                  <TableHead key={header.id}>\n                    {header.isPlaceholder\n                      ? null\n                      : flexRender(\n                          header.column.columnDef.header,\n                          header.getContext()\n                        )}\n                  </TableHead>\n                ))}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody>\n            {table.getRowModel().rows.length ? (\n              table.getRowModel().rows.map((row) => (\n                <TableRow\n                  data-state={row.getIsSelected() && 'selected'}\n                  key={row.id}\n                >\n                  {row.getVisibleCells().map((cell) => (\n                    <TableCell key={cell.id}>\n                      {flexRender(\n                        cell.column.columnDef.cell,\n                        cell.getContext()\n                      )}\n                    </TableCell>\n                  ))}\n                </TableRow>\n              ))\n            ) : (\n              <TableRow>\n                <TableCell\n                  className=\"h-24 text-center\"\n                  colSpan={columns.length}\n                >\n                  No results.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </div>\n\n      <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n        <p className=\"text-pretty text-muted-foreground text-sm\">\n          Showing{' '}\n          {table.getState().pagination.pageIndex *\n            table.getState().pagination.pageSize +\n            1}{' '}\n          to{' '}\n          {Math.min(\n            (table.getState().pagination.pageIndex + 1) *\n              table.getState().pagination.pageSize,\n            table.getFilteredRowModel().rows.length\n          )}{' '}\n          of {table.getFilteredRowModel().rows.length} entries\n        </p>\n        <div className=\"flex items-center gap-1\">\n          <Button\n            aria-label=\"Previous page\"\n            className=\"h-8 w-8\"\n            disabled={!table.getCanPreviousPage()}\n            onClick={() => table.previousPage()}\n            size=\"icon\"\n            variant=\"outline\"\n          >\n            <ChevronLeft className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Previous page</span>\n          </Button>\n          {Array.from({ length: pageCount }, (_, i) => i + 1).map((page) => (\n            <Button\n              aria-label={`Go to page ${page}`}\n              className=\"h-8 w-8\"\n              key={page}\n              onClick={() => table.setPageIndex(page - 1)}\n              size=\"icon\"\n              variant={currentPage === page ? 'default' : 'outline'}\n            >\n              {page}\n            </Button>\n          ))}\n          <Button\n            aria-label=\"Next page\"\n            className=\"h-8 w-8\"\n            disabled={!table.getCanNextPage()}\n            onClick={() => table.nextPage()}\n            size=\"icon\"\n            variant=\"outline\"\n          >\n            <ChevronRight className=\"h-4 w-4\" />\n            <span className=\"sr-only\">Next page</span>\n          </Button>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
        }
      ],
      "categories": [
        "tables",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/table-05/table-05.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/tables/table-05 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "Blocks.so",
          "url": "https://github.com/ephraimduncan/blocks",
          "license": "MIT",
          "author": "Ephraim Duncan",
          "revision": "3f759d5171c2391253ec4c772e9e5b3db2bc7c7f"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "table",
          "data",
          "filter",
          "rows",
          "表格",
          "数据",
          "筛选",
          "列表"
        ],
        "contentGuidelines": [
          "Use stable row identifiers and label row actions.",
          "Test long cells, no results, and horizontal overflow on mobile."
        ],
        "recommendedWith": [],
        "entry": "@components/uiblocks/table-05/table-05.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/table-05"
      }
    },
    {
      "name": "task-queue-01",
      "type": "registry:block",
      "title": "Processing Task Queue",
      "description": "A compact task queue with progress, status labels, and row actions for media processing workflows.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/task-queue-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/task-queue-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/workspace/task-queue-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/task-queue-01/task-queue-01.tsx",
          "content": "\"use client\";\n\nimport {\n  AlertCircle,\n  ArrowRight,\n  CheckCircle2,\n  Clock3,\n  FileVideo2,\n  Inbox,\n  LoaderCircle,\n  RotateCcw,\n} from \"lucide-react\";\nimport { useId, useState } from \"react\";\n\nexport type DemoTask = {\n  id: string;\n  name: string;\n  detail: string;\n  status: \"queued\" | \"processing\" | \"completed\" | \"error\";\n  progress?: number;\n};\nexport type TaskQueue01Props = {\n  title?: string;\n  tasks?: DemoTask[];\n  searchQuery?: string;\n  onRetry?: (id: string) => void;\n};\nconst sampleTasks: DemoTask[] = [\n  {\n    id: \"sample-1\",\n    name: \"Autumn-campaign.mp4\",\n    detail: \"MP4 · Web ready\",\n    status: \"processing\",\n    progress: 64,\n  },\n  {\n    id: \"sample-2\",\n    name: \"Product-walkthrough.mov\",\n    detail: \"MP4 · Web ready\",\n    status: \"queued\",\n  },\n  {\n    id: \"sample-3\",\n    name: \"Studio-notes.mp4\",\n    detail: \"MP4 · Web ready\",\n    status: \"completed\",\n  },\n];\nconst statusLabels = {\n  queued: \"In queue\",\n  processing: \"Preparing\",\n  completed: \"Ready\",\n  error: \"Needs attention\",\n};\n\n/** Task queue with status filtering, search, progress, empty and error states. */\nexport default function TaskQueue01({\n  title = \"Your task queue\",\n  tasks = sampleTasks,\n  searchQuery = \"\",\n  onRetry,\n}: TaskQueue01Props = {}) {\n  const [filter, setFilter] = useState<\"all\" | \"active\" | \"completed\">(\"all\");\n  const [notice, setNotice] = useState(\"\");\n  const id = useId();\n  const visible = tasks.filter(\n    (task) =>\n      task.name.toLowerCase().includes(searchQuery.toLowerCase()) &&\n      (filter === \"all\" ||\n        (filter === \"active\"\n          ? task.status === \"queued\" || task.status === \"processing\"\n          : task.status === \"completed\"))\n  );\n\n  return (\n    <section\n      aria-labelledby={id}\n      className=\"w-full overflow-hidden rounded-2xl border border-zinc-200 bg-white text-zinc-950 shadow-sm shadow-zinc-950/[0.02] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n    >\n      <div className=\"flex flex-wrap items-center justify-between gap-3 border-b border-zinc-100 px-5 py-5 sm:px-6 dark:border-zinc-800\">\n        <div className=\"flex items-center gap-2\">\n          <h2 className=\"text-sm font-semibold\" id={id}>\n            {title}\n          </h2>\n          <span className=\"rounded-md bg-zinc-100 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:bg-zinc-800\">\n            {tasks.length}\n          </span>\n        </div>\n        <span className=\"inline-flex items-center gap-1.5 text-[10px] text-zinc-400\">\n          <span className=\"size-1.5 rounded-full bg-emerald-500\" /> Demo\n          activity\n        </span>\n      </div>\n      <div\n        aria-label=\"Filter task queue\"\n        className=\"flex gap-5 border-b border-zinc-100 px-5 sm:px-6 dark:border-zinc-800\"\n        role=\"group\"\n      >\n        {([\"all\", \"active\", \"completed\"] as const).map((value) => (\n          <button\n            aria-pressed={filter === value}\n            className={`relative min-h-11 text-xs font-medium capitalize transition-colors ${filter === value ? \"text-violet-600 after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:bg-violet-600 dark:text-violet-400\" : \"text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200\"}`}\n            key={value}\n            onClick={() => setFilter(value)}\n            type=\"button\"\n          >\n            {value === \"all\" ? \"All files\" : value}\n          </button>\n        ))}\n      </div>\n      {visible.length ? (\n        <ul className=\"divide-y divide-zinc-100 dark:divide-zinc-800\">\n          {visible.map((task) => (\n            <li\n              className=\"flex items-center gap-3 px-5 py-4 sm:px-6\"\n              key={task.id}\n            >\n              <span className=\"grid size-10 shrink-0 place-items-center rounded-xl border border-zinc-100 bg-zinc-50 text-zinc-400 dark:border-zinc-800 dark:bg-zinc-900\">\n                <FileVideo2 aria-hidden=\"true\" className=\"size-4\" />\n              </span>\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"truncate text-xs font-medium\">{task.name}</p>\n                <p className=\"mt-1 text-[10px] text-zinc-400\">{task.detail}</p>\n                {task.status === \"processing\" && (\n                  <div\n                    aria-label={`${task.name} progress`}\n                    aria-valuemax={100}\n                    aria-valuemin={0}\n                    aria-valuenow={task.progress ?? 0}\n                    className=\"mt-2 h-1 max-w-48 overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800\"\n                    role=\"progressbar\"\n                  >\n                    <div\n                      className=\"h-full rounded-full bg-violet-500 transition-all duration-500\"\n                      style={{ width: `${task.progress ?? 0}%` }}\n                    />\n                  </div>\n                )}\n              </div>\n              <div\n                className={`flex shrink-0 items-center gap-1.5 text-[10px] ${task.status === \"completed\" ? \"text-emerald-600 dark:text-emerald-400\" : task.status === \"error\" ? \"text-red-500\" : task.status === \"processing\" ? \"text-violet-600 dark:text-violet-400\" : \"text-zinc-400\"}`}\n              >\n                {task.status === \"processing\" ? (\n                  <LoaderCircle\n                    aria-hidden=\"true\"\n                    className=\"size-3 animate-spin\"\n                  />\n                ) : task.status === \"completed\" ? (\n                  <CheckCircle2 aria-hidden=\"true\" className=\"size-3\" />\n                ) : task.status === \"error\" ? (\n                  <AlertCircle aria-hidden=\"true\" className=\"size-3\" />\n                ) : (\n                  <Clock3 aria-hidden=\"true\" className=\"size-3\" />\n                )}\n                <span className=\"hidden sm:inline\">\n                  {statusLabels[task.status]}\n                </span>\n                {task.status === \"processing\" && (\n                  <span>{task.progress ?? 0}%</span>\n                )}\n                {task.status === \"error\" && (\n                  <button\n                    aria-label={`Retry ${task.name}`}\n                    className=\"ml-1 rounded-md p-1.5 hover:bg-red-50 dark:hover:bg-red-950/40\"\n                    onClick={() => {\n                      if (onRetry) onRetry(task.id);\n                      else\n                        setNotice(\n                          \"Retry selected. This standalone queue is a browser-only demo.\"\n                        );\n                    }}\n                    type=\"button\"\n                  >\n                    <RotateCcw aria-hidden=\"true\" className=\"size-3\" />\n                  </button>\n                )}\n              </div>\n            </li>\n          ))}\n        </ul>\n      ) : (\n        <div className=\"flex min-h-44 flex-col items-center justify-center px-6 py-8 text-center\">\n          <Inbox\n            aria-hidden=\"true\"\n            className=\"size-7 text-zinc-300 dark:text-zinc-600\"\n          />\n          <p className=\"mt-3 text-xs font-medium\">\n            {searchQuery\n              ? \"No matching files\"\n              : filter === \"all\"\n                ? \"A fresh start\"\n                : \"Nothing here yet\"}\n          </p>\n          <p className=\"mt-1 text-[11px] text-zinc-400\">\n            {searchQuery\n              ? \"Try another file name.\"\n              : \"Your demo tasks will appear here.\"}\n          </p>\n        </div>\n      )}\n      {notice && (\n        <p\n          className=\"px-5 py-3 text-xs text-violet-600 dark:text-violet-400\"\n          role=\"status\"\n        >\n          {notice}\n        </p>\n      )}\n      <div className=\"flex items-center justify-between gap-2 border-t border-zinc-100 bg-zinc-50/50 px-5 py-3 text-[10px] text-zinc-400 sm:px-6 dark:border-zinc-800 dark:bg-zinc-900/30\">\n        <span>\n          {tasks.filter((task) => task.status === \"completed\").length} completed\n          ·{\" \"}\n          {\n            tasks.filter(\n              (task) => task.status === \"queued\" || task.status === \"processing\"\n            ).length\n          }{\" \"}\n          in progress\n        </span>\n        <span className=\"inline-flex items-center gap-1\">\n          Demo queue <ArrowRight aria-hidden=\"true\" className=\"size-3\" />\n        </span>\n      </div>\n    </section>\n  );\n}\n"
        }
      ],
      "categories": [
        "workspace",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/task-queue-01/task-queue-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/workspace/task-queue-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": true,
        "tags": [
          "queue",
          "tasks",
          "jobs",
          "progress",
          "status",
          "队列",
          "任务",
          "进度",
          "状态",
          "批量"
        ],
        "contentGuidelines": [
          "Use a stable identifier and explicit status for each task.",
          "Connect cancellation and retry to actual background job controls."
        ],
        "recommendedWith": [
          "upload-workflow-01",
          "results-gallery-01"
        ],
        "entry": "@components/uiblocks/task-queue-01/task-queue-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/task-queue-01"
      }
    },
    {
      "name": "upload-workflow-01",
      "type": "registry:block",
      "title": "File Processing Workspace",
      "description": "A file processing input panel with drag and drop, format controls, and a local demo processing state.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/upload-workflow-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/upload-workflow-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/workspace/upload-workflow-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/upload-workflow-01/upload-workflow-01.tsx",
          "content": "\"use client\";\n\nimport {\n  ArrowRight,\n  Check,\n  FileVideo2,\n  Film,\n  LoaderCircle,\n  Plus,\n  ShieldCheck,\n  UploadCloud,\n  X,\n} from \"lucide-react\";\nimport { useId, useRef, useState } from \"react\";\n\nexport type WorkflowFile = { id: string; name: string; size: number };\nexport type UploadWorkflow01Props = {\n  title?: string;\n  description?: string;\n  files?: WorkflowFile[];\n  onFilesChange?: (files: WorkflowFile[]) => void;\n  onRun?: (files: WorkflowFile[], format: string) => void;\n  busy?: boolean;\n};\n\nconst formats = [\"MP4\", \"WebM\", \"GIF\"];\nconst supportedExtension = /\\.(mp4|mov|webm|mkv)$/i;\n\n/** Browser-only file selection. The demo never uploads or transcodes media. */\nexport default function UploadWorkflow01({\n  title = \"Make something ready to share.\",\n  description = \"Add your videos, choose an output, and try the workflow.\",\n  files: controlledFiles,\n  onFilesChange,\n  onRun,\n  busy = false,\n}: UploadWorkflow01Props = {}) {\n  const [localFiles, setLocalFiles] = useState<WorkflowFile[]>([]);\n  const [format, setFormat] = useState(\"MP4\");\n  const [isDragging, setIsDragging] = useState(false);\n  const [error, setError] = useState(\"\");\n  const [notice, setNotice] = useState(\"\");\n  const inputRef = useRef<HTMLInputElement>(null);\n  const inputId = useId();\n  const files = controlledFiles ?? localFiles;\n\n  function updateFiles(next: WorkflowFile[]) {\n    setLocalFiles(next);\n    onFilesChange?.(next);\n    setNotice(\"\");\n  }\n\n  function addFiles(selected: FileList | null) {\n    if (!selected || busy) return;\n    const incoming = Array.from(selected);\n    const invalid = incoming.find(\n      (file) =>\n        !supportedExtension.test(file.name) || file.size > 200 * 1024 * 1024\n    );\n    if (invalid) {\n      setError(\"Choose MP4, MOV, WebM, or MKV videos up to 200 MB each.\");\n      return;\n    }\n    if (files.length + incoming.length > 10) {\n      setError(\"You can add up to 10 videos to a demo batch.\");\n      return;\n    }\n    setError(\"\");\n    updateFiles([\n      ...files,\n      ...incoming.map((file, index) => ({\n        id: `${file.name}-${file.lastModified}-${index}-${crypto.randomUUID()}`,\n        name: file.name,\n        size: file.size,\n      })),\n    ]);\n  }\n\n  return (\n    <section\n      className=\"min-w-0 w-full rounded-2xl border border-zinc-200 bg-white p-5 text-zinc-950 shadow-sm shadow-zinc-950/[0.02] sm:p-6 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n      aria-labelledby={`${inputId}-title`}\n    >\n      <div className=\"flex items-start gap-3\">\n        <span className=\"grid size-9 shrink-0 place-items-center rounded-xl bg-violet-50 text-violet-600 dark:bg-violet-950/40 dark:text-violet-400\">\n          <Film aria-hidden=\"true\" className=\"size-4\" />\n        </span>\n        <div>\n          <h2\n            className=\"text-base font-semibold tracking-tight\"\n            id={`${inputId}-title`}\n          >\n            {title}\n          </h2>\n          <p className=\"mt-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n        </div>\n      </div>\n      <div\n        className={`mt-6 rounded-xl border border-dashed p-6 text-center transition-colors sm:p-8 ${isDragging ? \"border-violet-500 bg-violet-50 dark:bg-violet-950/30\" : \"border-zinc-300 bg-zinc-50/70 dark:border-zinc-700 dark:bg-zinc-900/50\"}`}\n        onDragOver={(event) => {\n          event.preventDefault();\n          if (!busy) setIsDragging(true);\n        }}\n        onDragLeave={() => setIsDragging(false)}\n        onDrop={(event) => {\n          event.preventDefault();\n          setIsDragging(false);\n          addFiles(event.dataTransfer.files);\n        }}\n      >\n        <span className=\"mx-auto grid size-11 place-items-center rounded-xl border border-zinc-200 bg-white text-zinc-500 shadow-sm dark:border-zinc-700 dark:bg-zinc-900\">\n          <UploadCloud aria-hidden=\"true\" className=\"size-5\" />\n        </span>\n        <p className=\"mt-4 text-sm font-medium\">Drop your videos here</p>\n        <p className=\"mt-1 text-xs text-zinc-500 dark:text-zinc-400\">\n          MP4, MOV, WebM, MKV · Up to 200 MB each\n        </p>\n        <input\n          accept=\".mp4,.mov,.webm,.mkv\"\n          aria-label=\"Choose videos for the demo\"\n          className=\"sr-only\"\n          disabled={busy}\n          id={inputId}\n          multiple\n          onChange={(event) => {\n            addFiles(event.target.files);\n            event.target.value = \"\";\n          }}\n          ref={inputRef}\n          type=\"file\"\n        />\n        <button\n          className=\"mt-4 inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-3 text-xs font-medium text-zinc-700 shadow-sm transition-colors hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-600 disabled:opacity-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800\"\n          disabled={busy}\n          onClick={() => inputRef.current?.click()}\n          type=\"button\"\n        >\n          <Plus aria-hidden=\"true\" className=\"size-3.5\" /> Choose files\n        </button>\n      </div>\n      {files.length > 0 && (\n        <ul className=\"mt-4 space-y-2\" aria-label=\"Selected videos\">\n          {files.map((file) => (\n            <li\n              className=\"flex items-center gap-3 rounded-lg border border-zinc-100 bg-zinc-50/50 p-3 dark:border-zinc-800 dark:bg-zinc-900/40\"\n              key={file.id}\n            >\n              <FileVideo2\n                aria-hidden=\"true\"\n                className=\"size-4 shrink-0 text-violet-500\"\n              />\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"truncate text-xs font-medium\">{file.name}</p>\n                <p className=\"mt-0.5 text-[11px] text-zinc-400\">\n                  {file.size < 1024 * 1024\n                    ? `${Math.max(1, Math.round(file.size / 1024))} KB`\n                    : `${(file.size / 1024 / 1024).toFixed(1)} MB`}\n                </p>\n              </div>\n              <button\n                aria-label={`Remove ${file.name}`}\n                className=\"grid size-8 shrink-0 place-items-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 disabled:opacity-40 dark:hover:bg-zinc-800 dark:hover:text-white\"\n                disabled={busy}\n                onClick={() =>\n                  updateFiles(files.filter((item) => item.id !== file.id))\n                }\n                type=\"button\"\n              >\n                <X aria-hidden=\"true\" className=\"size-3.5\" />\n              </button>\n            </li>\n          ))}\n        </ul>\n      )}\n      <fieldset className=\"mt-6\" disabled={busy}>\n        <legend className=\"mb-2.5 text-xs font-medium\">Output format</legend>\n        <div className=\"grid grid-cols-3 gap-2\">\n          {formats.map((item) => (\n            <label\n              className={`flex min-h-10 cursor-pointer items-center justify-between rounded-lg border px-3 text-xs transition-colors focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-violet-500 ${format === item ? \"border-violet-500 bg-violet-50/70 text-violet-700 dark:bg-violet-950/30 dark:text-violet-300\" : \"border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-800 dark:text-zinc-400\"}`}\n              key={item}\n            >\n              <input\n                checked={format === item}\n                className=\"sr-only\"\n                name={`${inputId}-format`}\n                onChange={() => setFormat(item)}\n                type=\"radio\"\n                value={item}\n              />\n              <span>{item}</span>\n              {format === item && (\n                <Check aria-hidden=\"true\" className=\"size-3.5\" />\n              )}\n            </label>\n          ))}\n        </div>\n      </fieldset>\n      {error && (\n        <p className=\"mt-3 text-xs text-red-600 dark:text-red-400\" role=\"alert\">\n          {error}\n        </p>\n      )}\n      <button\n        className=\"mt-6 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl bg-violet-600 px-4 text-sm font-medium text-white shadow-sm shadow-violet-600/10 transition-colors hover:bg-violet-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-500 disabled:cursor-not-allowed disabled:opacity-45\"\n        disabled={busy || files.length === 0}\n        onClick={() => {\n          if (onRun) onRun(files, format);\n          else\n            setNotice(\n              `Demo ready: ${files.length} ${files.length === 1 ? \"video\" : \"videos\"} selected for ${format}. This preview does not convert media.`\n            );\n        }}\n        type=\"button\"\n      >\n        {busy ? (\n          <LoaderCircle aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n        ) : (\n          <ArrowRight aria-hidden=\"true\" className=\"size-4\" />\n        )}\n        {busy ? \"Preparing demo…\" : `Try ${format} workflow`}\n      </button>\n      {notice && (\n        <p\n          className=\"mt-3 text-xs leading-5 text-violet-600 dark:text-violet-300\"\n          role=\"status\"\n        >\n          {notice}\n        </p>\n      )}\n      <p className=\"mt-3 flex items-start justify-center gap-1.5 text-center text-[10px] leading-4 text-zinc-400\">\n        <ShieldCheck aria-hidden=\"true\" className=\"mt-0.5 size-3 shrink-0\" />{\" \"}\n        Browser-only demo. Your videos stay on this device; no media is\n        converted.\n      </p>\n    </section>\n  );\n}\n"
        }
      ],
      "categories": [
        "workspace",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/upload-workflow-01/upload-workflow-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/workspace/upload-workflow-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": true,
        "tags": [
          "upload",
          "dropzone",
          "file",
          "processing",
          "video",
          "workspace",
          "上传",
          "文件",
          "处理",
          "视频",
          "工作台"
        ],
        "contentGuidelines": [
          "The preview simulates processing locally; supply your own processing service.",
          "Validate supported file types and limits on both client and server in a production integration."
        ],
        "recommendedWith": [
          "task-queue-01",
          "results-gallery-01",
          "usage-panel-01"
        ],
        "entry": "@components/uiblocks/upload-workflow-01/upload-workflow-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/upload-workflow-01"
      }
    },
    {
      "name": "usage-panel-01",
      "type": "registry:block",
      "title": "Credits and Usage",
      "description": "A credits and usage summary that makes the remaining processing allowance easy to understand.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/usage-panel-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/usage-panel-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/workspace/usage-panel-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/usage-panel-01/usage-panel-01.tsx",
          "content": "\"use client\";\n\nimport {\n  ArrowUpRight,\n  CheckCircle2,\n  CircleHelp,\n  Leaf,\n  Zap,\n} from \"lucide-react\";\nimport { useId, useState } from \"react\";\n\nexport type UsagePanel01Props = {\n  title?: string;\n  used?: number;\n  limit?: number;\n  planName?: string;\n  upgradeHref?: string;\n};\n\n/** A usage summary with bounded progress and a plain-language plan explanation. */\nexport default function UsagePanel01({\n  title = \"A little room to create.\",\n  used = 38,\n  limit = 100,\n  planName = \"Studio demo\",\n  upgradeHref = \"/preview/recipes/tool-landing#pricing\",\n}: UsagePanel01Props = {}) {\n  const [showInfo, setShowInfo] = useState(false);\n  const id = useId();\n  const percentage =\n    limit > 0 ? Math.max(0, Math.min(100, (used / limit) * 100)) : 0;\n  return (\n    <aside\n      aria-labelledby={id}\n      className=\"w-full rounded-2xl border border-zinc-200 bg-white p-5 text-zinc-950 shadow-sm shadow-zinc-950/[0.02] sm:p-6 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n    >\n      <div className=\"flex items-start justify-between gap-3\">\n        <span className=\"grid size-9 place-items-center rounded-xl bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300\">\n          <Zap aria-hidden=\"true\" className=\"size-4\" />\n        </span>\n        <span className=\"rounded-md bg-violet-50 px-2 py-1 text-[10px] font-medium text-violet-600 dark:bg-violet-950/40 dark:text-violet-400\">\n          {planName}\n        </span>\n      </div>\n      <h2 className=\"mt-4 text-sm font-semibold\" id={id}>\n        {title}\n      </h2>\n      <p className=\"mt-2 text-xs leading-5 text-zinc-500 dark:text-zinc-400\">\n        Keep an eye on your workspace usage as your ideas take shape.\n      </p>\n      <div className=\"mt-5 flex items-baseline justify-between gap-3\">\n        <p className=\"text-xl font-semibold tabular-nums tracking-tight\">\n          {Math.max(0, used)}\n          <span className=\"ml-1 text-xs font-normal text-zinc-400\">\n            / {Math.max(0, limit)} credits\n          </span>\n        </p>\n        <button\n          aria-expanded={showInfo}\n          aria-label=\"About demo credits\"\n          className=\"grid size-7 place-items-center rounded-md text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n          onClick={() => setShowInfo(!showInfo)}\n          type=\"button\"\n        >\n          <CircleHelp aria-hidden=\"true\" className=\"size-3.5\" />\n        </button>\n      </div>\n      <div\n        aria-label=\"Demo credit usage\"\n        aria-valuemax={100}\n        aria-valuemin={0}\n        aria-valuenow={Math.round(percentage)}\n        className=\"mt-3 h-1.5 overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800\"\n        role=\"progressbar\"\n      >\n        <div\n          className=\"h-full rounded-full bg-violet-500 transition-all duration-500\"\n          style={{ width: `${percentage}%` }}\n        />\n      </div>\n      <p className=\"mt-2 text-[10px] text-zinc-400\">\n        Illustrative usage · No account or charges\n      </p>\n      {showInfo && (\n        <p className=\"mt-3 rounded-lg bg-zinc-50 p-3 text-[11px] leading-5 text-zinc-500 dark:bg-zinc-900 dark:text-zinc-400\">\n          These credits demonstrate a usage layout. Demo runs do not spend real\n          credits, and no subscription is created.\n        </p>\n      )}\n      <a\n        className=\"mt-5 flex min-h-9 items-center justify-center gap-1.5 rounded-lg border border-zinc-200 text-xs font-medium transition-colors hover:border-violet-300 hover:bg-violet-50 hover:text-violet-700 dark:border-zinc-700 dark:hover:bg-violet-950/30 dark:hover:text-violet-300\"\n        href={upgradeHref}\n      >\n        Explore example plans{\" \"}\n        <ArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n      </a>\n      <div className=\"mt-5 space-y-2 border-t border-zinc-100 pt-4 text-[10px] text-zinc-400 dark:border-zinc-800\">\n        <p className=\"flex items-center gap-1.5\">\n          <CheckCircle2\n            aria-hidden=\"true\"\n            className=\"size-3 text-emerald-500\"\n          />{\" \"}\n          Local files stay private\n        </p>\n        <p className=\"flex items-center gap-1.5\">\n          <Leaf aria-hidden=\"true\" className=\"size-3 text-emerald-500\" /> No\n          background uploads\n        </p>\n      </div>\n    </aside>\n  );\n}\n"
        }
      ],
      "categories": [
        "workspace",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/usage-panel-01/usage-panel-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/workspace/usage-panel-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": true,
        "tags": [
          "usage",
          "credits",
          "quota",
          "billing",
          "allowance",
          "额度",
          "积分",
          "用量",
          "账单",
          "剩余"
        ],
        "contentGuidelines": [
          "Display the allowance period and unit alongside usage.",
          "Calculate authoritative usage and entitlements in your backend."
        ],
        "recommendedWith": [
          "workspace-header-01",
          "upload-workflow-01",
          "pricing-01"
        ],
        "entry": "@components/uiblocks/usage-panel-01/usage-panel-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/usage-panel-01"
      }
    },
    {
      "name": "workspace-header-01",
      "type": "registry:block",
      "title": "Workspace Header",
      "description": "A responsive tool workspace heading with project context, status, and a primary creation action.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/workspace-header-01/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/workspace-header-01/LICENSE.txt",
          "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/components/workspace/workspace-header-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/workspace-header-01/workspace-header-01.tsx",
          "content": "\"use client\";\n\nimport {\n  ArrowLeft,\n  ArrowUpRight,\n  Command,\n  Layers2,\n  Search,\n} from \"lucide-react\";\nimport { useState } from \"react\";\n\nexport type WorkspaceHeader01Props = {\n  productName?: string;\n  title?: string;\n  description?: string;\n  websiteHref?: string;\n  searchQuery?: string;\n  onSearch?: (query: string) => void;\n};\n\n/** A compact workspace masthead with an accessible, functional file search. */\nexport default function WorkspaceHeader01({\n  productName = \"Frame\",\n  title = \"A little less waiting. A lot more creating.\",\n  description = \"Prepare your next video, keep every task in view, and pick up where you left off.\",\n  websiteHref = \"/preview/recipes/tool-landing\",\n  searchQuery,\n  onSearch,\n}: WorkspaceHeader01Props = {}) {\n  const [query, setQuery] = useState(\"\");\n\n  return (\n    <header\n      className=\"w-full bg-white text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50\"\n      id=\"workspace\"\n    >\n      <div className=\"border-b border-zinc-200 dark:border-zinc-800\">\n        <div className=\"mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-4 px-5 py-4 sm:px-8\">\n          <a\n            className=\"inline-flex items-center gap-2.5 text-lg font-semibold tracking-tight\"\n            href={websiteHref}\n          >\n            <span className=\"grid size-8 place-items-center rounded-xl bg-violet-600 text-white\">\n              <Layers2 aria-hidden=\"true\" className=\"size-4\" />\n            </span>\n            {productName}\n            <span className=\"ml-1 rounded-md border border-zinc-200 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-widest text-zinc-500 dark:border-zinc-800\">\n              Workspace\n            </span>\n          </a>\n          <div className=\"flex items-center gap-4\">\n            <a\n              className=\"hidden items-center gap-1.5 text-xs text-zinc-500 transition-colors hover:text-violet-600 sm:inline-flex\"\n              href={websiteHref}\n            >\n              <ArrowLeft aria-hidden=\"true\" className=\"size-3.5\" /> Back to\n              website\n            </a>\n            <span className=\"inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-[11px] font-medium text-amber-700 ring-1 ring-inset ring-amber-200 dark:bg-amber-950/30 dark:text-amber-300 dark:ring-amber-900\">\n              Interactive demo{\" \"}\n              <ArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n            </span>\n            <span\n              aria-label=\"Demo account\"\n              className=\"grid size-8 place-items-center rounded-full bg-zinc-100 text-xs font-semibold text-zinc-600 ring-1 ring-zinc-200 dark:bg-zinc-800 dark:text-zinc-300 dark:ring-zinc-700\"\n            >\n              JD\n            </span>\n          </div>\n        </div>\n      </div>\n      <div className=\"mx-auto grid max-w-7xl gap-6 px-5 pt-9 pb-7 sm:px-8 md:grid-cols-[1fr_260px] md:items-end\">\n        <div>\n          <p className=\"mb-2 text-xs font-medium text-violet-600 dark:text-violet-400\">\n            Your studio, in one place\n          </p>\n          <h1 className=\"max-w-2xl text-balance text-2xl font-semibold leading-tight tracking-tight sm:text-3xl\">\n            {title}\n          </h1>\n          <p className=\"mt-3 max-w-2xl text-sm leading-6 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n        </div>\n        <label className=\"flex min-h-10 items-center gap-2.5 rounded-xl border border-zinc-200 bg-zinc-50 px-3 text-zinc-400 focus-within:border-violet-400 focus-within:ring-2 focus-within:ring-violet-100 dark:border-zinc-800 dark:bg-zinc-900 dark:focus-within:ring-violet-900\">\n          <Search aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n          <input\n            aria-label=\"Search workspace files\"\n            className=\"w-full min-w-0 bg-transparent py-2 text-sm text-zinc-800 outline-none placeholder:text-zinc-400 dark:text-zinc-200\"\n            onChange={(event) => {\n              setQuery(event.target.value);\n              onSearch?.(event.target.value);\n            }}\n            placeholder=\"Find a file…\"\n            type=\"search\"\n            value={searchQuery ?? query}\n          />\n          <Command\n            aria-hidden=\"true\"\n            className=\"size-3.5 shrink-0 opacity-50\"\n          />\n        </label>\n      </div>\n    </header>\n  );\n}\n"
        }
      ],
      "categories": [
        "workspace",
        "application",
        "clean-saas"
      ],
      "docs": "Import the default component from @components/uiblocks/workspace-header-01/workspace-header-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/workspace/workspace-header-01 for content guidelines and source attribution.",
      "meta": {
        "kind": "block",
        "version": "0.1.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.1.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": true,
        "tags": [
          "workspace",
          "header",
          "project",
          "tool",
          "工作台",
          "页头",
          "项目",
          "工具"
        ],
        "contentGuidelines": [
          "Use one workspace title and a short status message.",
          "Connect the primary action to file selection or creation in the containing page."
        ],
        "recommendedWith": [
          "upload-workflow-01",
          "usage-panel-01"
        ],
        "entry": "@components/uiblocks/workspace-header-01/workspace-header-01.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/workspace-header-01"
      }
    },
    {
      "name": "recipe-tool-landing",
      "type": "registry:block",
      "title": "AI tool landing page",
      "description": "A complete, quietly confident product page. Introduce your tool, show its workflow, explain the plans, and answer the questions that matter.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/recipe-tool-landing/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/recipe-tool-landing/LICENSE.txt",
          "content": "UIBlocks original page recipe: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/recipe-components/tool-landing.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-landing/recipe-tool-landing.tsx",
          "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport Faq01 from \"./shared/content/components/marketing/faq-01\";\nimport Features01 from \"./shared/content/components/marketing/features-01\";\nimport Footer01 from \"./shared/content/components/marketing/footer-01\";\nimport Hero01 from \"./shared/content/components/marketing/hero-01\";\nimport LogoCloud01 from \"./shared/content/components/marketing/logo-cloud-01\";\nimport Navigation01 from \"./shared/content/components/marketing/navigation-01\";\nimport Pricing01, {\n  type PricingPlan,\n} from \"./shared/content/components/marketing/pricing-01\";\n\nconst samplePlans: PricingPlan[] = [\n  {\n    name: \"Personal\",\n    description: \"For the occasional good idea.\",\n    monthlyPrice: 0,\n    annualMonthlyPrice: 0,\n    features: [\n      \"3 projects\",\n      \"Single-file workflow\",\n      \"Standard exports\",\n      \"Local demo workspace\",\n    ],\n    actionLabel: \"Explore the demo\",\n  },\n  {\n    name: \"Creator\",\n    description: \"For a workflow that keeps moving.\",\n    monthlyPrice: 19,\n    annualMonthlyPrice: 15,\n    features: [\n      \"Unlimited projects\",\n      \"Batch file workflows\",\n      \"Reusable presets\",\n      \"Export history\",\n    ],\n    highlighted: true,\n    actionLabel: \"Explore Creator\",\n  },\n  {\n    name: \"Studio\",\n    description: \"For a little more room to make.\",\n    monthlyPrice: 39,\n    annualMonthlyPrice: 32,\n    features: [\n      \"Everything in Creator\",\n      \"Shared project folders\",\n      \"Team-ready layouts\",\n      \"Usage overview\",\n    ],\n    actionLabel: \"Explore Studio\",\n  },\n];\n\nconst sectionLinks = [\n  { label: \"Features\", href: \"#features\" },\n  { label: \"Pricing\", href: \"#pricing\" },\n  { label: \"Questions\", href: \"#faq\" },\n];\n\nexport type ToolLandingRecipeProps = {\n  /** Inherit the host theme, or enable its class-based dark variant for this page. */\n  theme?: \"inherit\" | \"dark\";\n  className?: string;\n  brand?: string;\n  homeHref?: string;\n  primaryHref?: string;\n  primaryLabel?: string;\n  navigation?: ComponentProps<typeof Navigation01>;\n  hero?: ComponentProps<typeof Hero01>;\n  formats?: ComponentProps<typeof LogoCloud01>;\n  features?: ComponentProps<typeof Features01>;\n  pricing?: ComponentProps<typeof Pricing01>;\n  faq?: ComponentProps<typeof Faq01>;\n  footer?: ComponentProps<typeof Footer01>;\n};\n\n/**\n * An installable seven-section page, independent of the host router.\n * Replace the fictional Frame copy, sample prices, and capability claims through\n * the section props before publishing. Anchors are ordinary host-owned URLs.\n */\nexport default function ToolLandingRecipe({\n  theme = \"inherit\",\n  className = \"\",\n  brand = \"Frame\",\n  homeHref = \"#frame-top\",\n  primaryHref = \"#pricing\",\n  primaryLabel = \"Get started\",\n  navigation,\n  hero,\n  formats,\n  features,\n  pricing,\n  faq,\n  footer,\n}: ToolLandingRecipeProps) {\n  return (\n    <div\n      className={`${theme === \"inherit\" ? \"\" : theme} min-h-screen bg-white dark:bg-zinc-950 ${className}`}\n    >\n      <Navigation01\n        actionHref={primaryHref}\n        actionLabel={primaryLabel}\n        brand={brand}\n        homeHref={homeHref}\n        links={sectionLinks}\n        {...navigation}\n      />\n      <main>\n        <Hero01\n          primaryHref={primaryHref}\n          primaryLabel={primaryLabel}\n          secondaryHref=\"#features\"\n          {...hero}\n        />\n        <LogoCloud01 {...formats} />\n        <Features01 {...features} />\n        <Pricing01\n          {...pricing}\n          plans={(pricing?.plans ?? samplePlans).map((plan) => ({\n            ...plan,\n            actionHref: plan.actionHref ?? primaryHref,\n          }))}\n        />\n        <Faq01 actionHref={primaryHref} actionLabel={primaryLabel} {...faq} />\n      </main>\n      <Footer01\n        brand={brand}\n        homeHref={homeHref}\n        links={[...sectionLinks, { label: \"Workspace\", href: primaryHref }]}\n        {...footer}\n      />\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/marketing/faq-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-landing/shared/content/components/marketing/faq-01.tsx",
          "content": "\"use client\";\n\nimport { ArrowUpRight, Minus, Plus } from \"lucide-react\";\nimport { useId, useState } from \"react\";\n\nexport type Faq01Props = {\n  title?: string;\n  description?: string;\n  items?: { question: string; answer: string }[];\n  actionLabel?: string;\n  actionHref?: string;\n};\n\n/** Single-open FAQ with semantic controls and content-sized answer panels. */\nexport default function Faq01({\n  title = \"A few things you might be wondering.\",\n  description = \"A little clarity before you give it a try.\",\n  actionLabel = \"Try the workspace\",\n  actionHref = \"/preview/recipes/tool-workspace\",\n  items = [\n    {\n      question: \"Is this a real file processing service?\",\n      answer:\n        \"Frame is a fictional product used to demonstrate this UIBlocks page recipe. Its workspace shows local interface interactions; it does not upload, convert, or store your files on a server.\",\n    },\n    {\n      question: \"What happens to files I choose in the demo?\",\n      answer:\n        \"Selected file details stay in your browser session. The demo can create a downloadable SVG demo preview to show the export interaction. It does not convert your selected files. Closing or refreshing the page clears the session.\",\n    },\n    {\n      question: \"Can I use these sections in my own product?\",\n      answer:\n        \"Yes. The UIBlocks-original sections in this recipe use the MIT license. You can install the source, adapt the copy and brand, and connect your own product behavior. Retain the included license notices.\",\n    },\n    {\n      question: \"How do I keep the whole page consistent?\",\n      answer:\n        \"Start with the complete recipe, then change your text, product name, and accent color together. Keep the shared content width, type scale, borders, and section spacing as you adapt the page.\",\n    },\n  ],\n}: Faq01Props) {\n  const [openIndex, setOpenIndex] = useState<number | null>(0);\n  const baseId = useId();\n  return (\n    <section\n      className=\"w-full scroll-mt-10 bg-white py-20 text-zinc-950 md:py-24 dark:bg-zinc-950 dark:text-white\"\n      id=\"faq\"\n    >\n      <div className=\"mx-auto grid max-w-6xl gap-10 px-6 lg:grid-cols-[0.9fr_1.1fr] lg:gap-20 lg:px-8\">\n        <div>\n          <p className=\"text-[10px] font-semibold tracking-[0.16em] text-violet-600 dark:text-violet-400\">\n            GOOD QUESTIONS\n          </p>\n          <h2 className=\"mt-4 max-w-sm text-balance text-3xl font-semibold leading-tight tracking-[-0.035em] sm:text-4xl\">\n            {title}\n          </h2>\n          <p className=\"mt-4 text-sm leading-6 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n          <a\n            className=\"mt-6 inline-flex min-h-10 items-center gap-2 rounded text-sm font-medium text-violet-600 hover:text-violet-700 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-violet-400\"\n            href={actionHref}\n          >\n            {actionLabel}\n            <ArrowUpRight aria-hidden=\"true\" className=\"size-3.5\" />\n          </a>\n        </div>\n        <div className=\"border-t border-zinc-200 dark:border-zinc-800\">\n          {items.map((item, index) => {\n            const isOpen = openIndex === index;\n            const panelId = `${baseId}-answer-${index}`;\n            const buttonId = `${baseId}-question-${index}`;\n            return (\n              <div\n                className=\"border-b border-zinc-200 dark:border-zinc-800\"\n                key={item.question}\n              >\n                <h3>\n                  <button\n                    aria-controls={panelId}\n                    aria-expanded={isOpen}\n                    className=\"flex w-full items-center justify-between gap-5 py-5 text-left text-sm font-medium leading-6 transition-colors hover:text-violet-600 focus-visible:outline-2 focus-visible:outline-violet-500 dark:hover:text-violet-400\"\n                    id={buttonId}\n                    onClick={() => setOpenIndex(isOpen ? null : index)}\n                    type=\"button\"\n                  >\n                    {item.question}\n                    {isOpen ? (\n                      <Minus\n                        aria-hidden=\"true\"\n                        className=\"size-4 shrink-0 text-zinc-400\"\n                      />\n                    ) : (\n                      <Plus\n                        aria-hidden=\"true\"\n                        className=\"size-4 shrink-0 text-zinc-400\"\n                      />\n                    )}\n                  </button>\n                </h3>\n                <div\n                  aria-labelledby={buttonId}\n                  hidden={!isOpen}\n                  id={panelId}\n                  role=\"region\"\n                >\n                  <p className=\"pb-6 pr-6 text-sm leading-7 text-zinc-500 dark:text-zinc-400\">\n                    {item.answer}\n                  </p>\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "content/components/marketing/features-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-landing/shared/content/components/marketing/features-01.tsx",
          "content": "import {\n  ArrowRight,\n  Download,\n  FolderOpen,\n  SlidersHorizontal,\n} from \"lucide-react\";\n\nexport type Features01Props = {\n  eyebrow?: string;\n  title?: string;\n  description?: string;\n  items?: { title: string; description: string; detail: string }[];\n};\n\n/** Three-step feature section. Keep each description to roughly two sentences. */\nexport default function Features01({\n  eyebrow = \"FROM FIRST FILE TO FINAL EXPORT\",\n  title = \"A clear path from here to done.\",\n  description = \"The little details stay organized, so your next step always feels obvious.\",\n  items = [\n    {\n      title: \"Bring everything together\",\n      description:\n        \"Drop in a file or a whole batch. See names, formats, and sizes together before you start.\",\n      detail: \"One tidy starting point\",\n    },\n    {\n      title: \"Make it your workflow\",\n      description:\n        \"Choose your output settings once. Keep an eye on each task without losing your place.\",\n      detail: \"Small steps, clear progress\",\n    },\n    {\n      title: \"Leave with something useful\",\n      description:\n        \"Review your results, find the right version, and download it when you’re ready.\",\n      detail: \"An easy last mile\",\n    },\n  ],\n}: Features01Props) {\n  const icons = [FolderOpen, SlidersHorizontal, Download];\n  return (\n    <section\n      className=\"w-full scroll-mt-10 bg-white py-20 text-zinc-950 md:py-24 dark:bg-zinc-950 dark:text-white\"\n      id=\"features\"\n    >\n      <div className=\"mx-auto max-w-6xl px-6 lg:px-8\">\n        <div className=\"max-w-xl\">\n          <p className=\"text-[10px] font-semibold tracking-[0.16em] text-violet-600 dark:text-violet-400\">\n            {eyebrow}\n          </p>\n          <h2 className=\"mt-4 text-balance text-3xl font-semibold leading-tight tracking-[-0.035em] sm:text-4xl\">\n            {title}\n          </h2>\n          <p className=\"mt-4 max-w-lg text-pretty text-sm leading-7 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n        </div>\n        <div className=\"mt-12 grid gap-8 md:grid-cols-3 md:gap-10\">\n          {items.map((item, index) => {\n            const Icon = icons[index % icons.length];\n            return (\n              <article\n                className=\"relative border-t border-zinc-200 pt-6 dark:border-zinc-800\"\n                key={item.title}\n              >\n                <div className=\"flex items-center justify-between\">\n                  <span className=\"grid size-10 place-items-center rounded-lg border border-zinc-200 bg-zinc-50 text-zinc-700 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300\">\n                    <Icon aria-hidden=\"true\" className=\"size-4.5\" />\n                  </span>\n                  <span\n                    aria-hidden=\"true\"\n                    className=\"font-mono text-xs text-zinc-300 dark:text-zinc-600\"\n                  >\n                    0{index + 1}\n                  </span>\n                </div>\n                <h3 className=\"mt-5 text-base font-semibold tracking-tight\">\n                  {item.title}\n                </h3>\n                <p className=\"mt-3 text-sm leading-6 text-zinc-500 dark:text-zinc-400\">\n                  {item.description}\n                </p>\n                <p className=\"mt-5 flex items-center gap-2 text-xs font-medium text-violet-600 dark:text-violet-400\">\n                  {item.detail}\n                  <ArrowRight aria-hidden=\"true\" className=\"size-3\" />\n                </p>\n              </article>\n            );\n          })}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "content/components/marketing/footer-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-landing/shared/content/components/marketing/footer-01.tsx",
          "content": "import { ArrowUpRight, Layers2 } from \"lucide-react\";\n\nexport type Footer01Props = {\n  brand?: string;\n  homeHref?: string;\n  tagline?: string;\n  links?: { label: string; href: string }[];\n  note?: string;\n};\n\n/** Compact product footer; replace the demonstration notice for a live product. */\nexport default function Footer01({\n  brand = \"Frame\",\n  homeHref = \"/preview/recipes/tool-landing#frame-top\",\n  tagline = \"More space for the work you love.\",\n  links = [\n    { label: \"Features\", href: \"/preview/recipes/tool-landing#features\" },\n    { label: \"Pricing\", href: \"/preview/recipes/tool-landing#pricing\" },\n    { label: \"Questions\", href: \"/preview/recipes/tool-landing#faq\" },\n    { label: \"Workspace\", href: \"/preview/recipes/tool-workspace\" },\n  ],\n  note = \"Frame is a fictional product. A page recipe by UIBlocks.\",\n}: Footer01Props) {\n  return (\n    <footer className=\"w-full border-t border-zinc-200/70 bg-zinc-50/70 py-10 text-zinc-950 dark:border-zinc-800 dark:bg-zinc-900/30 dark:text-white\">\n      <div className=\"mx-auto max-w-6xl px-6 lg:px-8\">\n        <div className=\"flex flex-col justify-between gap-8 sm:flex-row sm:items-center\">\n          <div>\n            <a\n              className=\"inline-flex items-center gap-2.5 rounded text-lg font-semibold tracking-tight focus-visible:outline-2 focus-visible:outline-violet-500\"\n              href={homeHref}\n            >\n              <span className=\"grid size-7 place-items-center rounded-lg bg-violet-600 text-white\">\n                <Layers2 aria-hidden=\"true\" className=\"size-4\" />\n              </span>\n              {brand}\n            </a>\n            <p className=\"mt-3 text-xs text-zinc-500 dark:text-zinc-400\">\n              {tagline}\n            </p>\n          </div>\n          <nav\n            aria-label=\"Footer product navigation\"\n            className=\"flex flex-wrap gap-x-7 gap-y-4\"\n          >\n            {links.map((link) => (\n              <a\n                className=\"inline-flex items-center gap-1.5 rounded text-xs text-zinc-500 transition-colors hover:text-zinc-950 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-zinc-400 dark:hover:text-white\"\n                href={link.href}\n                key={link.href}\n              >\n                {link.label}\n                <ArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n              </a>\n            ))}\n          </nav>\n        </div>\n        <div className=\"mt-8 border-t border-zinc-200 pt-6 text-[10px] leading-5 text-zinc-400 dark:border-zinc-800\">\n          {note}\n        </div>\n      </div>\n    </footer>\n  );\n}\n"
        },
        {
          "path": "content/components/marketing/hero-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-landing/shared/content/components/marketing/hero-01.tsx",
          "content": "import {\n  ArrowDown,\n  ArrowRight,\n  Check,\n  CheckCheck,\n  FileVideo,\n  Film,\n  Play,\n  Sparkles,\n} from \"lucide-react\";\n\nexport type Hero01Props = {\n  eyebrow?: string;\n  title?: string;\n  description?: string;\n  primaryLabel?: string;\n  primaryHref?: string;\n  secondaryLabel?: string;\n  secondaryHref?: string;\n};\n\n/** Split hero with an entirely code-rendered, asset-free product illustration. */\nexport default function Hero01({\n  eyebrow = \"A little less busywork. A lot more creating.\",\n  title = \"Your files. Ready for what’s next.\",\n  description = \"One calm workspace to organize, prepare, and export your media. Bring the files. Keep your focus on the good part.\",\n  primaryLabel = \"Explore the workspace\",\n  primaryHref = \"/preview/recipes/tool-workspace\",\n  secondaryLabel = \"See how it works\",\n  secondaryHref = \"/preview/recipes/tool-landing#features\",\n}: Hero01Props) {\n  return (\n    <section className=\"w-full overflow-hidden bg-white text-zinc-950 dark:bg-zinc-950 dark:text-white\">\n      <div className=\"mx-auto grid max-w-6xl items-center gap-14 px-6 py-20 md:py-28 lg:grid-cols-[1fr_1.08fr] lg:gap-16 lg:px-8\">\n        <div>\n          <p className=\"mb-6 inline-flex items-center gap-2 text-xs font-medium tracking-wide text-violet-600 dark:text-violet-400\">\n            <span className=\"size-1.5 rounded-full bg-violet-500\" />\n            {eyebrow}\n          </p>\n          <h1 className=\"max-w-[12ch] text-balance text-5xl font-semibold leading-[1.08] tracking-[-0.055em] sm:text-6xl lg:text-[4.25rem]\">\n            {title}\n          </h1>\n          <p className=\"mt-6 max-w-md text-pretty text-base leading-7 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n          <div className=\"mt-8 flex flex-wrap items-center gap-x-6 gap-y-4\">\n            <a\n              className=\"inline-flex min-h-11 items-center gap-3 rounded-lg bg-violet-600 px-5 py-3 text-sm font-medium text-white shadow-sm shadow-violet-600/15 transition-colors hover:bg-violet-700 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-violet-500\"\n              href={primaryHref}\n            >\n              {primaryLabel}\n              <ArrowRight aria-hidden=\"true\" className=\"size-4\" />\n            </a>\n            <a\n              className=\"inline-flex min-h-11 items-center gap-2 rounded text-sm font-medium text-zinc-600 hover:text-zinc-950 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-zinc-300 dark:hover:text-white\"\n              href={secondaryHref}\n            >\n              {secondaryLabel}\n              <ArrowDown aria-hidden=\"true\" className=\"size-3.5\" />\n            </a>\n          </div>\n          <p className=\"mt-7 flex items-center gap-2 text-xs text-zinc-400\">\n            <CheckCheck aria-hidden=\"true\" className=\"size-4 text-zinc-400\" />\n            Interactive demo · No account needed\n          </p>\n        </div>\n        <div className=\"relative min-w-0 scroll-mt-8\" id=\"product-preview\">\n          <div\n            aria-hidden=\"true\"\n            className=\"absolute -inset-8 rounded-[3rem] bg-violet-50/70 dark:bg-violet-950/20\"\n          />\n          <div className=\"relative overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-[0_20px_60px_-25px_rgba(30,41,59,0.2)] dark:border-zinc-700 dark:bg-zinc-900\">\n            <div className=\"flex items-center justify-between border-b border-zinc-100 px-4 py-3 dark:border-zinc-800\">\n              <div aria-hidden=\"true\" className=\"flex gap-1.5\">\n                <span className=\"size-2 rounded-full bg-zinc-200 dark:bg-zinc-700\" />\n                <span className=\"size-2 rounded-full bg-zinc-200 dark:bg-zinc-700\" />\n                <span className=\"size-2 rounded-full bg-zinc-200 dark:bg-zinc-700\" />\n              </div>\n              <span className=\"font-mono text-[10px] text-zinc-400\">\n                frame / workspace\n              </span>\n              <span className=\"rounded border border-zinc-200 px-1.5 py-0.5 text-[9px] font-medium tracking-wider text-zinc-400 dark:border-zinc-700\">\n                DEMO\n              </span>\n            </div>\n            <div className=\"p-5 sm:p-7\">\n              <div className=\"mb-5 flex items-start justify-between gap-3\">\n                <div>\n                  <p className=\"text-sm font-semibold\">The next big idea</p>\n                  <p className=\"mt-1 text-[11px] text-zinc-400\">\n                    A place for work in progress.\n                  </p>\n                </div>\n                <span className=\"rounded-full bg-violet-50 px-2.5 py-1 text-[10px] font-medium text-violet-600 dark:bg-violet-500/10 dark:text-violet-300\">\n                  Personal project\n                </span>\n              </div>\n              <div className=\"relative grid aspect-[1.65] place-items-center overflow-hidden rounded-lg bg-zinc-950\">\n                <div\n                  aria-hidden=\"true\"\n                  className=\"absolute inset-0 bg-[radial-gradient(ellipse_at_25%_90%,#7c3aed_0%,transparent_50%),radial-gradient(ellipse_at_90%_10%,#71717a_0%,transparent_50%)] opacity-75\"\n                />\n                <div\n                  aria-hidden=\"true\"\n                  className=\"absolute size-32 rotate-[-25deg] rounded-[1.5rem] border border-white/30 bg-white/10 shadow-xl backdrop-blur-md sm:size-40\"\n                />\n                <div\n                  aria-hidden=\"true\"\n                  className=\"absolute size-32 translate-x-8 translate-y-5 rotate-[15deg] rounded-[1.5rem] border border-white/30 bg-white/10 shadow-xl backdrop-blur-md sm:size-40\"\n                />\n                <span className=\"relative grid size-12 place-items-center rounded-full border border-white/30 bg-white/15 text-white backdrop-blur\">\n                  <Play\n                    aria-hidden=\"true\"\n                    className=\"ml-0.5 size-4 fill-current\"\n                  />\n                </span>\n                <div className=\"absolute inset-x-3 bottom-3 flex items-center justify-between rounded bg-black/20 px-2 py-1.5 font-mono text-[9px] text-white/80\">\n                  <span>launch-film.mp4</span>\n                  <span>00:42 / 01:28</span>\n                </div>\n              </div>\n              <div className=\"mt-4 flex items-center gap-3 rounded-lg border border-zinc-200 p-3 dark:border-zinc-700\">\n                <span className=\"grid size-9 shrink-0 place-items-center rounded-lg bg-violet-50 text-violet-500 dark:bg-violet-500/10\">\n                  <FileVideo aria-hidden=\"true\" className=\"size-4\" />\n                </span>\n                <div className=\"min-w-0 flex-1\">\n                  <p className=\"truncate text-[11px] font-medium\">\n                    launch-film.mp4\n                  </p>\n                  <p className=\"mt-1 text-[10px] text-zinc-400\">\n                    MP4 · 1080p · Ready to export\n                  </p>\n                </div>\n                <span className=\"grid size-5 shrink-0 place-items-center rounded-full bg-emerald-50 text-emerald-600 dark:bg-emerald-500/10\">\n                  <Check aria-hidden=\"true\" className=\"size-3\" />\n                </span>\n              </div>\n              <div className=\"mt-3 grid grid-cols-3 gap-2 text-center text-[10px] text-zinc-500 dark:text-zinc-400\">\n                <span className=\"flex items-center justify-center gap-1.5 rounded border border-zinc-100 py-2 dark:border-zinc-800\">\n                  <Film aria-hidden=\"true\" className=\"size-3\" />\n                  Prepare\n                </span>\n                <span className=\"flex items-center justify-center gap-1.5 rounded border border-zinc-100 py-2 dark:border-zinc-800\">\n                  <Sparkles aria-hidden=\"true\" className=\"size-3\" />\n                  Organize\n                </span>\n                <span className=\"flex items-center justify-center gap-1.5 rounded border border-zinc-100 py-2 dark:border-zinc-800\">\n                  <CheckCheck aria-hidden=\"true\" className=\"size-3\" />\n                  Export\n                </span>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "content/components/marketing/logo-cloud-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-landing/shared/content/components/marketing/logo-cloud-01.tsx",
          "content": "import { Braces, Clapperboard, FileVideo, Film, Video } from \"lucide-react\";\n\nexport type LogoCloud01Props = {\n  eyebrow?: string;\n  formats?: { name: string; detail: string }[];\n};\n\n/** Format compatibility strip; it does not imply customer endorsements. */\nexport default function LogoCloud01({\n  eyebrow = \"Familiar formats. One clear workflow.\",\n  formats = [\n    { name: \"MP4\", detail: \"Video input\" },\n    { name: \"MOV\", detail: \"Video input\" },\n    { name: \"WebM\", detail: \"Video input\" },\n    { name: \"MKV\", detail: \"Video input\" },\n    { name: \"SVG\", detail: \"Demo preview\" },\n  ],\n}: LogoCloud01Props) {\n  const icons = [Film, Clapperboard, Video, FileVideo, Braces];\n  return (\n    <section\n      aria-label=\"Demo input and preview formats\"\n      className=\"w-full border-y border-zinc-200/70 bg-zinc-50/70 py-9 text-zinc-950 dark:border-zinc-800 dark:bg-zinc-900/30 dark:text-white\"\n    >\n      <div className=\"mx-auto max-w-6xl px-6 lg:px-8\">\n        <p className=\"text-center text-xs text-zinc-400\">{eyebrow}</p>\n        <div className=\"mt-7 flex flex-wrap justify-center gap-x-10 gap-y-6 sm:justify-between\">\n          {formats.map((format, index) => {\n            const Icon = icons[index % icons.length];\n            return (\n              <div className=\"flex items-center gap-3\" key={format.name}>\n                <Icon aria-hidden=\"true\" className=\"size-5 text-zinc-400\" />\n                <div>\n                  <p className=\"text-sm font-medium text-zinc-600 dark:text-zinc-300\">\n                    {format.name}\n                  </p>\n                  <p className=\"mt-0.5 font-mono text-[9px] tracking-wide text-zinc-400\">\n                    {format.detail}\n                  </p>\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "content/components/marketing/navigation-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-landing/shared/content/components/marketing/navigation-01.tsx",
          "content": "\"use client\";\n\nimport { ArrowUpRight, Layers2, Menu, X } from \"lucide-react\";\nimport { useId, useState } from \"react\";\n\nexport type Navigation01Props = {\n  brand?: string;\n  homeHref?: string;\n  links?: { label: string; href: string }[];\n  actionLabel?: string;\n  actionHref?: string;\n};\n\n/** A responsive product navigation. Supply real section URLs through links. */\nexport default function Navigation01({\n  brand = \"Frame\",\n  homeHref = \"/preview/recipes/tool-landing#frame-top\",\n  links = [\n    { label: \"Features\", href: \"/preview/recipes/tool-landing#features\" },\n    { label: \"Pricing\", href: \"/preview/recipes/tool-landing#pricing\" },\n    { label: \"Questions\", href: \"/preview/recipes/tool-landing#faq\" },\n  ],\n  actionLabel = \"Explore the demo\",\n  actionHref = \"/preview/recipes/tool-workspace\",\n}: Navigation01Props) {\n  const [open, setOpen] = useState(false);\n  const menuId = useId();\n\n  return (\n    <header\n      className=\"w-full border-b border-zinc-200/80 bg-white text-zinc-950 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n      id=\"frame-top\"\n    >\n      <div className=\"mx-auto flex h-20 max-w-6xl items-center justify-between gap-6 px-6 lg:px-8\">\n        <a\n          className=\"inline-flex items-center gap-2.5 rounded-md text-xl font-semibold tracking-tight outline-offset-4 focus-visible:outline-2 focus-visible:outline-violet-500\"\n          href={homeHref}\n        >\n          <span className=\"grid size-8 place-items-center rounded-lg bg-violet-600 text-white\">\n            <Layers2 aria-hidden=\"true\" className=\"size-4.5\" />\n          </span>\n          {brand}\n        </a>\n        <nav\n          aria-label=\"Product navigation\"\n          className=\"hidden items-center gap-8 md:flex\"\n        >\n          {links.map((link) => (\n            <a\n              className=\"rounded text-sm text-zinc-500 transition-colors hover:text-zinc-950 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-zinc-400 dark:hover:text-white\"\n              href={link.href}\n              key={link.href}\n            >\n              {link.label}\n            </a>\n          ))}\n        </nav>\n        <div className=\"flex items-center gap-3\">\n          <a\n            className=\"hidden items-center gap-2 rounded-lg bg-zinc-950 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-zinc-800 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-violet-500 sm:inline-flex dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200\"\n            href={actionHref}\n          >\n            {actionLabel}\n            <ArrowUpRight aria-hidden=\"true\" className=\"size-4\" />\n          </a>\n          <button\n            aria-controls={menuId}\n            aria-expanded={open}\n            aria-label={open ? \"Close product menu\" : \"Open product menu\"}\n            className=\"grid size-10 place-items-center rounded-lg border border-zinc-200 hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-violet-500 md:hidden dark:border-zinc-700 dark:hover:bg-zinc-900\"\n            onClick={() => setOpen(!open)}\n            type=\"button\"\n          >\n            {open ? (\n              <X aria-hidden=\"true\" className=\"size-5\" />\n            ) : (\n              <Menu aria-hidden=\"true\" className=\"size-5\" />\n            )}\n          </button>\n        </div>\n      </div>\n      {open && (\n        <nav\n          aria-label=\"Mobile product navigation\"\n          className=\"border-t border-zinc-200 px-6 py-4 md:hidden dark:border-zinc-800\"\n          id={menuId}\n        >\n          {links.map((link) => (\n            <a\n              className=\"block rounded-md px-2 py-3 text-sm text-zinc-600 hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-violet-500 dark:text-zinc-300 dark:hover:bg-zinc-900\"\n              href={link.href}\n              key={link.href}\n              onClick={() => setOpen(false)}\n            >\n              {link.label}\n            </a>\n          ))}\n          <a\n            className=\"mt-2 inline-flex items-center gap-2 rounded-md px-2 py-3 text-sm font-semibold text-violet-600 dark:text-violet-400\"\n            href={actionHref}\n            onClick={() => setOpen(false)}\n          >\n            {actionLabel}\n            <ArrowUpRight aria-hidden=\"true\" className=\"size-4\" />\n          </a>\n        </nav>\n      )}\n    </header>\n  );\n}\n"
        },
        {
          "path": "content/components/marketing/pricing-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-landing/shared/content/components/marketing/pricing-01.tsx",
          "content": "\"use client\";\n\nimport { ArrowRight, Check } from \"lucide-react\";\nimport { useState } from \"react\";\n\nexport type PricingPlan = {\n  name: string;\n  description: string;\n  monthlyPrice: number;\n  annualMonthlyPrice: number;\n  features: string[];\n  highlighted?: boolean;\n  actionLabel?: string;\n  actionHref?: string;\n};\n\nexport type Pricing01Props = {\n  eyebrow?: string;\n  title?: string;\n  description?: string;\n  plans?: PricingPlan[];\n  currency?: string;\n};\n\n/** Illustrative plan comparison with a working billing-period toggle. */\nexport default function Pricing01({\n  eyebrow = \"ROOM TO GROW\",\n  title = \"Start small. Keep creating.\",\n  description = \"A simple plan comparison for your next product. All prices below are illustrative.\",\n  currency = \"$\",\n  plans = [\n    {\n      name: \"Personal\",\n      description: \"For the occasional good idea.\",\n      monthlyPrice: 0,\n      annualMonthlyPrice: 0,\n      features: [\n        \"3 projects\",\n        \"Single-file workflow\",\n        \"Standard exports\",\n        \"Local demo workspace\",\n      ],\n      actionLabel: \"Explore the demo\",\n    },\n    {\n      name: \"Creator\",\n      description: \"For a workflow that keeps moving.\",\n      monthlyPrice: 19,\n      annualMonthlyPrice: 15,\n      features: [\n        \"Unlimited projects\",\n        \"Batch file workflows\",\n        \"Reusable presets\",\n        \"Export history\",\n      ],\n      highlighted: true,\n      actionLabel: \"Explore Creator\",\n    },\n    {\n      name: \"Studio\",\n      description: \"For a little more room to make.\",\n      monthlyPrice: 39,\n      annualMonthlyPrice: 32,\n      features: [\n        \"Everything in Creator\",\n        \"Shared project folders\",\n        \"Team-ready layouts\",\n        \"Usage overview\",\n      ],\n      actionLabel: \"Explore Studio\",\n    },\n  ],\n}: Pricing01Props) {\n  const [period, setPeriod] = useState<\"monthly\" | \"yearly\">(\"monthly\");\n  return (\n    <section\n      className=\"w-full scroll-mt-10 border-y border-zinc-200/70 bg-zinc-50/70 py-20 text-zinc-950 md:py-24 dark:border-zinc-800 dark:bg-zinc-900/30 dark:text-white\"\n      id=\"pricing\"\n    >\n      <div className=\"mx-auto max-w-6xl px-6 lg:px-8\">\n        <div className=\"flex flex-col items-start justify-between gap-6 sm:flex-row sm:items-end\">\n          <div className=\"max-w-lg\">\n            <p className=\"text-[10px] font-semibold tracking-[0.16em] text-violet-600 dark:text-violet-400\">\n              {eyebrow}\n            </p>\n            <h2 className=\"mt-4 text-balance text-3xl font-semibold tracking-[-0.035em] sm:text-4xl\">\n              {title}\n            </h2>\n            <p className=\"mt-4 text-sm leading-6 text-zinc-500 dark:text-zinc-400\">\n              {description}\n            </p>\n          </div>\n          <div\n            aria-label=\"Billing period\"\n            className=\"inline-flex shrink-0 gap-1 rounded-lg border border-zinc-200 bg-white p-1 text-xs dark:border-zinc-700 dark:bg-zinc-950\"\n            role=\"group\"\n          >\n            {([\"monthly\", \"yearly\"] as const).map((value) => (\n              <button\n                aria-pressed={period === value}\n                className={`rounded-md px-4 py-2 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-violet-500 ${period === value ? \"bg-zinc-950 text-white dark:bg-white dark:text-zinc-950\" : \"text-zinc-500 hover:text-zinc-950 dark:text-zinc-400 dark:hover:text-white\"}`}\n                key={value}\n                onClick={() => setPeriod(value)}\n                type=\"button\"\n              >\n                {value === \"monthly\" ? \"Monthly\" : \"Yearly\"}\n              </button>\n            ))}\n          </div>\n        </div>\n        <div className=\"mt-10 grid gap-4 md:grid-cols-3\">\n          {plans.map((plan) => {\n            const price =\n              period === \"monthly\"\n                ? plan.monthlyPrice\n                : plan.annualMonthlyPrice;\n            return (\n              <article\n                className={`relative flex flex-col rounded-xl border bg-white p-6 lg:p-7 dark:bg-zinc-950 ${plan.highlighted ? \"border-violet-500 ring-1 ring-violet-500/10 dark:border-violet-400\" : \"border-zinc-200 dark:border-zinc-700\"}`}\n                key={plan.name}\n              >\n                <div className=\"flex min-h-6 flex-wrap items-center justify-between gap-2\">\n                  <h3 className=\"text-sm font-semibold\">{plan.name}</h3>\n                  {plan.highlighted && (\n                    <span className=\"rounded-full bg-violet-50 px-2 py-1 text-[9px] font-semibold text-violet-600 dark:bg-violet-500/10 dark:text-violet-400\">\n                      FOR EVERYDAY CREATING\n                    </span>\n                  )}\n                </div>\n                <p className=\"mt-2 min-h-10 text-xs leading-5 text-zinc-500 dark:text-zinc-400\">\n                  {plan.description}\n                </p>\n                <p\n                  aria-live=\"polite\"\n                  className=\"mt-5 flex items-baseline gap-1\"\n                >\n                  <span className=\"text-4xl font-semibold tracking-tight\">\n                    {currency}\n                    {price}\n                  </span>\n                  <span className=\"text-xs text-zinc-400\">/ month</span>\n                </p>\n                <p className=\"mt-2 min-h-5 text-[10px] text-zinc-400\">\n                  {price === 0\n                    ? \"A place to try things out\"\n                    : period === \"yearly\"\n                      ? `${currency}${price * 12} billed yearly`\n                      : \"Billed monthly\"}\n                </p>\n                <a\n                  className={`mt-6 inline-flex min-h-10 items-center justify-center gap-2 rounded-lg border px-3 py-2.5 text-xs font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-violet-500 ${plan.highlighted ? \"border-violet-600 bg-violet-600 text-white hover:bg-violet-700\" : \"border-zinc-200 text-zinc-700 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-900\"}`}\n                  href={plan.actionHref ?? \"/preview/recipes/tool-workspace\"}\n                >\n                  {plan.actionLabel ?? `Explore ${plan.name}`}\n                  <ArrowRight aria-hidden=\"true\" className=\"size-3.5\" />\n                </a>\n                <ul className=\"mt-7 space-y-3 border-t border-zinc-100 pt-6 dark:border-zinc-800\">\n                  {plan.features.map((feature) => (\n                    <li\n                      className=\"flex items-start gap-2.5 text-xs leading-5 text-zinc-600 dark:text-zinc-400\"\n                      key={feature}\n                    >\n                      <Check\n                        aria-hidden=\"true\"\n                        className=\"mt-0.5 size-3.5 shrink-0 text-violet-500\"\n                      />\n                      {feature}\n                    </li>\n                  ))}\n                </ul>\n              </article>\n            );\n          })}\n        </div>\n        <p\n          className=\"mt-6 scroll-mt-8 text-center text-[11px] leading-5 text-zinc-400\"\n          id=\"pricing-demo\"\n        >\n          This is an interactive UI demo. No subscriptions or payments are\n          created.\n        </p>\n      </div>\n    </section>\n  );\n}\n"
        }
      ],
      "categories": [
        "recipe",
        "marketing",
        "clean-saas"
      ],
      "docs": "Install this one item to receive the complete AI tool landing page composition, its 7 selected blocks, required local helpers, and MIT notices. Import ToolLandingRecipe as the default export from @components/uiblocks/recipe-tool-landing/recipe-tool-landing.tsx; read meta.usage and meta.propsContract. It uses ordinary React 19 and Tailwind CSS 4, with no router dependency and no application route overwrite. Change host links and real product copy through typed props. All sample prices, product claims, and Frame branding are illustrative; replace them with verified product information. See https://uiblocks.org/recipes/tool-landing.",
      "meta": {
        "kind": "recipe",
        "recipeId": "tool-landing",
        "blockIds": [
          "navigation-01",
          "hero-01",
          "logo-cloud-01",
          "features-01",
          "pricing-01",
          "faq-01",
          "footer-01"
        ],
        "componentName": "ToolLandingRecipe",
        "propsContract": [
          {
            "name": "brand, homeHref, primaryHref, primaryLabel",
            "description": "Set the shared product identity and primary action across navigation, hero, plans, FAQ, and footer; ordinary anchors use the host's URLs."
          },
          {
            "name": "navigation, hero, formats, features, pricing, faq, footer",
            "description": "Pass each section's typed props for real copy, links, capabilities, plan prices, and FAQs. Pricing plans without an actionHref inherit primaryHref."
          },
          {
            "name": "theme, className",
            "description": "Inherit the host theme by default, or set theme='dark' to enable a class-based dark variant. Use the host Tailwind 4 theme and className to customize the visual system."
          }
        ],
        "usage": "import ToolLandingRecipe from \"@/components/uiblocks/recipe-tool-landing/recipe-tool-landing\";\n\nexport default function ProductPage() {\n  return (\n    <ToolLandingRecipe\n      brand=\"Your product\"\n      primaryHref=\"/app\"\n      primaryLabel=\"Open workspace\"\n      hero={{ title: \"A clearer starting point for your next project.\", description: \"Replace this with your actual product description.\" }}\n    />\n  );\n}",
        "includesBackend": false,
        "includedSources": [
          {
            "id": "navigation-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "hero-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "logo-cloud-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "features-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "pricing-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "faq-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "footer-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          }
        ],
        "version": "0.3.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org/recipes/tool-landing",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.3.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "marketing",
        "featured": false,
        "tags": [
          "complete page",
          "recipe",
          "整页方案",
          "tool-landing"
        ],
        "contentGuidelines": [
          "Keep the hero title around 8–12 English words or 14–24 Chinese characters; choose a shorter title if it wraps beyond three lines.",
          "Use one primary action across the navigation and hero. Connect the action to your real workspace or onboarding route.",
          "Keep the 1152px content width, violet accent, neutral borders, and shared section spacing throughout the page.",
          "The format strip describes supported formats, not customer endorsements. Replace it with verified capabilities only.",
          "Use three concise feature stories, three plans, and around four FAQs. Check longer content on mobile.",
          "Frame is a fictional demonstration product. Replace all plan details, demo notices, and claims with your own verified product information before launch."
        ],
        "recommendedWith": [
          "navigation-01",
          "hero-01",
          "logo-cloud-01",
          "features-01",
          "pricing-01",
          "faq-01",
          "footer-01"
        ],
        "entry": "@components/uiblocks/recipe-tool-landing/recipe-tool-landing.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/recipes/tool-landing"
      }
    },
    {
      "name": "recipe-tool-workspace",
      "type": "registry:block",
      "title": "File processing workspace",
      "description": "A focused media workspace with local file selection, output controls, a filterable task queue, downloadable demo previews, and a clear usage summary.",
      "author": "UIBlocks contributors",
      "registryDependencies": [],
      "dependencies": [
        "lucide-react@0.577.0"
      ],
      "files": [
        {
          "path": "licenses/recipe-tool-workspace/LICENSE.txt",
          "type": "registry:file",
          "target": "@components/uiblocks/recipe-tool-workspace/LICENSE.txt",
          "content": "UIBlocks original page recipe: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
        },
        {
          "path": "content/recipe-components/tool-workspace.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-workspace/recipe-tool-workspace.tsx",
          "content": "\"use client\";\n\nimport {\n  CheckCircle2,\n  Circle,\n  CircleAlert,\n  FlaskConical,\n  LoaderCircle,\n  RotateCcw,\n} from \"lucide-react\";\nimport {\n  useEffect,\n  useState,\n  type ReactNode,\n  type ComponentProps,\n} from \"react\";\nimport ResultsGallery01, {\n  type DemoResult,\n} from \"./shared/content/components/workspace/results-gallery-01\";\nimport TaskQueue01, {\n  type DemoTask,\n} from \"./shared/content/components/workspace/task-queue-01\";\nimport UploadWorkflow01, {\n  type WorkflowFile,\n} from \"./shared/content/components/workspace/upload-workflow-01\";\nimport UsagePanel01 from \"./shared/content/components/workspace/usage-panel-01\";\nimport WorkspaceHeader01 from \"./shared/content/components/workspace/workspace-header-01\";\n\nexport type WorkspaceStatus =\n  | \"empty\"\n  | \"ready\"\n  | \"processing\"\n  | \"completed\"\n  | \"error\";\nexport type { WorkflowFile, DemoTask, DemoResult };\nexport type WorkspaceUploadContext = {\n  files: WorkflowFile[];\n  busy: boolean;\n  onFilesChange: (files: WorkflowFile[]) => void;\n  onRun: (files: WorkflowFile[], format: string) => Promise<void>;\n};\nexport type ToolWorkspaceRecipeProps = {\n  /** Only explicit demo mode starts local timers or fabricates sample results. */\n  mode?: \"controlled\" | \"demo\";\n  theme?: \"inherit\" | \"dark\";\n  className?: string;\n  websiteHref?: string;\n  upgradeHref?: string;\n  header?: Omit<\n    NonNullable<ComponentProps<typeof WorkspaceHeader01>>,\n    \"onSearch\" | \"searchQuery\" | \"websiteHref\"\n  >;\n  upload?: Pick<\n    NonNullable<ComponentProps<typeof UploadWorkflow01>>,\n    \"title\" | \"description\"\n  >;\n  queueTitle?: string;\n  resultsTitle?: string;\n  usage?: ComponentProps<typeof UsagePanel01>;\n  files?: WorkflowFile[];\n  onFilesChange?: (files: WorkflowFile[]) => void;\n  status?: WorkspaceStatus;\n  progress?: number;\n  tasks?: DemoTask[];\n  results?: DemoResult[];\n  searchQuery?: string;\n  onSearchChange?: (query: string) => void;\n  /** Invoked only in controlled mode; the host owns network work and status updates. */\n  onRun?: (files: WorkflowFile[], format: string) => void | Promise<void>;\n  onRetry?: (id: string) => void | Promise<void>;\n  errorMessage?: string;\n  initialDemoState?: WorkspaceStatus;\n  demoFiles?: WorkflowFile[];\n  showDemoControls?: boolean;\n  /** The default selector returns metadata, not File bytes. Supply a real upload control here. */\n  renderUpload?: (context: WorkspaceUploadContext) => ReactNode;\n  /** The default gallery downloads SVG examples. Supply real result links here. */\n  renderResults?: (context: {\n    results: DemoResult[];\n    searchQuery: string;\n    status: WorkspaceStatus;\n  }) => ReactNode;\n  headerSlot?: ReactNode;\n  footer?: ReactNode;\n};\nconst exampleFiles: WorkflowFile[] = [\n  { id: \"sample-launch\", name: \"Launch-film.mp4\", size: 26_738_000 },\n  { id: \"sample-studio\", name: \"Studio-walkthrough.mov\", size: 48_124_000 },\n];\n\n/**\n * Five-block workspace composition. Controlled mode never simulates a backend.\n * The included UI blocks retain their honest demo labels; adapt these source\n * files, renderUpload/renderResults/headerSlot, and service callbacks for a\n * production product. No upload, transcoding, authentication, or billing API is included.\n */\nexport default function ToolWorkspaceRecipe({\n  mode = \"controlled\",\n  theme = \"inherit\",\n  className = \"\",\n  websiteHref = \"/\",\n  upgradeHref = \"#pricing\",\n  header,\n  upload,\n  queueTitle,\n  resultsTitle,\n  usage,\n  files: suppliedFiles,\n  onFilesChange,\n  status: suppliedStatus,\n  progress: suppliedProgress,\n  tasks: suppliedTasks,\n  results: suppliedResults,\n  searchQuery: suppliedSearch,\n  onSearchChange,\n  onRun,\n  onRetry,\n  errorMessage,\n  initialDemoState = \"completed\",\n  demoFiles = exampleFiles,\n  showDemoControls = true,\n  renderUpload,\n  renderResults,\n  headerSlot,\n  footer,\n}: ToolWorkspaceRecipeProps) {\n  const [localFiles, setLocalFiles] = useState<WorkflowFile[]>(\n    mode === \"demo\" && initialDemoState !== \"empty\" ? demoFiles : []\n  );\n  const files = suppliedFiles ?? localFiles;\n  const [demoState, setDemoState] = useState<WorkspaceStatus>(initialDemoState);\n  const state =\n    mode === \"demo\"\n      ? demoState\n      : (suppliedStatus ?? (files.length ? \"ready\" : \"empty\"));\n  const [format, setFormat] = useState(\"MP4\");\n  const [demoProgress, setDemoProgress] = useState(\n    initialDemoState === \"completed\" ? 100 : 0\n  );\n  const rawProgress = mode === \"demo\" ? demoProgress : (suppliedProgress ?? 0);\n  const progress = Number.isFinite(rawProgress)\n    ? Math.max(0, Math.min(100, rawProgress))\n    : 0;\n  const [submitting, setSubmitting] = useState(false);\n  const [serviceError, setServiceError] = useState(\"\");\n  const [localSearch, setLocalSearch] = useState(\"\");\n  const searchQuery = suppliedSearch ?? localSearch;\n  function setSearchQuery(query: string) {\n    setLocalSearch(query);\n    onSearchChange?.(query);\n  }\n\n  useEffect(() => {\n    if (mode !== \"demo\" || state !== \"processing\") return;\n    const interval = window.setInterval(\n      () => setDemoProgress((value) => Math.min(96, value + 14)),\n      350\n    );\n    const timeout = window.setTimeout(() => {\n      setDemoProgress(100);\n      setDemoState(\"completed\");\n    }, 2400);\n    return () => {\n      window.clearInterval(interval);\n      window.clearTimeout(timeout);\n    };\n  }, [mode, state]);\n\n  const tasks: DemoTask[] =\n    suppliedTasks?.map((task) =>\n      task.status === \"processing\" && task.progress === undefined\n        ? { ...task, progress }\n        : task\n    ) ??\n    (mode === \"demo\"\n      ? files.map((file, index) => ({\n          id: file.id,\n          name: file.name,\n          detail: `${format} · Demo workflow`,\n          status:\n            state === \"completed\"\n              ? \"completed\"\n              : state === \"error\" && index === 0\n                ? \"error\"\n                : state === \"processing\" && index === 0\n                  ? \"processing\"\n                  : \"queued\",\n          progress:\n            state === \"processing\" && index === 0 ? progress : undefined,\n        }))\n      : []);\n  const results: DemoResult[] =\n    suppliedResults ??\n    (mode === \"demo\" && state === \"completed\"\n      ? files.map((file, index) => ({\n          id: file.id,\n          name: file.name.replace(/\\.[^.]+$/, \"\"),\n          format,\n          detail: \"SVG demo preview · No video conversion\",\n          tone: ([\"violet\", \"mint\", \"peach\"] as const)[index % 3],\n        }))\n      : []);\n\n  function changeFiles(nextFiles: WorkflowFile[]) {\n    setLocalFiles(nextFiles);\n    if (mode === \"demo\") {\n      setDemoProgress(0);\n      setDemoState(nextFiles.length ? \"ready\" : \"empty\");\n    } else onFilesChange?.(nextFiles);\n  }\n\n  async function startWorkflow(nextFiles: WorkflowFile[], nextFormat: string) {\n    if (!nextFiles.length || submitting) return;\n    setFormat(nextFormat);\n    setServiceError(\"\");\n    if (mode === \"demo\") {\n      setLocalFiles(nextFiles);\n      setDemoProgress(8);\n      setDemoState(\"processing\");\n      return;\n    }\n    if (!onRun) {\n      setServiceError(\n        \"This workflow is not connected to a processing service yet.\"\n      );\n      return;\n    }\n    setSubmitting(true);\n    try {\n      await onRun(nextFiles, nextFormat);\n    } catch {\n      setServiceError(\"The workflow could not start. Please try again.\");\n    } finally {\n      setSubmitting(false);\n    }\n  }\n\n  async function retryTask(id: string) {\n    if (submitting) return;\n    if (mode === \"demo\") return startWorkflow(files, format);\n    if (!onRetry) {\n      if (onRun && files.length) return startWorkflow(files, format);\n      setServiceError(\"This task is not connected to a retry action yet.\");\n      return;\n    }\n    setServiceError(\"\");\n    setSubmitting(true);\n    try {\n      await onRetry(id);\n    } catch {\n      setServiceError(\"The task could not restart. Please try again.\");\n    } finally {\n      setSubmitting(false);\n    }\n  }\n  const uploadContext: WorkspaceUploadContext = {\n    files,\n    busy: state === \"processing\" || submitting,\n    onFilesChange: changeFiles,\n    onRun: startWorkflow,\n  };\n\n  function changeDemoState(nextState: WorkspaceStatus) {\n    if (nextState === \"empty\") {\n      setLocalFiles([]);\n      setSearchQuery(\"\");\n    } else if (!files.length) {\n      setLocalFiles(demoFiles);\n    }\n    setDemoProgress(\n      nextState === \"processing\" ? 8 : nextState === \"completed\" ? 100 : 0\n    );\n    setDemoState(nextState);\n  }\n\n  const states = [\n    { value: \"empty\", label: \"Empty\", icon: Circle },\n    { value: \"processing\", label: \"Processing\", icon: LoaderCircle },\n    { value: \"completed\", label: \"Complete\", icon: CheckCircle2 },\n    { value: \"error\", label: \"Error\", icon: CircleAlert },\n  ] as const;\n\n  return (\n    <div\n      className={`${theme === \"inherit\" ? \"\" : theme} min-h-screen bg-zinc-50 text-zinc-950 dark:bg-zinc-900/50 dark:text-zinc-50 ${className}`}\n    >\n      {headerSlot ?? (\n        <WorkspaceHeader01\n          {...header}\n          onSearch={setSearchQuery}\n          searchQuery={searchQuery}\n          websiteHref={websiteHref}\n        />\n      )}\n      <main className=\"mx-auto max-w-7xl px-5 pb-12 sm:px-8\">\n        {mode === \"demo\" && showDemoControls && (\n          <div className=\"mb-6 flex flex-col justify-between gap-3 rounded-xl border border-zinc-200 bg-white px-4 py-3 sm:flex-row sm:items-center dark:border-zinc-800 dark:bg-zinc-950\">\n            <div className=\"flex items-center gap-2.5\">\n              <FlaskConical\n                aria-hidden=\"true\"\n                className=\"size-4 shrink-0 text-violet-500\"\n              />\n              <p className=\"text-[11px] leading-5 text-zinc-500 dark:text-zinc-400\">\n                <span className=\"font-medium text-zinc-700 dark:text-zinc-200\">\n                  Try every state.\n                </span>{\" \"}\n                Sample files are illustrative. Nothing is uploaded or converted.\n              </p>\n            </div>\n            <div\n              aria-label=\"Choose a demo state\"\n              className=\"flex flex-wrap gap-1\"\n              role=\"group\"\n            >\n              {states.map(({ value, label, icon: Icon }) => (\n                <button\n                  aria-pressed={state === value}\n                  className={`inline-flex min-h-8 items-center gap-1.5 rounded-md px-2.5 text-[10px] font-medium transition-colors focus-visible:outline-2 focus-visible:outline-violet-500 ${state === value ? \"bg-violet-50 text-violet-600 dark:bg-violet-950/50 dark:text-violet-300\" : \"text-zinc-400 hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-200\"}`}\n                  key={value}\n                  onClick={() => changeDemoState(value)}\n                  type=\"button\"\n                >\n                  <Icon\n                    aria-hidden=\"true\"\n                    className={`size-3 ${state === value && value === \"processing\" ? \"animate-spin\" : \"\"}`}\n                  />\n                  {label}\n                </button>\n              ))}\n            </div>\n          </div>\n        )}\n        {(state === \"error\" || serviceError) && (\n          <div\n            className=\"mb-6 flex flex-wrap items-center gap-3 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-red-700 dark:border-red-900 dark:bg-red-950/20 dark:text-red-300\"\n            role=\"alert\"\n          >\n            <CircleAlert aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n            <p className=\"flex-1 text-xs leading-5\">\n              {errorMessage ??\n                (serviceError ||\n                  (mode === \"demo\"\n                    ? \"Demo error: this task needs another try. Your selected file details are still here.\"\n                    : \"This task needs another try. Your selected file details are still here.\"))}\n            </p>\n            {(mode === \"demo\" || onRetry || onRun) && (\n              <button\n                className=\"inline-flex min-h-8 items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 text-[11px] font-medium hover:bg-red-100 dark:border-red-800 dark:bg-red-950/30 dark:hover:bg-red-950\"\n                disabled={submitting}\n                onClick={() =>\n                  void retryTask(\n                    tasks.find((task) => task.status === \"error\")?.id ??\n                      files[0]?.id ??\n                      \"\"\n                  )\n                }\n                type=\"button\"\n              >\n                <RotateCcw aria-hidden=\"true\" className=\"size-3\" />{\" \"}\n                {mode === \"demo\" ? \"Retry demo\" : \"Retry\"}\n              </button>\n            )}\n          </div>\n        )}\n        <div className=\"grid grid-cols-1 items-start gap-6 md:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] xl:grid-cols-[320px_minmax(0,1fr)_250px]\">\n          {renderUpload ? (\n            renderUpload(uploadContext)\n          ) : (\n            <UploadWorkflow01\n              {...upload}\n              busy={uploadContext.busy}\n              files={files}\n              onFilesChange={changeFiles}\n              onRun={(nextFiles, nextFormat) =>\n                void startWorkflow(nextFiles, nextFormat)\n              }\n            />\n          )}\n          <div className=\"min-w-0 space-y-6\">\n            <TaskQueue01\n              onRetry={(id) => void retryTask(id)}\n              title={queueTitle}\n              searchQuery={searchQuery}\n              tasks={tasks}\n            />\n            {renderResults ? (\n              renderResults({ results, searchQuery, status: state })\n            ) : (\n              <ResultsGallery01\n                title={resultsTitle}\n                results={results}\n                searchQuery={searchQuery}\n              />\n            )}\n          </div>\n          <div className=\"md:col-span-2 xl:col-span-1\">\n            <UsagePanel01\n              planName={mode === \"demo\" ? \"Studio demo\" : \"Usage\"}\n              upgradeHref={upgradeHref}\n              used={\n                mode === \"demo\"\n                  ? 38 + (state === \"completed\" ? files.length : 0)\n                  : 0\n              }\n              {...usage}\n            />\n          </div>\n        </div>\n        {footer ??\n          (mode === \"demo\" && (\n            <p className=\"mt-7 text-center text-[10px] leading-5 text-zinc-400\">\n              Frame is a fictional product. This UIBlocks recipe demonstrates\n              local interface behavior, not a hosted processing service.\n            </p>\n          ))}\n      </main>\n    </div>\n  );\n}\n"
        },
        {
          "path": "content/components/workspace/results-gallery-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-workspace/shared/content/components/workspace/results-gallery-01.tsx",
          "content": "\"use client\";\n\nimport {\n  ArrowDownToLine,\n  Check,\n  FileImage,\n  ImagePlus,\n  Maximize2,\n  X,\n} from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\n\nexport type DemoResult = {\n  id: string;\n  name: string;\n  format: string;\n  detail: string;\n  tone?: \"violet\" | \"mint\" | \"peach\";\n};\nexport type ResultsGallery01Props = {\n  title?: string;\n  results?: DemoResult[];\n  searchQuery?: string;\n};\nconst sampleResults: DemoResult[] = [\n  {\n    id: \"result-1\",\n    name: \"Autumn campaign\",\n    format: \"MP4\",\n    detail: \"Sample output · SVG preview\",\n    tone: \"violet\",\n  },\n  {\n    id: \"result-2\",\n    name: \"Product walkthrough\",\n    format: \"WebM\",\n    detail: \"Sample output · SVG preview\",\n    tone: \"mint\",\n  },\n];\n\nfunction escapeXml(value: string) {\n  return value.replace(\n    /[<>&\"']/g,\n    (character) =>\n      ({\n        \"<\": \"&lt;\",\n        \">\": \"&gt;\",\n        \"&\": \"&amp;\",\n        '\"': \"&quot;\",\n        \"'\": \"&apos;\",\n      })[character] ?? character\n  );\n}\n\n/** Downloadable SVG preview cards. No video conversion is implied. */\nexport default function ResultsGallery01({\n  title = \"Ready for your next chapter.\",\n  results = sampleResults,\n  searchQuery = \"\",\n}: ResultsGallery01Props = {}) {\n  const [selected, setSelected] = useState<DemoResult | null>(null);\n  const [downloaded, setDownloaded] = useState<string | null>(null);\n  const id = useId();\n  const dialogRef = useRef<HTMLDialogElement>(null);\n  useEffect(() => {\n    const dialog = dialogRef.current;\n    if (!dialog) return;\n    if (selected && !dialog.open) dialog.showModal();\n    else if (!selected && dialog.open) dialog.close();\n  }, [selected]);\n  const visible = results.filter((result) =>\n    result.name.toLowerCase().includes(searchQuery.toLowerCase())\n  );\n\n  function download(result: DemoResult) {\n    const colors = { violet: \"#ede9fe\", mint: \"#d1fae5\", peach: \"#ffedd5\" };\n    const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"750\" viewBox=\"0 0 1200 750\"><rect width=\"1200\" height=\"750\" fill=\"${colors[result.tone ?? \"violet\"]}\"/><rect x=\"120\" y=\"100\" width=\"960\" height=\"550\" rx=\"32\" fill=\"#fff\"/><text x=\"180\" y=\"200\" font-family=\"system-ui,sans-serif\" font-size=\"24\" fill=\"#7c3aed\">FRAME / WORKFLOW DEMO</text><text x=\"180\" y=\"310\" font-family=\"system-ui,sans-serif\" font-size=\"38\" font-weight=\"600\" fill=\"#18181b\">${escapeXml(result.name.slice(0, 45))}</text><text x=\"180\" y=\"375\" font-family=\"system-ui,sans-serif\" font-size=\"20\" fill=\"#71717a\">Selected output: ${escapeXml(result.format)}</text><text x=\"180\" y=\"520\" font-family=\"system-ui,sans-serif\" font-size=\"18\" fill=\"#71717a\">This is an SVG demo preview. No video was uploaded or converted.</text></svg>`;\n    const url = URL.createObjectURL(new Blob([svg], { type: \"image/svg+xml\" }));\n    const anchor = document.createElement(\"a\");\n    anchor.href = url;\n    anchor.download = `${result.name.replace(/[^a-z0-9-]/gi, \"-\").toLowerCase()}-demo.svg`;\n    anchor.click();\n    window.setTimeout(() => URL.revokeObjectURL(url), 1000);\n    setDownloaded(result.id);\n  }\n\n  function artwork(result: DemoResult, expanded = false) {\n    const tones = {\n      violet: \"bg-violet-100 dark:bg-violet-950/60\",\n      mint: \"bg-emerald-100 dark:bg-emerald-950/60\",\n      peach: \"bg-orange-100 dark:bg-orange-950/60\",\n    };\n    return (\n      <div\n        className={`relative flex aspect-[16/10] items-center justify-center overflow-hidden ${tones[result.tone ?? \"violet\"]} ${expanded ? \"rounded-xl\" : \"\"}`}\n      >\n        <div\n          className=\"absolute inset-0 opacity-30\"\n          style={{\n            backgroundImage:\n              \"radial-gradient(currentColor 0.7px, transparent 0.7px)\",\n            backgroundSize: \"12px 12px\",\n            color: \"#a1a1aa\",\n          }}\n        />\n        <div className=\"relative w-[68%] -rotate-6 rounded-lg border border-white/80 bg-white p-[8%] shadow-lg shadow-zinc-800/5 dark:border-zinc-700 dark:bg-zinc-900\">\n          <span className=\"text-[8px] font-medium uppercase tracking-[0.18em] text-zinc-400\">\n            FRAME STUDIO\n          </span>\n          <div className=\"mt-3 space-y-1.5\">\n            <div className=\"h-2.5 w-4/5 rounded bg-zinc-800 dark:bg-zinc-200\" />\n            <div className=\"h-2.5 w-3/5 rounded bg-zinc-800 dark:bg-zinc-200\" />\n          </div>\n          <div className=\"mt-4 flex gap-1\">\n            <span className=\"h-1 w-3 rounded bg-violet-400\" />\n            <span className=\"h-1 w-7 rounded bg-zinc-200 dark:bg-zinc-700\" />\n          </div>\n        </div>\n        <span className=\"absolute right-2.5 bottom-2 rounded-md bg-white/80 px-1.5 py-0.5 text-[8px] font-medium text-zinc-600 backdrop-blur dark:bg-zinc-900/80 dark:text-zinc-300\">\n          SVG demo\n        </span>\n      </div>\n    );\n  }\n\n  return (\n    <section\n      aria-labelledby={id}\n      className=\"w-full rounded-2xl border border-zinc-200 bg-white p-5 text-zinc-950 shadow-sm shadow-zinc-950/[0.02] sm:p-6 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n    >\n      <div className=\"flex items-center justify-between gap-3\">\n        <div>\n          <h2 className=\"text-sm font-semibold\" id={id}>\n            {title}\n          </h2>\n          <p className=\"mt-1 text-[11px] text-zinc-400\">\n            Preview cards you can actually download.\n          </p>\n        </div>\n        <span className=\"grid size-8 place-items-center rounded-lg border border-zinc-100 text-zinc-400 dark:border-zinc-800\">\n          <FileImage aria-hidden=\"true\" className=\"size-4\" />\n        </span>\n      </div>\n      {visible.length ? (\n        <div className=\"mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n          {visible.map((result) => (\n            <article\n              className=\"overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800\"\n              key={result.id}\n            >\n              <button\n                aria-label={`Preview ${result.name}`}\n                className=\"group relative block w-full text-left focus-visible:outline-2 focus-visible:outline-violet-500\"\n                onClick={() => setSelected(result)}\n                type=\"button\"\n              >\n                {artwork(result)}\n                <span className=\"absolute top-2.5 right-2.5 grid size-7 place-items-center rounded-lg bg-white/80 text-zinc-500 opacity-100 transition-opacity sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-visible:opacity-100 dark:bg-zinc-900/80\">\n                  <Maximize2 aria-hidden=\"true\" className=\"size-3\" />\n                </span>\n              </button>\n              <div className=\"flex items-center gap-2 p-3\">\n                <div className=\"min-w-0 flex-1\">\n                  <h3 className=\"truncate text-xs font-medium\">\n                    {result.name}\n                  </h3>\n                  <p className=\"mt-1 text-[9px] text-zinc-400\">\n                    {result.detail}\n                  </p>\n                </div>\n                <button\n                  aria-label={`Download SVG demo for ${result.name}`}\n                  className=\"grid size-8 shrink-0 place-items-center rounded-lg border border-zinc-200 text-zinc-500 transition-colors hover:border-violet-300 hover:bg-violet-50 hover:text-violet-600 focus-visible:outline-2 focus-visible:outline-violet-500 dark:border-zinc-700 dark:hover:bg-violet-950/40 dark:hover:text-violet-300\"\n                  onClick={() => download(result)}\n                  type=\"button\"\n                >\n                  {downloaded === result.id ? (\n                    <Check\n                      aria-hidden=\"true\"\n                      className=\"size-3.5 text-emerald-500\"\n                    />\n                  ) : (\n                    <ArrowDownToLine aria-hidden=\"true\" className=\"size-3.5\" />\n                  )}\n                </button>\n              </div>\n            </article>\n          ))}\n        </div>\n      ) : (\n        <div className=\"flex min-h-48 flex-col items-center justify-center rounded-xl border border-dashed border-zinc-200 p-6 text-center dark:border-zinc-800\">\n          <ImagePlus\n            aria-hidden=\"true\"\n            className=\"size-7 text-zinc-300 dark:text-zinc-600\"\n          />\n          <p className=\"mt-3 text-xs font-medium\">\n            {searchQuery ? \"No matching results\" : \"Room for your next idea.\"}\n          </p>\n          <p className=\"mt-1 max-w-56 text-[11px] leading-5 text-zinc-400\">\n            {searchQuery\n              ? \"Try another file name.\"\n              : \"Complete a demo workflow to see your preview cards here.\"}\n          </p>\n        </div>\n      )}\n      {downloaded && (\n        <p\n          className=\"mt-3 text-[10px] text-emerald-600 dark:text-emerald-400\"\n          role=\"status\"\n        >\n          SVG demo preview downloaded. Your source video was not converted.\n        </p>\n      )}\n      <dialog\n        aria-label={\n          selected ? `${selected.name} demo preview` : \"Result preview\"\n        }\n        className=\"fixed inset-0 m-0 h-dvh max-h-none w-screen max-w-none items-center justify-center bg-transparent p-5 text-zinc-950 open:flex backdrop:bg-zinc-950/50 backdrop:backdrop-blur-sm dark:text-zinc-50\"\n        onCancel={(event) => {\n          event.preventDefault();\n          setSelected(null);\n        }}\n        onClick={(event) => {\n          if (event.target === event.currentTarget) setSelected(null);\n        }}\n        onClose={() => setSelected(null)}\n        ref={dialogRef}\n      >\n        {selected && (\n          <div className=\"relative my-auto w-full max-w-xl rounded-2xl bg-white p-5 shadow-2xl dark:bg-zinc-950\">\n            <div className=\"mb-4 flex items-center justify-between gap-4\">\n              <h3 className=\"truncate text-sm font-medium\">{selected.name}</h3>\n              <button\n                aria-label=\"Close preview\"\n                autoFocus\n                className=\"grid size-8 shrink-0 place-items-center rounded-lg text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n                onClick={() => setSelected(null)}\n                type=\"button\"\n              >\n                <X aria-hidden=\"true\" className=\"size-4\" />\n              </button>\n            </div>\n            {artwork(selected, true)}\n            <p className=\"mt-4 text-xs leading-5 text-zinc-500\">\n              This is an SVG demo card. Your videos stay local and are not\n              converted.\n            </p>\n            <button\n              className=\"mt-4 inline-flex min-h-10 items-center gap-2 rounded-lg bg-violet-600 px-4 text-xs font-medium text-white hover:bg-violet-700\"\n              onClick={() => download(selected)}\n              type=\"button\"\n            >\n              <ArrowDownToLine aria-hidden=\"true\" className=\"size-3.5\" />{\" \"}\n              Download SVG demo\n            </button>\n          </div>\n        )}\n      </dialog>\n    </section>\n  );\n}\n"
        },
        {
          "path": "content/components/workspace/task-queue-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-workspace/shared/content/components/workspace/task-queue-01.tsx",
          "content": "\"use client\";\n\nimport {\n  AlertCircle,\n  ArrowRight,\n  CheckCircle2,\n  Clock3,\n  FileVideo2,\n  Inbox,\n  LoaderCircle,\n  RotateCcw,\n} from \"lucide-react\";\nimport { useId, useState } from \"react\";\n\nexport type DemoTask = {\n  id: string;\n  name: string;\n  detail: string;\n  status: \"queued\" | \"processing\" | \"completed\" | \"error\";\n  progress?: number;\n};\nexport type TaskQueue01Props = {\n  title?: string;\n  tasks?: DemoTask[];\n  searchQuery?: string;\n  onRetry?: (id: string) => void;\n};\nconst sampleTasks: DemoTask[] = [\n  {\n    id: \"sample-1\",\n    name: \"Autumn-campaign.mp4\",\n    detail: \"MP4 · Web ready\",\n    status: \"processing\",\n    progress: 64,\n  },\n  {\n    id: \"sample-2\",\n    name: \"Product-walkthrough.mov\",\n    detail: \"MP4 · Web ready\",\n    status: \"queued\",\n  },\n  {\n    id: \"sample-3\",\n    name: \"Studio-notes.mp4\",\n    detail: \"MP4 · Web ready\",\n    status: \"completed\",\n  },\n];\nconst statusLabels = {\n  queued: \"In queue\",\n  processing: \"Preparing\",\n  completed: \"Ready\",\n  error: \"Needs attention\",\n};\n\n/** Task queue with status filtering, search, progress, empty and error states. */\nexport default function TaskQueue01({\n  title = \"Your task queue\",\n  tasks = sampleTasks,\n  searchQuery = \"\",\n  onRetry,\n}: TaskQueue01Props = {}) {\n  const [filter, setFilter] = useState<\"all\" | \"active\" | \"completed\">(\"all\");\n  const [notice, setNotice] = useState(\"\");\n  const id = useId();\n  const visible = tasks.filter(\n    (task) =>\n      task.name.toLowerCase().includes(searchQuery.toLowerCase()) &&\n      (filter === \"all\" ||\n        (filter === \"active\"\n          ? task.status === \"queued\" || task.status === \"processing\"\n          : task.status === \"completed\"))\n  );\n\n  return (\n    <section\n      aria-labelledby={id}\n      className=\"w-full overflow-hidden rounded-2xl border border-zinc-200 bg-white text-zinc-950 shadow-sm shadow-zinc-950/[0.02] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n    >\n      <div className=\"flex flex-wrap items-center justify-between gap-3 border-b border-zinc-100 px-5 py-5 sm:px-6 dark:border-zinc-800\">\n        <div className=\"flex items-center gap-2\">\n          <h2 className=\"text-sm font-semibold\" id={id}>\n            {title}\n          </h2>\n          <span className=\"rounded-md bg-zinc-100 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:bg-zinc-800\">\n            {tasks.length}\n          </span>\n        </div>\n        <span className=\"inline-flex items-center gap-1.5 text-[10px] text-zinc-400\">\n          <span className=\"size-1.5 rounded-full bg-emerald-500\" /> Demo\n          activity\n        </span>\n      </div>\n      <div\n        aria-label=\"Filter task queue\"\n        className=\"flex gap-5 border-b border-zinc-100 px-5 sm:px-6 dark:border-zinc-800\"\n        role=\"group\"\n      >\n        {([\"all\", \"active\", \"completed\"] as const).map((value) => (\n          <button\n            aria-pressed={filter === value}\n            className={`relative min-h-11 text-xs font-medium capitalize transition-colors ${filter === value ? \"text-violet-600 after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:bg-violet-600 dark:text-violet-400\" : \"text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200\"}`}\n            key={value}\n            onClick={() => setFilter(value)}\n            type=\"button\"\n          >\n            {value === \"all\" ? \"All files\" : value}\n          </button>\n        ))}\n      </div>\n      {visible.length ? (\n        <ul className=\"divide-y divide-zinc-100 dark:divide-zinc-800\">\n          {visible.map((task) => (\n            <li\n              className=\"flex items-center gap-3 px-5 py-4 sm:px-6\"\n              key={task.id}\n            >\n              <span className=\"grid size-10 shrink-0 place-items-center rounded-xl border border-zinc-100 bg-zinc-50 text-zinc-400 dark:border-zinc-800 dark:bg-zinc-900\">\n                <FileVideo2 aria-hidden=\"true\" className=\"size-4\" />\n              </span>\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"truncate text-xs font-medium\">{task.name}</p>\n                <p className=\"mt-1 text-[10px] text-zinc-400\">{task.detail}</p>\n                {task.status === \"processing\" && (\n                  <div\n                    aria-label={`${task.name} progress`}\n                    aria-valuemax={100}\n                    aria-valuemin={0}\n                    aria-valuenow={task.progress ?? 0}\n                    className=\"mt-2 h-1 max-w-48 overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800\"\n                    role=\"progressbar\"\n                  >\n                    <div\n                      className=\"h-full rounded-full bg-violet-500 transition-all duration-500\"\n                      style={{ width: `${task.progress ?? 0}%` }}\n                    />\n                  </div>\n                )}\n              </div>\n              <div\n                className={`flex shrink-0 items-center gap-1.5 text-[10px] ${task.status === \"completed\" ? \"text-emerald-600 dark:text-emerald-400\" : task.status === \"error\" ? \"text-red-500\" : task.status === \"processing\" ? \"text-violet-600 dark:text-violet-400\" : \"text-zinc-400\"}`}\n              >\n                {task.status === \"processing\" ? (\n                  <LoaderCircle\n                    aria-hidden=\"true\"\n                    className=\"size-3 animate-spin\"\n                  />\n                ) : task.status === \"completed\" ? (\n                  <CheckCircle2 aria-hidden=\"true\" className=\"size-3\" />\n                ) : task.status === \"error\" ? (\n                  <AlertCircle aria-hidden=\"true\" className=\"size-3\" />\n                ) : (\n                  <Clock3 aria-hidden=\"true\" className=\"size-3\" />\n                )}\n                <span className=\"hidden sm:inline\">\n                  {statusLabels[task.status]}\n                </span>\n                {task.status === \"processing\" && (\n                  <span>{task.progress ?? 0}%</span>\n                )}\n                {task.status === \"error\" && (\n                  <button\n                    aria-label={`Retry ${task.name}`}\n                    className=\"ml-1 rounded-md p-1.5 hover:bg-red-50 dark:hover:bg-red-950/40\"\n                    onClick={() => {\n                      if (onRetry) onRetry(task.id);\n                      else\n                        setNotice(\n                          \"Retry selected. This standalone queue is a browser-only demo.\"\n                        );\n                    }}\n                    type=\"button\"\n                  >\n                    <RotateCcw aria-hidden=\"true\" className=\"size-3\" />\n                  </button>\n                )}\n              </div>\n            </li>\n          ))}\n        </ul>\n      ) : (\n        <div className=\"flex min-h-44 flex-col items-center justify-center px-6 py-8 text-center\">\n          <Inbox\n            aria-hidden=\"true\"\n            className=\"size-7 text-zinc-300 dark:text-zinc-600\"\n          />\n          <p className=\"mt-3 text-xs font-medium\">\n            {searchQuery\n              ? \"No matching files\"\n              : filter === \"all\"\n                ? \"A fresh start\"\n                : \"Nothing here yet\"}\n          </p>\n          <p className=\"mt-1 text-[11px] text-zinc-400\">\n            {searchQuery\n              ? \"Try another file name.\"\n              : \"Your demo tasks will appear here.\"}\n          </p>\n        </div>\n      )}\n      {notice && (\n        <p\n          className=\"px-5 py-3 text-xs text-violet-600 dark:text-violet-400\"\n          role=\"status\"\n        >\n          {notice}\n        </p>\n      )}\n      <div className=\"flex items-center justify-between gap-2 border-t border-zinc-100 bg-zinc-50/50 px-5 py-3 text-[10px] text-zinc-400 sm:px-6 dark:border-zinc-800 dark:bg-zinc-900/30\">\n        <span>\n          {tasks.filter((task) => task.status === \"completed\").length} completed\n          ·{\" \"}\n          {\n            tasks.filter(\n              (task) => task.status === \"queued\" || task.status === \"processing\"\n            ).length\n          }{\" \"}\n          in progress\n        </span>\n        <span className=\"inline-flex items-center gap-1\">\n          Demo queue <ArrowRight aria-hidden=\"true\" className=\"size-3\" />\n        </span>\n      </div>\n    </section>\n  );\n}\n"
        },
        {
          "path": "content/components/workspace/upload-workflow-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-workspace/shared/content/components/workspace/upload-workflow-01.tsx",
          "content": "\"use client\";\n\nimport {\n  ArrowRight,\n  Check,\n  FileVideo2,\n  Film,\n  LoaderCircle,\n  Plus,\n  ShieldCheck,\n  UploadCloud,\n  X,\n} from \"lucide-react\";\nimport { useId, useRef, useState } from \"react\";\n\nexport type WorkflowFile = { id: string; name: string; size: number };\nexport type UploadWorkflow01Props = {\n  title?: string;\n  description?: string;\n  files?: WorkflowFile[];\n  onFilesChange?: (files: WorkflowFile[]) => void;\n  onRun?: (files: WorkflowFile[], format: string) => void;\n  busy?: boolean;\n};\n\nconst formats = [\"MP4\", \"WebM\", \"GIF\"];\nconst supportedExtension = /\\.(mp4|mov|webm|mkv)$/i;\n\n/** Browser-only file selection. The demo never uploads or transcodes media. */\nexport default function UploadWorkflow01({\n  title = \"Make something ready to share.\",\n  description = \"Add your videos, choose an output, and try the workflow.\",\n  files: controlledFiles,\n  onFilesChange,\n  onRun,\n  busy = false,\n}: UploadWorkflow01Props = {}) {\n  const [localFiles, setLocalFiles] = useState<WorkflowFile[]>([]);\n  const [format, setFormat] = useState(\"MP4\");\n  const [isDragging, setIsDragging] = useState(false);\n  const [error, setError] = useState(\"\");\n  const [notice, setNotice] = useState(\"\");\n  const inputRef = useRef<HTMLInputElement>(null);\n  const inputId = useId();\n  const files = controlledFiles ?? localFiles;\n\n  function updateFiles(next: WorkflowFile[]) {\n    setLocalFiles(next);\n    onFilesChange?.(next);\n    setNotice(\"\");\n  }\n\n  function addFiles(selected: FileList | null) {\n    if (!selected || busy) return;\n    const incoming = Array.from(selected);\n    const invalid = incoming.find(\n      (file) =>\n        !supportedExtension.test(file.name) || file.size > 200 * 1024 * 1024\n    );\n    if (invalid) {\n      setError(\"Choose MP4, MOV, WebM, or MKV videos up to 200 MB each.\");\n      return;\n    }\n    if (files.length + incoming.length > 10) {\n      setError(\"You can add up to 10 videos to a demo batch.\");\n      return;\n    }\n    setError(\"\");\n    updateFiles([\n      ...files,\n      ...incoming.map((file, index) => ({\n        id: `${file.name}-${file.lastModified}-${index}-${crypto.randomUUID()}`,\n        name: file.name,\n        size: file.size,\n      })),\n    ]);\n  }\n\n  return (\n    <section\n      className=\"min-w-0 w-full rounded-2xl border border-zinc-200 bg-white p-5 text-zinc-950 shadow-sm shadow-zinc-950/[0.02] sm:p-6 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n      aria-labelledby={`${inputId}-title`}\n    >\n      <div className=\"flex items-start gap-3\">\n        <span className=\"grid size-9 shrink-0 place-items-center rounded-xl bg-violet-50 text-violet-600 dark:bg-violet-950/40 dark:text-violet-400\">\n          <Film aria-hidden=\"true\" className=\"size-4\" />\n        </span>\n        <div>\n          <h2\n            className=\"text-base font-semibold tracking-tight\"\n            id={`${inputId}-title`}\n          >\n            {title}\n          </h2>\n          <p className=\"mt-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n        </div>\n      </div>\n      <div\n        className={`mt-6 rounded-xl border border-dashed p-6 text-center transition-colors sm:p-8 ${isDragging ? \"border-violet-500 bg-violet-50 dark:bg-violet-950/30\" : \"border-zinc-300 bg-zinc-50/70 dark:border-zinc-700 dark:bg-zinc-900/50\"}`}\n        onDragOver={(event) => {\n          event.preventDefault();\n          if (!busy) setIsDragging(true);\n        }}\n        onDragLeave={() => setIsDragging(false)}\n        onDrop={(event) => {\n          event.preventDefault();\n          setIsDragging(false);\n          addFiles(event.dataTransfer.files);\n        }}\n      >\n        <span className=\"mx-auto grid size-11 place-items-center rounded-xl border border-zinc-200 bg-white text-zinc-500 shadow-sm dark:border-zinc-700 dark:bg-zinc-900\">\n          <UploadCloud aria-hidden=\"true\" className=\"size-5\" />\n        </span>\n        <p className=\"mt-4 text-sm font-medium\">Drop your videos here</p>\n        <p className=\"mt-1 text-xs text-zinc-500 dark:text-zinc-400\">\n          MP4, MOV, WebM, MKV · Up to 200 MB each\n        </p>\n        <input\n          accept=\".mp4,.mov,.webm,.mkv\"\n          aria-label=\"Choose videos for the demo\"\n          className=\"sr-only\"\n          disabled={busy}\n          id={inputId}\n          multiple\n          onChange={(event) => {\n            addFiles(event.target.files);\n            event.target.value = \"\";\n          }}\n          ref={inputRef}\n          type=\"file\"\n        />\n        <button\n          className=\"mt-4 inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-3 text-xs font-medium text-zinc-700 shadow-sm transition-colors hover:bg-zinc-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-600 disabled:opacity-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:hover:bg-zinc-800\"\n          disabled={busy}\n          onClick={() => inputRef.current?.click()}\n          type=\"button\"\n        >\n          <Plus aria-hidden=\"true\" className=\"size-3.5\" /> Choose files\n        </button>\n      </div>\n      {files.length > 0 && (\n        <ul className=\"mt-4 space-y-2\" aria-label=\"Selected videos\">\n          {files.map((file) => (\n            <li\n              className=\"flex items-center gap-3 rounded-lg border border-zinc-100 bg-zinc-50/50 p-3 dark:border-zinc-800 dark:bg-zinc-900/40\"\n              key={file.id}\n            >\n              <FileVideo2\n                aria-hidden=\"true\"\n                className=\"size-4 shrink-0 text-violet-500\"\n              />\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"truncate text-xs font-medium\">{file.name}</p>\n                <p className=\"mt-0.5 text-[11px] text-zinc-400\">\n                  {file.size < 1024 * 1024\n                    ? `${Math.max(1, Math.round(file.size / 1024))} KB`\n                    : `${(file.size / 1024 / 1024).toFixed(1)} MB`}\n                </p>\n              </div>\n              <button\n                aria-label={`Remove ${file.name}`}\n                className=\"grid size-8 shrink-0 place-items-center rounded-lg text-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 disabled:opacity-40 dark:hover:bg-zinc-800 dark:hover:text-white\"\n                disabled={busy}\n                onClick={() =>\n                  updateFiles(files.filter((item) => item.id !== file.id))\n                }\n                type=\"button\"\n              >\n                <X aria-hidden=\"true\" className=\"size-3.5\" />\n              </button>\n            </li>\n          ))}\n        </ul>\n      )}\n      <fieldset className=\"mt-6\" disabled={busy}>\n        <legend className=\"mb-2.5 text-xs font-medium\">Output format</legend>\n        <div className=\"grid grid-cols-3 gap-2\">\n          {formats.map((item) => (\n            <label\n              className={`flex min-h-10 cursor-pointer items-center justify-between rounded-lg border px-3 text-xs transition-colors focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-violet-500 ${format === item ? \"border-violet-500 bg-violet-50/70 text-violet-700 dark:bg-violet-950/30 dark:text-violet-300\" : \"border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-800 dark:text-zinc-400\"}`}\n              key={item}\n            >\n              <input\n                checked={format === item}\n                className=\"sr-only\"\n                name={`${inputId}-format`}\n                onChange={() => setFormat(item)}\n                type=\"radio\"\n                value={item}\n              />\n              <span>{item}</span>\n              {format === item && (\n                <Check aria-hidden=\"true\" className=\"size-3.5\" />\n              )}\n            </label>\n          ))}\n        </div>\n      </fieldset>\n      {error && (\n        <p className=\"mt-3 text-xs text-red-600 dark:text-red-400\" role=\"alert\">\n          {error}\n        </p>\n      )}\n      <button\n        className=\"mt-6 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl bg-violet-600 px-4 text-sm font-medium text-white shadow-sm shadow-violet-600/10 transition-colors hover:bg-violet-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-500 disabled:cursor-not-allowed disabled:opacity-45\"\n        disabled={busy || files.length === 0}\n        onClick={() => {\n          if (onRun) onRun(files, format);\n          else\n            setNotice(\n              `Demo ready: ${files.length} ${files.length === 1 ? \"video\" : \"videos\"} selected for ${format}. This preview does not convert media.`\n            );\n        }}\n        type=\"button\"\n      >\n        {busy ? (\n          <LoaderCircle aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n        ) : (\n          <ArrowRight aria-hidden=\"true\" className=\"size-4\" />\n        )}\n        {busy ? \"Preparing demo…\" : `Try ${format} workflow`}\n      </button>\n      {notice && (\n        <p\n          className=\"mt-3 text-xs leading-5 text-violet-600 dark:text-violet-300\"\n          role=\"status\"\n        >\n          {notice}\n        </p>\n      )}\n      <p className=\"mt-3 flex items-start justify-center gap-1.5 text-center text-[10px] leading-4 text-zinc-400\">\n        <ShieldCheck aria-hidden=\"true\" className=\"mt-0.5 size-3 shrink-0\" />{\" \"}\n        Browser-only demo. Your videos stay on this device; no media is\n        converted.\n      </p>\n    </section>\n  );\n}\n"
        },
        {
          "path": "content/components/workspace/usage-panel-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-workspace/shared/content/components/workspace/usage-panel-01.tsx",
          "content": "\"use client\";\n\nimport {\n  ArrowUpRight,\n  CheckCircle2,\n  CircleHelp,\n  Leaf,\n  Zap,\n} from \"lucide-react\";\nimport { useId, useState } from \"react\";\n\nexport type UsagePanel01Props = {\n  title?: string;\n  used?: number;\n  limit?: number;\n  planName?: string;\n  upgradeHref?: string;\n};\n\n/** A usage summary with bounded progress and a plain-language plan explanation. */\nexport default function UsagePanel01({\n  title = \"A little room to create.\",\n  used = 38,\n  limit = 100,\n  planName = \"Studio demo\",\n  upgradeHref = \"/preview/recipes/tool-landing#pricing\",\n}: UsagePanel01Props = {}) {\n  const [showInfo, setShowInfo] = useState(false);\n  const id = useId();\n  const percentage =\n    limit > 0 ? Math.max(0, Math.min(100, (used / limit) * 100)) : 0;\n  return (\n    <aside\n      aria-labelledby={id}\n      className=\"w-full rounded-2xl border border-zinc-200 bg-white p-5 text-zinc-950 shadow-sm shadow-zinc-950/[0.02] sm:p-6 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n    >\n      <div className=\"flex items-start justify-between gap-3\">\n        <span className=\"grid size-9 place-items-center rounded-xl bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-300\">\n          <Zap aria-hidden=\"true\" className=\"size-4\" />\n        </span>\n        <span className=\"rounded-md bg-violet-50 px-2 py-1 text-[10px] font-medium text-violet-600 dark:bg-violet-950/40 dark:text-violet-400\">\n          {planName}\n        </span>\n      </div>\n      <h2 className=\"mt-4 text-sm font-semibold\" id={id}>\n        {title}\n      </h2>\n      <p className=\"mt-2 text-xs leading-5 text-zinc-500 dark:text-zinc-400\">\n        Keep an eye on your workspace usage as your ideas take shape.\n      </p>\n      <div className=\"mt-5 flex items-baseline justify-between gap-3\">\n        <p className=\"text-xl font-semibold tabular-nums tracking-tight\">\n          {Math.max(0, used)}\n          <span className=\"ml-1 text-xs font-normal text-zinc-400\">\n            / {Math.max(0, limit)} credits\n          </span>\n        </p>\n        <button\n          aria-expanded={showInfo}\n          aria-label=\"About demo credits\"\n          className=\"grid size-7 place-items-center rounded-md text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n          onClick={() => setShowInfo(!showInfo)}\n          type=\"button\"\n        >\n          <CircleHelp aria-hidden=\"true\" className=\"size-3.5\" />\n        </button>\n      </div>\n      <div\n        aria-label=\"Demo credit usage\"\n        aria-valuemax={100}\n        aria-valuemin={0}\n        aria-valuenow={Math.round(percentage)}\n        className=\"mt-3 h-1.5 overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800\"\n        role=\"progressbar\"\n      >\n        <div\n          className=\"h-full rounded-full bg-violet-500 transition-all duration-500\"\n          style={{ width: `${percentage}%` }}\n        />\n      </div>\n      <p className=\"mt-2 text-[10px] text-zinc-400\">\n        Illustrative usage · No account or charges\n      </p>\n      {showInfo && (\n        <p className=\"mt-3 rounded-lg bg-zinc-50 p-3 text-[11px] leading-5 text-zinc-500 dark:bg-zinc-900 dark:text-zinc-400\">\n          These credits demonstrate a usage layout. Demo runs do not spend real\n          credits, and no subscription is created.\n        </p>\n      )}\n      <a\n        className=\"mt-5 flex min-h-9 items-center justify-center gap-1.5 rounded-lg border border-zinc-200 text-xs font-medium transition-colors hover:border-violet-300 hover:bg-violet-50 hover:text-violet-700 dark:border-zinc-700 dark:hover:bg-violet-950/30 dark:hover:text-violet-300\"\n        href={upgradeHref}\n      >\n        Explore example plans{\" \"}\n        <ArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n      </a>\n      <div className=\"mt-5 space-y-2 border-t border-zinc-100 pt-4 text-[10px] text-zinc-400 dark:border-zinc-800\">\n        <p className=\"flex items-center gap-1.5\">\n          <CheckCircle2\n            aria-hidden=\"true\"\n            className=\"size-3 text-emerald-500\"\n          />{\" \"}\n          Local files stay private\n        </p>\n        <p className=\"flex items-center gap-1.5\">\n          <Leaf aria-hidden=\"true\" className=\"size-3 text-emerald-500\" /> No\n          background uploads\n        </p>\n      </div>\n    </aside>\n  );\n}\n"
        },
        {
          "path": "content/components/workspace/workspace-header-01.tsx",
          "type": "registry:component",
          "target": "@components/uiblocks/recipe-tool-workspace/shared/content/components/workspace/workspace-header-01.tsx",
          "content": "\"use client\";\n\nimport {\n  ArrowLeft,\n  ArrowUpRight,\n  Command,\n  Layers2,\n  Search,\n} from \"lucide-react\";\nimport { useState } from \"react\";\n\nexport type WorkspaceHeader01Props = {\n  productName?: string;\n  title?: string;\n  description?: string;\n  websiteHref?: string;\n  searchQuery?: string;\n  onSearch?: (query: string) => void;\n};\n\n/** A compact workspace masthead with an accessible, functional file search. */\nexport default function WorkspaceHeader01({\n  productName = \"Frame\",\n  title = \"A little less waiting. A lot more creating.\",\n  description = \"Prepare your next video, keep every task in view, and pick up where you left off.\",\n  websiteHref = \"/preview/recipes/tool-landing\",\n  searchQuery,\n  onSearch,\n}: WorkspaceHeader01Props = {}) {\n  const [query, setQuery] = useState(\"\");\n\n  return (\n    <header\n      className=\"w-full bg-white text-zinc-950 dark:bg-zinc-950 dark:text-zinc-50\"\n      id=\"workspace\"\n    >\n      <div className=\"border-b border-zinc-200 dark:border-zinc-800\">\n        <div className=\"mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-4 px-5 py-4 sm:px-8\">\n          <a\n            className=\"inline-flex items-center gap-2.5 text-lg font-semibold tracking-tight\"\n            href={websiteHref}\n          >\n            <span className=\"grid size-8 place-items-center rounded-xl bg-violet-600 text-white\">\n              <Layers2 aria-hidden=\"true\" className=\"size-4\" />\n            </span>\n            {productName}\n            <span className=\"ml-1 rounded-md border border-zinc-200 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-widest text-zinc-500 dark:border-zinc-800\">\n              Workspace\n            </span>\n          </a>\n          <div className=\"flex items-center gap-4\">\n            <a\n              className=\"hidden items-center gap-1.5 text-xs text-zinc-500 transition-colors hover:text-violet-600 sm:inline-flex\"\n              href={websiteHref}\n            >\n              <ArrowLeft aria-hidden=\"true\" className=\"size-3.5\" /> Back to\n              website\n            </a>\n            <span className=\"inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-1 text-[11px] font-medium text-amber-700 ring-1 ring-inset ring-amber-200 dark:bg-amber-950/30 dark:text-amber-300 dark:ring-amber-900\">\n              Interactive demo{\" \"}\n              <ArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n            </span>\n            <span\n              aria-label=\"Demo account\"\n              className=\"grid size-8 place-items-center rounded-full bg-zinc-100 text-xs font-semibold text-zinc-600 ring-1 ring-zinc-200 dark:bg-zinc-800 dark:text-zinc-300 dark:ring-zinc-700\"\n            >\n              JD\n            </span>\n          </div>\n        </div>\n      </div>\n      <div className=\"mx-auto grid max-w-7xl gap-6 px-5 pt-9 pb-7 sm:px-8 md:grid-cols-[1fr_260px] md:items-end\">\n        <div>\n          <p className=\"mb-2 text-xs font-medium text-violet-600 dark:text-violet-400\">\n            Your studio, in one place\n          </p>\n          <h1 className=\"max-w-2xl text-balance text-2xl font-semibold leading-tight tracking-tight sm:text-3xl\">\n            {title}\n          </h1>\n          <p className=\"mt-3 max-w-2xl text-sm leading-6 text-zinc-500 dark:text-zinc-400\">\n            {description}\n          </p>\n        </div>\n        <label className=\"flex min-h-10 items-center gap-2.5 rounded-xl border border-zinc-200 bg-zinc-50 px-3 text-zinc-400 focus-within:border-violet-400 focus-within:ring-2 focus-within:ring-violet-100 dark:border-zinc-800 dark:bg-zinc-900 dark:focus-within:ring-violet-900\">\n          <Search aria-hidden=\"true\" className=\"size-4 shrink-0\" />\n          <input\n            aria-label=\"Search workspace files\"\n            className=\"w-full min-w-0 bg-transparent py-2 text-sm text-zinc-800 outline-none placeholder:text-zinc-400 dark:text-zinc-200\"\n            onChange={(event) => {\n              setQuery(event.target.value);\n              onSearch?.(event.target.value);\n            }}\n            placeholder=\"Find a file…\"\n            type=\"search\"\n            value={searchQuery ?? query}\n          />\n          <Command\n            aria-hidden=\"true\"\n            className=\"size-3.5 shrink-0 opacity-50\"\n          />\n        </label>\n      </div>\n    </header>\n  );\n}\n"
        }
      ],
      "categories": [
        "recipe",
        "application",
        "clean-saas"
      ],
      "docs": "Install this one item to receive the complete File processing workspace composition, its 5 selected blocks, required local helpers, and MIT notices. Import ToolWorkspaceRecipe as the default export from @components/uiblocks/recipe-tool-workspace/recipe-tool-workspace.tsx; read meta.usage and meta.propsContract. It uses ordinary React 19 and Tailwind CSS 4, with no router dependency and no application route overwrite. Change host links and real product copy through typed props. Controlled mode is the default and never simulates a processing backend. Explicit demo mode shows only browser-local interface behavior; use renderUpload for actual File bytes and renderResults for real download URLs. The bundled example blocks retain honest demo labels. See https://uiblocks.org/recipes/tool-workspace.",
      "meta": {
        "kind": "recipe",
        "recipeId": "tool-workspace",
        "blockIds": [
          "workspace-header-01",
          "upload-workflow-01",
          "task-queue-01",
          "results-gallery-01",
          "usage-panel-01"
        ],
        "componentName": "ToolWorkspaceRecipe",
        "propsContract": [
          {
            "name": "mode",
            "description": "Default 'controlled' mode never fabricates processing or results. Explicit mode='demo' enables browser-only timers, illustrative files, SVG downloads, and optional state controls."
          },
          {
            "name": "files, onFilesChange, status, progress, tasks, results",
            "description": "Supply real service state in controlled mode. The default file selector retains metadata only, not File bytes. Keep File objects in the host uploader and use renderUpload for authenticated uploads."
          },
          {
            "name": "onRun, onRetry",
            "description": "Controlled-mode callbacks receive selected file metadata/format or a task ID. The host performs network work and supplies status/results; rejected callbacks show a retryable error, never a simulated success."
          },
          {
            "name": "renderUpload, renderResults, headerSlot, footer",
            "description": "Replace example UI with your real upload control, authenticated result/download links, navigation/account header, and product footer. The bundled blocks retain explicit demo labels; the default gallery only downloads SVG examples."
          },
          {
            "name": "header, upload, queueTitle, resultsTitle, usage, websiteHref, upgradeHref",
            "description": "Adapt workspace copy, product identity, plan/usage data, and normal host navigation links. Do not treat illustrative credits or download previews as real billing or conversion."
          },
          {
            "name": "searchQuery, onSearchChange",
            "description": "Share one search state across the workspace header, task queue, and results, or allow the page to manage local search."
          },
          {
            "name": "theme, className",
            "description": "Inherit the host theme, or enable its class-based dark variant with theme='dark'; customize shared Tailwind 4 theme tokens and the page className."
          }
        ],
        "usage": "import ToolWorkspaceRecipe from \"@/components/uiblocks/recipe-tool-workspace/recipe-tool-workspace\";\n\nexport default function WorkspacePage() {\n  return <ToolWorkspaceRecipe mode=\"demo\" websiteHref=\"/\" upgradeHref=\"/pricing\" />;\n}",
        "includesBackend": false,
        "defaultMode": "controlled",
        "demoMode": "explicit",
        "includedSources": [
          {
            "id": "workspace-header-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "upload-workflow-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "task-queue-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "results-gallery-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          },
          {
            "id": "usage-panel-01",
            "name": "UIBlocks",
            "url": "https://uiblocks.org",
            "license": "MIT",
            "author": "UIBlocks contributors",
            "revision": "uiblocks-v0.1.0"
          }
        ],
        "version": "0.3.0",
        "source": {
          "name": "UIBlocks",
          "url": "https://uiblocks.org/recipes/tool-workspace",
          "license": "MIT",
          "author": "UIBlocks contributors",
          "revision": "uiblocks-v0.3.0"
        },
        "license": "MIT",
        "style": "clean-saas",
        "pageType": "application",
        "featured": false,
        "tags": [
          "complete page",
          "recipe",
          "整页方案",
          "tool-workspace"
        ],
        "contentGuidelines": [
          "Keep the workspace heading to one short sentence and file names to their original values, truncating visual labels when needed.",
          "Use shared state for file selection, queue progress, and results; keep the upload controls disabled while a task runs.",
          "Provide empty, processing, completed, and error states. Status must remain understandable without relying on color.",
          "The preview is a local interface simulation. Selected videos are never uploaded or converted; downloads are labeled SVG demo previews.",
          "Connect the upload and retry callbacks to your own authenticated processing service before using this as a production tool.",
          "Keep demo credits and illustrative usage separate from real billing data. Validate the layout with long file names and mobile widths."
        ],
        "recommendedWith": [
          "workspace-header-01",
          "upload-workflow-01",
          "task-queue-01",
          "results-gallery-01",
          "usage-panel-01"
        ],
        "entry": "@components/uiblocks/recipe-tool-workspace/recipe-tool-workspace.tsx",
        "exportName": "default",
        "frameworkIndependent": true,
        "frameworkDependencies": [],
        "compatibleHosts": [
          "react",
          "vite",
          "tanstack-start",
          "nextjs"
        ],
        "requirements": {
          "react": ">=19",
          "tailwind": ">=4"
        },
        "previewUrl": "https://uiblocks.org/preview/recipes/tool-workspace"
      }
    }
  ]
}
