{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ds-button",
  "title": "Button UI",
  "description": "Feature-rich button with loading state, start/end icons, rounded variant (full/lg/md/none), fullWidth, double-click debounce, two-step destructive confirmation, and built-in tooltip via Radix. Wraps the shadcn Button primitive.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "@lema-ds/button",
    "@lema-ds/tooltip",
    "@lema-ds/spinner"
  ],
  "files": [
    {
      "path": "components/ds/ds-button.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button as ButtonRoot } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\n\n// ── Types ──\n\nexport interface ButtonProps extends React.ComponentProps<typeof ButtonRoot> {\n  /** Shows a spinner and disables the button. */\n  loading?: boolean\n  /** Text shown next to the spinner during loading. Defaults to children. */\n  loadingText?: string\n  /** Icon rendered before the button text. */\n  startIcon?: React.ReactNode\n  /** Icon rendered after the button text. */\n  endIcon?: React.ReactNode\n  /** Border radius. @default \"md\" */\n  rounded?: \"full\" | \"lg\" | \"md\" | \"none\"\n  /** Makes the button span the full width of its container. */\n  fullWidth?: boolean\n  /** Minimum interval (ms) between clicks — prevents accidental double-submits. */\n  debounceMs?: number\n  /** Two-step confirmation. First click shows `text`, second click fires `onClick`. */\n  confirm?: { text: string; duration?: number }\n  /** Shows a tooltip on hover. Pass a string or `{ text, side }`. */\n  tooltip?:\n    string | { text: string; side?: \"top\" | \"right\" | \"bottom\" | \"left\" }\n}\n\n// ── CVA Variants ──\n\nexport const dsButtonVariants = cva(\"\", {\n  variants: {\n    rounded: {\n      full: \"rounded-4xl\",\n      lg: \"rounded-xl\",\n      md: \"rounded-md\",\n      none: \"rounded-none\",\n    },\n    fullWidth: {\n      true: \"w-full\",\n    },\n  },\n  defaultVariants: {\n    rounded: \"md\",\n  },\n})\n\n// ── Component ──\n\nexport function Button({\n  loading = false,\n  loadingText,\n  startIcon,\n  endIcon,\n  rounded,\n  fullWidth,\n  debounceMs,\n  confirm,\n  tooltip,\n  disabled,\n  className,\n  children,\n  onClick,\n  ...props\n}: ButtonProps) {\n  const [confirming, setConfirming] = React.useState(false)\n  const confirmTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n  const lastClick = React.useRef(0)\n\n  React.useEffect(() => {\n    return () => {\n      if (confirmTimer.current) clearTimeout(confirmTimer.current)\n    }\n  }, [])\n\n  const handleClick = React.useCallback(\n    (e: React.MouseEvent<HTMLButtonElement>) => {\n      if (debounceMs) {\n        const now = Date.now()\n        if (now - lastClick.current < debounceMs) return\n        lastClick.current = now\n      }\n\n      if (confirm && !confirming) {\n        setConfirming(true)\n        confirmTimer.current = setTimeout(() => {\n          setConfirming(false)\n        }, confirm.duration ?? 3000)\n        return\n      }\n\n      if (confirm && confirming) {\n        setConfirming(false)\n        if (confirmTimer.current) clearTimeout(confirmTimer.current)\n      }\n\n      onClick?.(e)\n    },\n    [confirm, confirming, debounceMs, onClick]\n  )\n\n  const resolvedTooltip =\n    typeof tooltip === \"string\"\n      ? { text: tooltip, side: \"bottom\" as const }\n      : tooltip\n        ? { text: tooltip.text, side: tooltip.side ?? (\"bottom\" as const) }\n        : null\n\n  const button = (\n    <ButtonRoot\n      disabled={disabled || loading || (confirm && confirming)}\n      aria-busy={loading || undefined}\n      data-slot=\"ds-button\"\n      data-confirming={confirming || undefined}\n      className={cn(\n        dsButtonVariants({ rounded, fullWidth }),\n        loading && \"cursor-wait\",\n        confirming && \"cursor-pointer\",\n        className\n      )}\n      onClick={handleClick}\n      {...props}\n    >\n      {loading ? (\n        <>\n          <Spinner data-icon=\"inline-start\" />\n          {loadingText ?? children}\n        </>\n      ) : confirming && confirm ? (\n        confirm.text\n      ) : (\n        <>\n          {startIcon && <span data-icon=\"inline-start\">{startIcon}</span>}\n          {children}\n          {endIcon && <span data-icon=\"inline-end\">{endIcon}</span>}\n        </>\n      )}\n    </ButtonRoot>\n  )\n\n  if (!resolvedTooltip) return button\n\n  return (\n    <TooltipProvider>\n      <Tooltip>\n        <TooltipTrigger asChild>{button}</TooltipTrigger>\n        <TooltipContent side={resolvedTooltip.side}>\n          <p>{resolvedTooltip.text}</p>\n        </TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
