{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"icon-button-clean","type":"registry:ui","title":"Icon Button (Clean)","description":"A square button that is only an icon – ghost, outlined or solid, two sizes, a pressed state for toggles and its label as the tooltip","dependencies":[],"files":[{"path":"registry/icon-button-clean/icon-button-clean.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { createElement, type ComponentPropsWithRef, type ReactNode } from \"react\";\n\n/*\n * Icon Button – a single self-contained file, clean style.\n * A square button that is only an icon: quiet ghost, a white tool with a hairline shadow, or a dark chip. It can stay\n * pressed (a toggle), and its label is both the accessible name and the tooltip.\n * The styles are injected below, so no extra files are needed.\n */\n\n/** An icon from Hugeicons (e.g. `import { Search01Icon } from \"@hugeicons/core-free-icons\"`) – drawn inline */\nexport type HugeIcon = readonly (readonly [string, { readonly [key: string]: string | number }])[];\n\nexport type IconButtonProps = Omit<ComponentPropsWithRef<\"button\">, \"children\"> & {\n  /** The glyph: any element (an inline SVG) or a Hugeicons icon object */\n  icon: ReactNode | HugeIcon;\n  /** What the button does – read out by screen readers and shown as the tooltip */\n  label: string;\n  /** ghost: just the icon, a soft wash on hover · secondary: white with a hairline shadow · primary: a dark chip */\n  variant?: \"ghost\" | \"secondary\" | \"primary\";\n  /** sm: 24 × 24 · md: 28 × 28 – the hit area grows on touch screens */\n  size?: \"sm\" | \"md\";\n  /** Makes it a toggle: true or false sets aria-pressed and keeps it looking pressed while true */\n  pressed?: boolean;\n};\n\nexport function IconButton({\n  icon,\n  label,\n  variant = \"ghost\",\n  size = \"md\",\n  pressed,\n  title,\n  className,\n  type = \"button\",\n  ...rest\n}: IconButtonProps) {\n  // Unknown values fall back to the defaults instead of an unstyled button\n  const tone = VARIANTS.includes(variant) ? variant : \"ghost\";\n  const scale = size === \"sm\" ? \"sm\" : \"md\";\n\n  return (\n    <>\n      {/* React 19 hoists this into <head> once, no matter how many buttons render */}\n      <style href=\"ui-icon-button-clean\" precedence=\"default\">\n        {css}\n      </style>\n      <button\n        type={type}\n        className={[\"ui-icbc\", className].filter(Boolean).join(\" \")}\n        data-variant={tone}\n        data-size={scale}\n        aria-label={label}\n        // The label doubles as the tooltip; pass title=\"\" to leave it out\n        title={title ?? label}\n        aria-pressed={pressed}\n        {...rest}\n      >\n        <span className=\"ui-icbc__icon\" aria-hidden=\"true\">\n          <Glyph icon={icon} />\n        </span>\n      </button>\n    </>\n  );\n}\n\nconst VARIANTS = [\"ghost\", \"secondary\", \"primary\"];\n\nconst isHugeIcon = (icon: unknown): icon is HugeIcon =>\n  Array.isArray(icon) && icon.length > 0 && icon.every((part) => Array.isArray(part) && typeof part[0] === \"string\");\n\n/** Draws a Hugeicons icon object as an inline SVG at the button's stroke; anything else is shown as it is */\nfunction Glyph({ icon }: { icon: ReactNode | HugeIcon }) {\n  if (!isHugeIcon(icon)) return <>{icon}</>;\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" focusable=\"false\">\n      {icon.map(([tag, { key, ...attrs }], i) =>\n        createElement(tag, { ...attrs, key: String(key ?? i), ...(\"strokeWidth\" in attrs ? { strokeWidth: STROKE } : null) }),\n      )}\n    </svg>\n  );\n}\nconst STROKE = 1.9;\n\n/* ---------- Styles ---------- */\n\nconst css = /* css */ `\n/* Colors as variables – override them on .ui-icbc (see Theming in the docs) */\n.ui-icbc {\n  --ui-icbc-icon: var(--aui-muted, rgba(0, 0, 0, 0.45));\n  --ui-icbc-ink: var(--aui-ink, rgba(0, 0, 0, 0.88));\n  --ui-icbc-wash: rgba(0, 0, 0, 0.04);\n  --ui-icbc-pressed: var(--aui-subtle, rgba(0, 0, 0, 0.06));\n  --ui-icbc-surface: var(--aui-surface, #ffffff);\n  --ui-icbc-shadow: 0 0 0 0.5px var(--aui-border, rgba(0, 0, 0, 0.06)), 0 1px 1px -1px rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.05);\n  --ui-icbc-shadow-hover: 0 0 0 0.5px var(--aui-border-strong, rgba(0, 0, 0, 0.1)), 0 2px 6px -1px rgba(0, 0, 0, 0.08), 0 1px 2px 0 rgba(0, 0, 0, 0.05);\n  --ui-icbc-line: var(--aui-border, rgba(0, 0, 0, 0.06));\n  --ui-icbc-solid: var(--aui-primary, rgba(0, 0, 0, 0.88));\n  --ui-icbc-solid-hover: rgba(0, 0, 0, 0.8);\n  --ui-icbc-on-solid: var(--aui-on-primary, #fcfcfc);\n  --ui-icbc-focus: var(--aui-focus, rgba(0, 96, 226, 0.7));\n  --ui-icbc-ease: cubic-bezier(0.22, 1, 0.36, 1);\n\n  --size: 28px;\n  --icon: 15px;\n  --radius: var(--aui-radius, 6px);\n\n  position: relative;\n  display: inline-grid;\n  place-items: center;\n  flex: none;\n  box-sizing: border-box;\n  width: var(--size);\n  height: var(--size);\n  padding: 0;\n  margin: 0;\n  border: 0;\n  border-radius: var(--radius);\n  background: transparent;\n  color: var(--ui-icbc-icon);\n  font: inherit;\n  cursor: pointer;\n  user-select: none;\n  -webkit-tap-highlight-color: transparent;\n  transition:\n    background-color 140ms ease,\n    color 140ms ease,\n    box-shadow 140ms ease,\n    transform 160ms var(--ui-icbc-ease),\n    opacity 140ms ease;\n}\n.ui-icbc[data-size=\"sm\"] {\n  --size: 24px;\n  --icon: 14px;\n}\n\n.ui-icbc__icon {\n  display: grid;\n  place-items: center;\n  width: var(--icon);\n  height: var(--icon);\n  pointer-events: none;\n}\n.ui-icbc__icon > svg,\n.ui-icbc__icon > img {\n  display: block;\n  width: 100%;\n  height: 100%;\n}\n\n/* ghost: just the icon, a soft wash on hover */\n.ui-icbc[data-variant=\"ghost\"]:hover:not(:disabled) {\n  background: var(--ui-icbc-wash);\n  color: var(--ui-icbc-ink);\n}\n.ui-icbc[data-variant=\"ghost\"][aria-pressed=\"true\"] {\n  background: var(--ui-icbc-pressed);\n  color: var(--ui-icbc-ink);\n}\n\n/* secondary: the white tool with the card's hairline shadow */\n.ui-icbc[data-variant=\"secondary\"] {\n  background: var(--ui-icbc-surface);\n  box-shadow: var(--ui-icbc-shadow);\n}\n.ui-icbc[data-variant=\"secondary\"]:hover:not(:disabled) {\n  box-shadow: var(--ui-icbc-shadow-hover);\n  color: var(--ui-icbc-ink);\n}\n/* Pressed sinks into a gray fill with only the hairline left */\n.ui-icbc[data-variant=\"secondary\"][aria-pressed=\"true\"] {\n  background: var(--ui-icbc-pressed);\n  box-shadow: inset 0 0 0 0.5px var(--ui-icbc-line);\n  color: var(--ui-icbc-ink);\n}\n\n/* primary: a dark chip */\n.ui-icbc[data-variant=\"primary\"] {\n  background: var(--ui-icbc-solid);\n  color: var(--ui-icbc-on-solid);\n}\n.ui-icbc[data-variant=\"primary\"]:hover:not(:disabled) {\n  background: var(--ui-icbc-solid-hover);\n}\n\n.ui-icbc:active:not(:disabled) {\n  transform: scale(0.94);\n}\n.ui-icbc:focus-visible {\n  outline: 2px solid var(--ui-icbc-focus);\n  outline-offset: 2px;\n}\n.ui-icbc:disabled {\n  opacity: 0.4;\n  cursor: default;\n}\n\n/* Touch screens: the look stays, an invisible ring around it takes the tap (36 × 36 at least) */\n@media (pointer: coarse) {\n  .ui-icbc::after {\n    content: \"\";\n    position: absolute;\n    inset: -4px;\n  }\n  .ui-icbc[data-size=\"sm\"]::after {\n    inset: -6px;\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ui-icbc {\n    transition-duration: 1ms;\n  }\n  .ui-icbc:active:not(:disabled) {\n    transform: none;\n  }\n}\n\n/* Dark mode: follows a .dark class or data-theme=\"dark\" on any ancestor, e.g. <html> */\n:where(.dark, [data-theme=\"dark\"]) .ui-icbc {\n  --ui-icbc-icon: var(--aui-muted, rgba(255, 255, 255, 0.45));\n  --ui-icbc-ink: var(--aui-ink, rgba(255, 255, 255, 0.9));\n  --ui-icbc-wash: rgba(255, 255, 255, 0.06);\n  --ui-icbc-pressed: var(--aui-subtle, rgba(255, 255, 255, 0.09));\n  --ui-icbc-surface: var(--aui-surface, #1b1b1e);\n  --ui-icbc-shadow: 0 0 0 0.5px var(--aui-border, rgba(255, 255, 255, 0.1)), 0 1px 2px rgba(0, 0, 0, 0.4);\n  --ui-icbc-shadow-hover: 0 0 0 0.5px var(--aui-border-strong, rgba(255, 255, 255, 0.16)), 0 2px 6px rgba(0, 0, 0, 0.4);\n  --ui-icbc-line: var(--aui-border, rgba(255, 255, 255, 0.08));\n  --ui-icbc-solid: var(--aui-primary, rgba(255, 255, 255, 0.9));\n  --ui-icbc-solid-hover: rgba(255, 255, 255, 0.82);\n  --ui-icbc-on-solid: var(--aui-on-primary, #161618);\n  --ui-icbc-focus: var(--aui-focus, rgba(77, 141, 255, 0.8));\n}\n\n/* Hover of the dark chip follows its fill – also a brand color from --aui-primary */\n@supports (color: color-mix(in srgb, red 50%, transparent)) {\n  .ui-icbc {\n    --ui-icbc-solid-hover: color-mix(in srgb, var(--ui-icbc-solid) 91%, transparent);\n  }\n}\n`;\n"}]}