{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "icon-box",
  "title": "Icon Box",
  "type": "registry:ui",
  "description": "Icon container primitive with calibrated sizes (2xs to xl), surface variants (solid, outline, subtle, primary, muted), and 2.5D layered IconStack for empty states and feature hero cards.",
  "categories": [
    "data-display"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/icon-box/icon-box.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport type { IconNode } from 'lucide'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n\nexport type IconBoxVariant =\n  'primary' | 'muted' | 'outline' | 'solid' | 'subtle' | 'destructive' | 'success' | 'warning' | 'ghost' | 'custom'\nexport type IconBoxSize = '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'\nexport type IconBoxShape = 'rounded' | 'circle' | 'square'\n\nconst variantClasses: Record<IconBoxVariant, string> = {\n  primary: 'bg-primary/10 text-primary',\n  muted: 'bg-muted text-muted-foreground',\n  outline: 'border border-border bg-background text-foreground shadow-xs',\n  solid: 'bg-foreground text-background shadow-xs',\n  subtle: 'bg-accent text-accent-foreground',\n  destructive: 'bg-destructive/10 text-destructive',\n  success: 'bg-success/15 text-success',\n  warning: 'bg-warning/15 text-warning',\n  ghost: 'text-muted-foreground hover:bg-accent hover:text-foreground',\n  custom: '',\n}\n\nconst shapeClasses: Record<IconBoxShape, string> = {\n  rounded: 'rounded-lg',\n  circle: 'rounded-full',\n  square: 'rounded-none',\n}\n\nconst sizeClasses: Record<IconBoxSize, string> = {\n  '2xs': 'size-6 p-1',\n  xs: 'size-7 p-1.5',\n  sm: 'size-8 p-1.5',\n  md: 'size-9 p-2',\n  lg: 'size-11 p-2.5',\n  xl: 'size-14 p-3.5',\n}\n\nconst iconSizes: Record<IconBoxSize, string> = {\n  '2xs': 'size-3',\n  xs: 'size-3.5',\n  sm: 'size-4',\n  md: 'size-4.5',\n  lg: 'size-6',\n  xl: 'size-7',\n}\n\n// The shared icon() helper renders the <svg> without a part; tag it after render.\nfunction tagIconPart(root: ParentNode) {\n  root.querySelector('svg.lucide')?.setAttribute('part', 'icon')\n}\n\n/**\n * <uip-icon-box> — the registry IconBox.\n *\n * `icon` is a Lucide IconNode from the `lucide` package (property only) and\n * `icon-name` its kebab-case name (for lucide-react's `lucide-<name>` class);\n * or slot your own <svg> (React's `children`).\n *\n * React's `className` lands on the box itself → `part=\"base\"`; React's\n * `iconClassName` on the rendered icon → `part=\"icon\"`. Style both from the\n * page on the host, e.g. React's `className=\"bg-rose-500/15\"\n * iconClassName=\"text-rose-500\"` is\n * `class=\"[&::part(base)]:bg-rose-500/15 [&::part(icon)]:text-rose-500\"`.\n * (A slotted icon is light DOM — style it directly.)\n */\nexport class UipIconBox extends LitElement {\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: inline-flex; }`]\n\n  static properties = {\n    icon: { attribute: false },\n    iconName: { attribute: 'icon-name' },\n    variant: { reflect: true },\n    shape: { reflect: true },\n    size: { reflect: true },\n  }\n\n  icon?: IconNode\n  iconName = 'icon'\n  variant: IconBoxVariant = 'primary'\n  shape: IconBoxShape = 'rounded'\n  size: IconBoxSize = 'md'\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'icon-box')\n  }\n\n  willUpdate() {\n    this.setAttribute('data-variant', this.variant)\n    this.setAttribute('data-size', this.size)\n    this.setAttribute('data-shape', this.shape)\n  }\n\n  updated() {\n    tagIconPart(this.renderRoot)\n  }\n\n  render() {\n    return html`<div\n      part=\"base\"\n      data-slot=\"icon-box\"\n      data-variant=${this.variant}\n      data-size=${this.size}\n      data-shape=${this.shape}\n      class=${cn(\n        'inline-flex shrink-0 items-center justify-center transition-colors',\n        variantClasses[this.variant],\n        shapeClasses[this.shape],\n        sizeClasses[this.size],\n      )}\n    >\n      <slot>${this.icon ? icon(this.icon, this.iconName, iconSizes[this.size]) : nothing}</slot>\n    </div>`\n  }\n}\n\nexport type IconStackVariant = 'primary' | 'muted' | 'destructive' | 'success' | 'warning'\nexport type IconStackSize = 'sm' | 'md' | 'lg' | 'xl'\n\nconst stackSizeMap: Record<IconStackSize, { root: string; base: string; icon: string }> = {\n  sm: { root: 'size-10', base: 'size-8 rounded-lg', icon: 'size-4' },\n  md: { root: 'size-14', base: 'size-11 rounded-xl', icon: 'size-5' },\n  lg: { root: 'size-18', base: 'size-14 rounded-2xl', icon: 'size-7' },\n  xl: { root: 'size-24', base: 'size-18 rounded-3xl', icon: 'size-9' },\n}\n\nconst stackVariantMap: Record<IconStackVariant, { back: string; front: string; text: string }> = {\n  primary: {\n    back: 'bg-primary/20 border-primary/30',\n    front: 'bg-background border-primary/20 shadow-primary/10',\n    text: 'text-primary',\n  },\n  muted: {\n    back: 'bg-muted border-border',\n    front: 'bg-card border-border shadow-black/5',\n    text: 'text-muted-foreground',\n  },\n  destructive: {\n    back: 'bg-destructive/20 border-destructive/30',\n    front: 'bg-background border-destructive/20 shadow-destructive/10',\n    text: 'text-destructive',\n  },\n  success: {\n    back: 'bg-success/20 border-success/30',\n    front: 'bg-background border-success/20 shadow-success/10',\n    text: 'text-success',\n  },\n  warning: {\n    back: 'bg-warning/20 border-warning/30',\n    front: 'bg-background border-warning/20 shadow-warning/10',\n    text: 'text-warning',\n  },\n}\n\n/**\n * <uip-icon-stack> — the registry IconStack (2.5D layered icon container).\n * Same `icon` / `icon-name` / slot contract as <uip-icon-box>.\n *\n * React's `iconClassName` → `part=\"icon\"` on the rendered icon\n * (`class=\"[&::part(icon)]:…\"` on the host).\n *\n * React's front tile lifts on `group-hover:` of an ancestor `.group`; an\n * outside `.group` can't be matched from inside the shadow root, so that\n * class is kept verbatim but is inert here.\n */\nexport class UipIconStack extends LitElement {\n  static styles = [tailwind, css`:host { display: inline-flex; }`]\n\n  static properties = {\n    icon: { attribute: false },\n    iconName: { attribute: 'icon-name' },\n    variant: { reflect: true },\n    size: { reflect: true },\n  }\n\n  icon?: IconNode\n  iconName = 'icon'\n  variant: IconStackVariant = 'primary'\n  size: IconStackSize = 'md'\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'icon-stack')\n  }\n\n  willUpdate() {\n    this.setAttribute('data-variant', this.variant)\n    this.setAttribute('data-size', this.size)\n  }\n\n  updated() {\n    tagIconPart(this.renderRoot)\n  }\n\n  render() {\n    const s = stackSizeMap[this.size]\n    const v = stackVariantMap[this.variant]\n    return html`<div\n      part=\"base\"\n      data-slot=\"icon-stack\"\n      data-variant=${this.variant}\n      data-size=${this.size}\n      class=${cn('relative inline-flex items-center justify-center select-none', s.root)}\n    >\n      <div\n        aria-hidden=\"true\"\n        class=${cn('absolute inset-0 m-auto rotate-6 border transition-transform duration-300', s.base, v.back)}\n      ></div>\n      <div\n        class=${cn(\n          'relative z-10 flex items-center justify-center border shadow-md transition-transform duration-300 group-hover:-translate-y-0.5',\n          s.base,\n          v.front,\n          v.text,\n        )}\n      >\n        <slot>${this.icon ? icon(this.icon, this.iconName, s.icon) : nothing}</slot>\n      </div>\n    </div>`\n  }\n}\n\ncustomElements.get('uip-icon-box') || customElements.define('uip-icon-box', UipIconBox)\ncustomElements.get('uip-icon-stack') || customElements.define('uip-icon-stack', UipIconStack)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-icon-box': UipIconBox\n    'uip-icon-stack': UipIconStack\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/icon-box/icon-box.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
