{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "fab",
  "title": "Fab",
  "type": "registry:ui",
  "description": "Floating action button — a fixed or absolute positioned circular button for the primary screen action. Supports an icon (default), an extended label variant, mini/large sizes, four color variants, six anchor positions, and a disabled state.",
  "categories": [
    "control",
    "navigation"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/fab/fab.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { fabVariants, type FabVariants } from './fab.variants'\n\ntype Variant = NonNullable<FabVariants['variant']>\ntype Size = NonNullable<FabVariants['size']>\ntype Position = NonNullable<FabVariants['position']>\n\n/**\n * <uip-fab> — the registry Fab as a web component.\n *\n * Class strings are React's `fabVariants` verbatim. One shadow-DOM adaptation\n * (a light-DOM <svg> is not a descendant of the inner button): icon sizing\n * moves from `[&_svg]:…` to `::slotted(svg)` on the <slot>, with the size-4 /\n * size-6 / size-7 default per resolved size. `href` renders an <a> (the\n * web-component stand-in for React's `asChild`).\n *\n * `label` renders an extended (pill) FAB — the resolved size is forced to\n * `extended`, like React. `absolute` swaps the fixed anchor for absolute\n * positioning (contained FABs). The accessible name resolves exactly like\n * React: `aria-label` attribute → `label` → `'Floating action'` (React's\n * `ariaLabel` prop maps to the native `aria-label` attribute).\n *\n * Form-associated (React extends ButtonHTMLAttributes): `type` (`button` |\n * `submit` | `reset`) drives the ancestor form through ElementInternals, like\n * <uip-button>. React's `onClick` is the native `click` event.\n *\n * Popup-trigger state (`aria-expanded`, `aria-haspopup`, `aria-description`,\n * `aria-controls` set by a wrapping popover/menu) is forwarded to the inner\n * button, like <uip-button>.\n *\n * Parts: `base` (the button), `label`.\n */\nexport class UipFab extends LitElement {\n  static formAssociated = true\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\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    variant: { reflect: true },\n    size: { reflect: true },\n    position: { reflect: true },\n    label: {},\n    absolute: { type: Boolean, reflect: true },\n    disabled: { type: Boolean, reflect: true },\n    type: { reflect: true },\n    name: { reflect: true },\n    value: {},\n    href: {},\n    target: {},\n    accessibleLabel: { attribute: 'aria-label' },\n    fwdExpanded: { attribute: 'aria-expanded' },\n    fwdHaspopup: { attribute: 'aria-haspopup' },\n    fwdDescription: { attribute: 'aria-description' },\n    fwdControls: { attribute: 'aria-controls' },\n  }\n\n  variant: Variant = 'default'\n  size: Size = 'default'\n  position: Position = 'bottom-right'\n  label?: string\n  absolute = false\n  disabled = false\n  type: 'button' | 'submit' | 'reset' = 'button'\n  name?: string\n  value?: string\n  href?: string\n  target?: string\n  accessibleLabel?: string\n  fwdExpanded?: string\n  fwdHaspopup?: string\n  fwdDescription?: string\n  fwdControls?: string\n\n  private internals = this.attachInternals()\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', 'fab')\n  }\n\n  /** React forces `extended` whenever a label is present. */\n  private get resolvedSize(): Size {\n    return this.label ? 'extended' : this.size\n  }\n\n  protected willUpdate() {\n    this.setAttribute('data-variant', this.variant)\n    this.setAttribute('data-size', this.resolvedSize)\n    this.setAttribute('data-position', this.position)\n  }\n\n  // aria-controls is an id in the host's tree; an id can't be referenced from\n  // inside the shadow root, so point the inner control at the element itself.\n  protected updated() {\n    const inner = this.renderRoot.querySelector<HTMLElement>('[part=base]')\n    if (!inner) return\n    const root = this.getRootNode() as Document | ShadowRoot\n    const target = this.fwdControls ? root.getElementById?.(this.fwdControls) : null\n    inner.ariaControlsElements = target ? [target] : null\n  }\n\n  formDisabledCallback(disabled: boolean) {\n    this.disabled = disabled\n  }\n\n  // A <button> inside a shadow root can't submit the host's form on its own,\n  // so submit/reset are forwarded through ElementInternals (like <uip-button>).\n  private onClick() {\n    if (this.disabled || this.href) return\n    const form = this.internals.form\n    if (!form) return\n    if (this.type === 'reset') {\n      form.reset()\n      return\n    }\n    if (this.type !== 'submit') return\n    if (this.name != null && this.value != null) this.internals.setFormValue(this.value)\n    else this.internals.setFormValue(null)\n    const restore = () => this.internals.setFormValue(null)\n    form.addEventListener('formdata', restore, { once: true })\n    setTimeout(restore, 0)\n    form.requestSubmit()\n  }\n\n  render() {\n    const resolvedSize = this.resolvedSize\n    const classes = cn(\n      fabVariants({ variant: this.variant, size: resolvedSize, position: this.position }),\n      // React: `absolute && position !== 'inline' && 'absolute'` (raw prop).\n      this.absolute && this.position !== 'inline' && 'absolute',\n      // `grow`: fill the host when a layout stretches it (like <uip-button>).\n      'grow',\n    )\n    // React's `[&_svg]:…` sizing, re-expressed for slotted icons: mini → size-4,\n    // large → size-7, everything else (default, extended) → size-6.\n    const slottedIconSize =\n      resolvedSize === 'mini'\n        ? \"[&::slotted(svg:not([class*='size-']))]:size-4\"\n        : resolvedSize === 'large'\n          ? \"[&::slotted(svg:not([class*='size-']))]:size-7\"\n          : \"[&::slotted(svg:not([class*='size-']))]:size-6\"\n    const body = html`<slot class=${cn('[&::slotted(svg)]:pointer-events-none [&::slotted(svg)]:shrink-0', slottedIconSize)}></slot>\n      ${this.label ? html`<span part=\"label\" class=\"pr-1\">${this.label}</span>` : nothing}`\n    const ariaLabel = this.accessibleLabel || this.label || 'Floating action'\n    const shared = {\n      'part': 'base',\n      'class': classes,\n      'aria-label': ariaLabel,\n      'aria-expanded': this.fwdExpanded ?? nothing,\n      'aria-haspopup': this.fwdHaspopup ?? nothing,\n      'aria-description': this.fwdDescription ?? nothing,\n      'data-variant': this.variant,\n      'data-size': resolvedSize,\n      'data-position': this.position,\n    }\n    if (this.href) {\n      return html`<a\n        part=\"base\"\n        class=${shared.class}\n        href=${this.disabled ? nothing : this.href}\n        target=${this.target ?? nothing}\n        aria-disabled=${this.disabled ? 'true' : nothing}\n        aria-label=${shared['aria-label']}\n        aria-expanded=${shared['aria-expanded']}\n        aria-haspopup=${shared['aria-haspopup']}\n        aria-description=${shared['aria-description']}\n        data-variant=${shared['data-variant']}\n        data-size=${shared['data-size']}\n        data-position=${shared['data-position']}\n        >${body}</a\n      >`\n    }\n    return html`<button\n      part=\"base\"\n      class=${shared.class}\n      type=${this.type}\n      name=${this.name ?? nothing}\n      value=${this.value ?? nothing}\n      ?disabled=${this.disabled}\n      aria-label=${shared['aria-label']}\n      aria-expanded=${shared['aria-expanded']}\n      aria-haspopup=${shared['aria-haspopup']}\n      aria-description=${shared['aria-description']}\n      data-variant=${shared['data-variant']}\n      data-size=${shared['data-size']}\n      data-position=${shared['data-position']}\n      @click=${this.onClick}\n    >\n      ${body}\n    </button>`\n  }\n}\n\ncustomElements.get('uip-fab') || customElements.define('uip-fab', UipFab)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-fab': UipFab\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/fab/fab.ts"
    },
    {
      "path": "packages/elements-lit/src/components/fab/fab.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\nexport const fabVariants = cva(\n  \"inline-flex items-center justify-center gap-2 rounded-full font-medium shadow-lg transition-[color,background-color,box-shadow,transform,scale] duration-200 outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px] active:scale-95 touch-manipulation [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-6 disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n        destructive: 'bg-destructive text-white hover:bg-destructive/90',\n        outline:\n          'border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n      },\n      size: {\n        mini: \"size-10 [&_svg:not([class*='size-'])]:size-4\",\n        default: 'size-14',\n        large: \"size-16 [&_svg:not([class*='size-'])]:size-7\",\n        extended: 'h-14 px-5',\n      },\n      position: {\n        'bottom-right': 'fixed bottom-6 right-6',\n        'bottom-left': 'fixed bottom-6 left-6',\n        'top-right': 'fixed top-6 right-6',\n        'top-left': 'fixed top-6 left-6',\n        'bottom-center': 'fixed bottom-6 left-1/2 -translate-x-1/2',\n        inline: '',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n      position: 'bottom-right',\n    },\n  },\n)\n\nexport type FabVariants = VariantProps<typeof fabVariants>\n",
      "type": "registry:file",
      "target": "src/uip/components/fab/fab.variants.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority",
    "lit"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
