{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "icon-button",
  "title": "Icon Button",
  "description": "A glyph-only button that cannot be shipped without a name, with four surfaces, a pending state that keeps focus and a 40 px hit area on touch.",
  "dependencies": [],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/tooltip.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/icon-button.tsx",
      "content": "import type * as React from \"react\"\r\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/registry/miseui/ui/tooltip\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\ntype IconButtonVariant = \"ghost\" | \"outline\" | \"solid\" | \"soft\"\r\ntype IconButtonSize = \"sm\" | \"md\" | \"lg\"\r\ntype IconButtonEffect = \"press\"\r\n\r\nconst VARIANT_CLASS: Record<IconButtonVariant, string> = {\r\n  ghost: \"rounded-md text-muted-foreground hover:bg-foreground/[0.06] hover:text-foreground active:bg-foreground/10\",\r\n  outline: \"rounded-md border bg-background text-foreground shadow-xs hover:bg-accent active:bg-accent/70\",\r\n  solid: \"rounded-full bg-mise text-mise-foreground shadow-xs hover:bg-mise/90 active:bg-mise/80\",\r\n  soft: \"rounded-lg bg-foreground/[0.06] text-foreground hover:bg-foreground/10 active:bg-foreground/[0.14]\",\r\n}\r\n\r\n/** The glyph stays the same on a phone; only the invisible hit area below grows. */\r\nconst SIZE_CLASS: Record<IconButtonSize, string> = {\r\n  sm: \"size-8 [&_svg]:size-4\",\r\n  md: \"size-9 [&_svg]:size-4\",\r\n  lg: \"size-10 [&_svg]:size-5\",\r\n}\r\n\r\nconst BASE_CLASS = cn(\r\n  \"relative inline-flex shrink-0 items-center justify-center outline-none select-none\",\r\n  \"transition-[color,background-color,border-color,box-shadow,scale] duration-(--duration-base) ease-(--ease-mise-out)\",\r\n  \"focus-visible:ring-[3px] focus-visible:ring-ring/50\",\r\n  \"disabled:pointer-events-none disabled:opacity-50 aria-disabled:cursor-progress\",\r\n  \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\r\n  \"pointer-coarse:after:absolute pointer-coarse:after:top-1/2 pointer-coarse:after:left-1/2 pointer-coarse:after:size-10 pointer-coarse:after:-translate-x-1/2 pointer-coarse:after:-translate-y-1/2\",\r\n)\r\n\r\n/** Down lands on the first frame with no transition; only the release takes time, so a fast tap still feels the press. */\r\nconst PRESS_CLASS = \"active:scale-[0.97] active:duration-0 motion-reduce:active:scale-100\"\r\nconst DEEP_PRESS_CLASS = \"active:scale-[0.88] active:duration-0 motion-reduce:active:scale-100\"\r\n\r\nfunction PendingGlyph() {\r\n  return (\r\n    <svg\r\n      aria-hidden=\"true\"\r\n      data-slot=\"icon-button-spinner\"\r\n      viewBox=\"0 0 24 24\"\r\n      fill=\"none\"\r\n      className=\"animate-spin [animation-duration:900ms] motion-reduce:animate-pulse motion-reduce:[animation-duration:2400ms]\"\r\n    >\r\n      <circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"currentColor\" strokeWidth=\"2.5\" opacity=\"0.25\" />\r\n      <circle cx=\"12\" cy=\"12\" r=\"9\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeDasharray=\"14 57\" />\r\n    </svg>\r\n  )\r\n}\r\n\r\n/** `aria-label` is replaced by the required `label`, so a nameless icon button does not compile. */\r\ntype IconButtonProps = Omit<React.ComponentProps<\"button\">, \"aria-label\" | \"aria-labelledby\" | \"children\"> & {\r\n  /** The accessible name. A glyph alone tells a screen reader nothing. */\r\n  label: string\r\n  /** The glyph, usually one lucide icon. */\r\n  children: React.ReactNode\r\n  /** `ghost` no surface, `outline` a hairline, `solid` the one accent circle, `soft` a quiet fill. */\r\n  variant?: IconButtonVariant\r\n  /** 32, 36 or 40 px. On touch the hit area is 40 px whichever you pick. */\r\n  size?: IconButtonSize\r\n  /** `true` shows the label in a tooltip; anything else shows that content. */\r\n  tooltip?: boolean | React.ReactNode\r\n  /** Swaps the glyph for a spinner and ignores clicks, while keeping focus on the button. */\r\n  pending?: boolean\r\n  /** The name while pending, such as \"Saving\". Falls back to `label`. */\r\n  pendingLabel?: string\r\n  /** `press` dips the whole button further, for controls hit many times in a row. */\r\n  effect?: IconButtonEffect\r\n}\r\n\r\nfunction IconButton({\r\n  label,\r\n  children,\r\n  variant = \"ghost\",\r\n  size = \"md\",\r\n  tooltip,\r\n  pending = false,\r\n  pendingLabel,\r\n  effect,\r\n  type = \"button\",\r\n  className,\r\n  onClick,\r\n  ...props\r\n}: IconButtonProps) {\r\n  const button = (\r\n    <button\r\n      type={type}\r\n      data-slot=\"icon-button\"\r\n      data-variant={variant}\r\n      data-size={size}\r\n      data-effect={effect}\r\n      data-pending={pending ? \"true\" : undefined}\r\n      aria-label={pending && pendingLabel ? pendingLabel : label}\r\n      aria-busy={pending ? true : undefined}\r\n      // Not `disabled`: that would throw focus away the moment a keyboard user pressed the button.\r\n      aria-disabled={pending ? true : undefined}\r\n      onClick={pending ? (event) => event.preventDefault() : onClick}\r\n      className={cn(BASE_CLASS, VARIANT_CLASS[variant], SIZE_CLASS[size], effect === \"press\" ? DEEP_PRESS_CLASS : PRESS_CLASS, className)}\r\n      {...props}\r\n    >\r\n      {pending ? <PendingGlyph /> : children}\r\n    </button>\r\n  )\r\n  if (!tooltip) return button\r\n  return (\r\n    <Tooltip>\r\n      <TooltipTrigger asChild>{button}</TooltipTrigger>\r\n      <TooltipContent>{tooltip === true ? label : tooltip}</TooltipContent>\r\n    </Tooltip>\r\n  )\r\n}\r\n\r\nexport { IconButton, type IconButtonProps, type IconButtonVariant, type IconButtonSize, type IconButtonEffect }\r\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}