{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "back-top",
  "title": "Back Top",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/back-top/back-top.component.ts",
      "content": "import {\n  Component,\n  EventEmitter,\n  Input,\n  OnChanges,\n  OnDestroy,\n  AfterViewInit,\n  Output,\n  SimpleChanges,\n  TemplateRef,\n  booleanAttribute,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiRenderTemplateDirective } from '@/ui/popper/popper'\nimport { backTopVariants, type BackTopVariants } from './back-top.variants'\n\nexport type BackTopSize = NonNullable<BackTopVariants['size']>\nexport type BackTopPosition = NonNullable<BackTopVariants['position']>\ntype ScrollTarget = HTMLElement | Window\n\n/**\n * Angular port of UIPKGE BackTop (React `BackTop`). A scroll-to-top <button> that mounts once\n * the target (window by default, or a selector / element) scrolls past `threshold`, animates\n * out via `data-state=\"closed\"` for 200ms before unmounting, and smooth-scrolls the target back\n * to the top on click (instant under prefers-reduced-motion). The `ui-back-top` host is\n * `display: contents`; `class` lands on the button. React's `icon` node is a TemplateRef input.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-back-top',\n  standalone: true,\n  imports: [UiRenderTemplateDirective],\n  host: { '[attr.class]': '\"contents\"' },\n  template: `@if (mounted()) {\n    <button\n      data-uipkge=\"\"\n      data-slot=\"back-top\"\n      [attr.data-state]=\"dataState()\"\n      [attr.data-size]=\"size\"\n      [attr.data-position]=\"position\"\n      type=\"button\"\n      [attr.aria-label]=\"ariaLabel\"\n      [class]=\"buttonClass\"\n      [style.right]=\"edge.right\"\n      [style.left]=\"edge.left\"\n      [style.top]=\"edge.top\"\n      [style.bottom]=\"edge.bottom\"\n      (click)=\"onClick()\"\n    >\n      @if (icon) {\n        <ng-container [uiRenderTemplate]=\"icon\" />\n      } @else {\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"24\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          stroke-width=\"2\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n          class=\"lucide lucide-arrow-up\"\n          aria-hidden=\"true\"\n        >\n          <path d=\"m5 12 7-7 7 7\" />\n          <path d=\"M12 19V5\" />\n        </svg>\n      }\n    </button>\n  }`,\n})\nexport class UiBackTopComponent implements OnChanges, AfterViewInit, OnDestroy {\n  /** Visibility threshold in pixels (shows at scrollTop >= threshold). */\n  @Input() threshold = 200\n  /** Scroll container: CSS selector, element or window (default). */\n  @Input() target?: string | HTMLElement | Window\n  @Input() behavior: ScrollBehavior = 'smooth'\n  @Input() size: BackTopSize = 'default'\n  @Input() position: BackTopPosition = 'bottom-right'\n  /** Distance from the edge (px). */\n  @Input() offset = 24\n  /** Absolute (section-level) instead of fixed (viewport) positioning. */\n  @Input({ transform: booleanAttribute }) absolute = false\n  @Input() ariaLabel = 'Scroll to top'\n  /** Replaces the default ArrowUp icon (React `icon`). */\n  @Input() icon?: TemplateRef<unknown>\n  @Input('class') className?: string\n  /** Fires whenever visibility toggles (React `onVisible`). */\n  @Output() visible = new EventEmitter<boolean>()\n\n  readonly isVisible = signal(false)\n  readonly mounted = signal(false)\n  readonly dataState = signal<'open' | 'closed'>('closed')\n  private current: ScrollTarget | null = null\n  private exitTimer?: ReturnType<typeof setTimeout>\n  private ready = false\n  private readonly onScroll = () => this.check()\n\n  ngOnChanges(changes: SimpleChanges): void {\n    if (!this.ready) return\n    if (changes['target']) this.attach()\n    else if (changes['threshold']) this.check()\n  }\n\n  ngAfterViewInit(): void {\n    this.ready = true\n    this.attach()\n  }\n\n  ngOnDestroy(): void {\n    this.detach()\n    clearTimeout(this.exitTimer)\n  }\n\n  private resolveTarget(): ScrollTarget | null {\n    if (typeof window === 'undefined') return null\n    const t = this.target\n    if (t === undefined || t === null) return window\n    if (typeof t === 'string') return document.querySelector<HTMLElement>(t) ?? window\n    return t\n  }\n\n  private attach(): void {\n    this.detach()\n    this.current = this.resolveTarget()\n    const current = this.current\n    if (!current) return\n    current.addEventListener('scroll', this.onScroll, { passive: true })\n    if (current !== window) window.addEventListener('scroll', this.onScroll, { passive: true })\n    this.check()\n  }\n\n  private detach(): void {\n    const current = this.current\n    if (!current) return\n    current.removeEventListener('scroll', this.onScroll)\n    if (current !== window) window.removeEventListener('scroll', this.onScroll)\n    this.current = null\n  }\n\n  private scrollTop(el: ScrollTarget): number {\n    if (el === window) return window.scrollY ?? document.documentElement.scrollTop ?? document.body.scrollTop ?? 0\n    return (el as HTMLElement).scrollTop\n  }\n\n  private check(): void {\n    if (!this.current) return\n    const next = this.scrollTop(this.current) >= this.threshold\n    if (next === this.isVisible()) return\n    this.isVisible.set(next)\n    this.visible.emit(next)\n    clearTimeout(this.exitTimer)\n    if (next) {\n      this.mounted.set(true)\n      this.dataState.set('open')\n    } else if (this.mounted()) {\n      this.dataState.set('closed')\n      this.exitTimer = setTimeout(() => this.mounted.set(false), 200)\n    }\n  }\n\n  onClick(): void {\n    const el = this.current\n    if (!el) return\n    const reduce = typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches\n    el.scrollTo({ top: 0, behavior: reduce ? 'auto' : this.behavior })\n  }\n\n  get edge(): { left: string | null; right: string | null; top: string | null; bottom: string | null } {\n    const o = `${this.offset}px`\n    switch (this.position) {\n      case 'bottom-left':\n        return { left: o, bottom: o, right: null, top: null }\n      case 'top-right':\n        return { right: o, top: o, left: null, bottom: null }\n      case 'top-left':\n        return { left: o, top: o, right: null, bottom: null }\n      default:\n        return { right: o, bottom: o, left: null, top: null }\n    }\n  }\n\n  get buttonClass(): string {\n    return cn(\n      backTopVariants({ size: this.size, position: this.position }),\n      this.absolute ? 'absolute' : 'fixed',\n      this.className,\n    )\n  }\n}\n\nexport { backTopVariants, type BackTopVariants }\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/back-top/back-top.component.ts"
    },
    {
      "path": "packages/registry-angular/components/back-top/back-top.variants.ts",
      "content": "import type { VariantProps } from 'class-variance-authority'\nimport { cva } from 'class-variance-authority'\n\n/**\n * Variant definitions live in their own file (rather than the package\n * `index.ts`) so components can import `backTopVariants` from './back-top.variants' without creating a circular dependency through the index.\n */\nexport const backTopVariants = cva(\n  'inline-flex items-center justify-center rounded-full border bg-background text-foreground shadow-lg transition-[color,background-color,box-shadow] duration-200 hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none cursor-pointer motion-safe:data-[state=open]:animate-in motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=closed]:zoom-out-95 [&_svg:not([class*=size-])]:size-5 [&_svg]:pointer-events-none [&_svg]:shrink-0',\n  {\n    variants: {\n      size: {\n        sm: 'size-8 [&_svg:not([class*=size-])]:size-4',\n        default: 'size-10',\n        lg: 'size-12 [&_svg:not([class*=size-])]:size-6',\n      },\n      position: {\n        'bottom-right': '',\n        'bottom-left': '',\n        'top-right': '',\n        'top-left': '',\n      },\n    },\n    defaultVariants: {\n      size: 'default',\n      position: 'bottom-right',\n    },\n  },\n)\n\nexport type BackTopVariants = VariantProps<typeof backTopVariants>\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/back-top/back-top.variants.ts"
    },
    {
      "path": "packages/registry-angular/components/back-top/index.ts",
      "content": "export { UiBackTopComponent, backTopVariants } from './back-top.component'\nexport type { BackTopPosition, BackTopSize, BackTopVariants } from './back-top.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/back-top/index.ts"
    }
  ],
  "dependencies": [
    "class-variance-authority"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/popper.json"
  ],
  "description": "Scroll-to-top floating button. Appears after the target container scrolls past a threshold and smooth-scrolls back on click. Supports custom target container, visibility threshold, scroll behavior, custom icon slot, four edge anchors, size variants, and edge offset.",
  "categories": [
    "navigation",
    "utility"
  ]
}