{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "FuseButton-TS-TW",
	"title": "FuseButton",
	"description": "Action button whose done state carries an undo on a burning fuse.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "FuseButton/FuseButton.tsx",
			"content": "'use client';\n\nimport React, { useEffect, useId, useRef, useState, type CSSProperties, type ReactNode } from 'react';\nimport { HugeiconsIcon } from '@hugeicons/react';\nimport { Archive02Icon, Tick02Icon, Undo02Icon } from '@hugeicons/core-free-icons';\n\nexport type FusePosition = 'outline' | 'bottom' | 'top';\nexport type FuseCommitOn = 'press' | 'fuseEnd';\nexport type FuseSettle = 'reset' | 'stay';\nexport type FusePhase = 'idle' | 'armed' | 'settled';\nexport type FuseSize = 'sm' | 'md' | 'lg';\n\nexport interface FuseButtonProps {\n  label?: string;\n  undoLabel?: string;\n  doneLabel?: string;\n  icon?: ReactNode;\n  color?: string;\n  background?: string;\n  fuseColor?: string;\n  size?: FuseSize;\n  radius?: number;\n  undoWindow?: number;\n  fuse?: FusePosition;\n  fuseThickness?: number;\n  crossfadeMs?: number;\n  commitOn?: FuseCommitOn;\n  pauseOnHover?: boolean;\n  settle?: FuseSettle;\n  disabled?: boolean;\n  onCommit?: (reason: FuseCommitOn) => void;\n  onUndo?: () => void;\n  onFuseEnd?: () => void;\n  onPhaseChange?: (phase: FusePhase) => void;\n  className?: string;\n  type?: 'button' | 'submit' | 'reset';\n}\n\ninterface Latest {\n  onCommit?: (reason: FuseCommitOn) => void;\n  onUndo?: () => void;\n  onFuseEnd?: () => void;\n  onPhaseChange?: (phase: FusePhase) => void;\n  commitOn: FuseCommitOn;\n  settle: FuseSettle;\n}\n\nconst LINE: Keyframe[] = [{ transform: 'scaleX(1)' }, { transform: 'scaleX(0)' }];\nconst OUTLINE: Keyframe[] = [{ strokeDashoffset: 0 }, { strokeDashoffset: -1 }];\nconst SIZES: Record<FuseSize, { height: number; font: number; icon: number; px: number }> = {\n  sm: { height: 36, font: 13, icon: 14, px: 16 },\n  md: { height: 44, font: 14, icon: 15, px: 20 },\n  lg: { height: 52, font: 15, icon: 17, px: 24 }\n};\n\nconst FuseButton: React.FC<FuseButtonProps> = ({\n  label = 'Archive',\n  undoLabel = 'Undo',\n  doneLabel = 'Archived',\n  icon,\n  color = '#f5f5f5',\n  background = '#27272a',\n  fuseColor = '#f5a524',\n  size = 'md',\n  radius = 22,\n  undoWindow = 4000,\n  fuse = 'outline',\n  fuseThickness = 1.5,\n  crossfadeMs = 200,\n  commitOn = 'press',\n  pauseOnHover = true,\n  settle = 'reset',\n  disabled = false,\n  onCommit,\n  onUndo,\n  onFuseEnd,\n  onPhaseChange,\n  className = '',\n  type = 'button'\n}) => {\n  const [phase, setPhase] = useState<FusePhase>('idle');\n  const [instant, setInstant] = useState(false);\n  const rootRef = useRef<HTMLSpanElement>(null);\n  const idleRef = useRef<HTMLButtonElement>(null);\n  const undoRef = useRef<HTMLButtonElement>(null);\n  const lineRef = useRef<HTMLElement>(null);\n  const rimRef = useRef<SVGRectElement>(null);\n  const anim = useRef<Animation | null>(null);\n  const pause = useRef({ hover: false, hidden: false, canHoverPause: false });\n  const lastInput = useRef<'pointer' | 'keyboard'>('pointer');\n  const windowRef = useRef(undoWindow);\n  const latest = useRef<Latest>({ commitOn, settle });\n  latest.current = { onCommit, onUndo, onFuseEnd, onPhaseChange, commitOn, settle };\n  const statusId = useId();\n  const preset = SIZES[size] || SIZES.md;\n\n  const go = (next: FusePhase) => {\n    setInstant(lastInput.current === 'keyboard');\n    setPhase(next);\n    latest.current.onPhaseChange?.(next);\n  };\n\n  const syncPlayState = () => {\n    const a = anim.current;\n    if (!a) return;\n    const { hover, hidden } = pause.current;\n    if (hover || hidden) {\n      if (a.playState === 'running') a.pause();\n    } else if (a.playState === 'paused') {\n      a.play();\n    }\n  };\n\n  const light = (from = 0) => {\n    const el = fuse === 'outline' ? rimRef.current : lineRef.current;\n    if (!el) return;\n    anim.current?.cancel();\n    const a = el.animate(fuse === 'outline' ? OUTLINE : LINE, {\n      duration: windowRef.current,\n      easing: 'linear',\n      fill: 'forwards'\n    });\n    if (from) a.currentTime = from;\n    a.onfinish = () => {\n      const l = latest.current;\n      l.onFuseEnd?.();\n      if (l.commitOn === 'fuseEnd') l.onCommit?.('fuseEnd');\n      lastInput.current = 'pointer';\n      go(l.settle === 'stay' ? 'settled' : 'idle');\n    };\n    anim.current = a;\n    syncPlayState();\n  };\n\n  const arm = () => {\n    if (disabled || phase !== 'idle') return;\n    windowRef.current = undoWindow;\n    light();\n    pause.current.canHoverPause = false;\n    pause.current.hover = false;\n    if (commitOn === 'press') onCommit?.('press');\n    go('armed');\n  };\n\n  const undo = () => {\n    if (phase !== 'armed') return;\n    const a = anim.current;\n    if (a) {\n      a.onfinish = null;\n      a.pause();\n    }\n    onUndo?.();\n    go('idle');\n  };\n\n  useEffect(() => {\n    const inside = rootRef.current?.contains(document.activeElement);\n    if (phase === 'armed') undoRef.current?.focus({ preventScroll: true });\n    else if (inside) (phase === 'idle' ? idleRef.current : rootRef.current)?.focus({ preventScroll: true });\n  }, [phase]);\n\n  useEffect(() => {\n    const onVisibility = () => {\n      pause.current.hidden = document.hidden;\n      syncPlayState();\n    };\n    document.addEventListener('visibilitychange', onVisibility);\n    return () => {\n      document.removeEventListener('visibilitychange', onVisibility);\n      anim.current?.cancel();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  useEffect(() => {\n    const a = anim.current;\n    if (!a || phase !== 'armed') return;\n    light(Number(a.currentTime) || 0);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [fuse]);\n\n  useEffect(() => {\n    if (pauseOnHover) return;\n    pause.current.hover = false;\n    syncPlayState();\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [pauseOnHover]);\n\n  const handlePointerDown = (e: React.PointerEvent<HTMLSpanElement>) => {\n    lastInput.current = 'pointer';\n    const pressable = phase === 'armed' || (phase === 'idle' && !disabled);\n    if (e.button === 0 && pressable && rootRef.current) rootRef.current.dataset.pressed = '';\n  };\n  const release = () => {\n    if (rootRef.current) delete rootRef.current.dataset.pressed;\n  };\n  const handlePointerEnter = (e: React.PointerEvent<HTMLSpanElement>) => {\n    if (pauseOnHover && e.pointerType === 'mouse' && pause.current.canHoverPause) {\n      pause.current.hover = true;\n      syncPlayState();\n    }\n  };\n  const handlePointerLeave = (e: React.PointerEvent<HTMLSpanElement>) => {\n    release();\n    if (e.pointerType !== 'mouse') return;\n    pause.current.canHoverPause = true;\n    pause.current.hover = false;\n    syncPlayState();\n  };\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {\n    if (e.key === 'Enter' || e.key === ' ') lastInput.current = 'keyboard';\n    if (e.key === 'Escape' && phase === 'armed') {\n      e.preventDefault();\n      lastInput.current = 'keyboard';\n      undo();\n    }\n  };\n\n  const actionIcon = icon ?? <HugeiconsIcon icon={Archive02Icon} size={preset.icon} strokeWidth={1.8} />;\n  const line = (\n    <i\n      ref={lineRef}\n      className=\"pointer-events-none absolute inset-x-0 bottom-0 h-[var(--fb-fuse-h)] origin-left [background:var(--fb-fuse)] [box-shadow:0_0_6px_color-mix(in_srgb,var(--fb-fuse)_55%,transparent)] group-data-[fuse=top]:top-0 group-data-[fuse=top]:bottom-auto forced-colors:[background:Highlight]\"\n      aria-hidden=\"true\"\n    />\n  );\n\n  return (\n    <span\n      ref={rootRef}\n      tabIndex={-1}\n      className={`group relative inline-grid h-[var(--fb-h)] grid-cols-[minmax(0,1fr)] isolate touch-manipulation select-none overflow-hidden rounded-[var(--fb-radius)] font-medium leading-none tracking-[0.01em] outline-none [font-family:inherit] [font-size:var(--fb-fs)] [background:var(--fb-bg)] [color:var(--fb-ink)] [-webkit-touch-callout:none] [-webkit-tap-highlight-color:transparent] [transition:transform_160ms_var(--fb-ease-out)] data-[pressed]:[transform:scale(var(--fb-press))] motion-reduce:data-[pressed]:[transform:none] has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-solid has-[:focus-visible]:outline-offset-[3px] has-[:focus-visible]:[outline-color:color-mix(in_srgb,var(--fb-ink)_60%,transparent)] data-[phase=idle]:has-[.fb-idle:disabled]:opacity-[0.55] contrast-more:[box-shadow:inset_0_0_0_1px_color-mix(in_srgb,var(--fb-ink)_40%,transparent)]${className ? ` ${className}` : ''}`}\n      data-phase={phase}\n      data-fuse={fuse}\n      data-instant={instant ? '' : undefined}\n      aria-disabled={phase === 'settled' || undefined}\n      style={\n        {\n          '--fb-ink': color,\n          '--fb-bg': background,\n          '--fb-fuse': fuseColor,\n          '--fb-fuse-h': `${fuseThickness}px`,\n          '--fb-radius': `${radius}px`,\n          '--fb-fade': `${crossfadeMs}ms`,\n          '--fb-h': `${preset.height}px`,\n          '--fb-fs': `${preset.font}px`,\n          '--fb-icon': `${preset.icon}px`,\n          '--fb-px': `${preset.px}px`,\n          '--fb-press': 0.97,\n          '--fb-ease-out': 'cubic-bezier(0.23, 1, 0.32, 1)'\n        } as CSSProperties\n      }\n      onPointerDown={handlePointerDown}\n      onPointerUp={release}\n      onPointerCancel={release}\n      onPointerEnter={handlePointerEnter}\n      onPointerLeave={handlePointerLeave}\n      onKeyDown={handleKeyDown}\n    >\n      <button\n        ref={idleRef}\n        type={type}\n        className=\"fb-idle relative [grid-area:1/1] inline-flex h-full w-full min-w-0 items-center justify-center gap-2 m-0 border-0 bg-transparent px-[var(--fb-px)] whitespace-nowrap cursor-pointer invisible opacity-0 [filter:blur(2px)] [color:inherit] [font:inherit] [letter-spacing:inherit] [transition:opacity_var(--fb-fade)_ease,filter_var(--fb-fade)_ease,visibility_0s_linear_var(--fb-fade),background-color_160ms_ease] motion-reduce:[filter:none] motion-reduce:[transition:opacity_var(--fb-fade)_ease,visibility_0s_linear_var(--fb-fade)] group-data-[instant]:[transition-duration:0s] group-data-[phase=idle]:visible group-data-[phase=idle]:opacity-100 group-data-[phase=idle]:[filter:blur(0)] group-data-[phase=idle]:[transition-delay:0s] disabled:cursor-default focus-visible:outline-none [@media(hover:hover)_and_(pointer:fine)]:enabled:hover:[background:color-mix(in_srgb,var(--fb-ink)_7%,transparent)]\"\n        disabled={disabled}\n        inert={phase !== 'idle'}\n        onClick={arm}\n      >\n        <span\n          className=\"inline-flex h-[var(--fb-icon)] w-[var(--fb-icon)] [&>svg]:h-full [&>svg]:w-full\"\n          aria-hidden=\"true\"\n        >\n          {actionIcon}\n        </span>\n        {label}\n      </button>\n      <button\n        ref={undoRef}\n        type=\"button\"\n        className=\"relative [grid-area:1/1] inline-flex h-full w-full min-w-0 items-center justify-center gap-2 m-0 border-0 bg-transparent px-[var(--fb-px)] whitespace-nowrap cursor-pointer invisible opacity-0 [filter:blur(2px)] [color:inherit] [font:inherit] [letter-spacing:inherit] [transition:opacity_var(--fb-fade)_ease,filter_var(--fb-fade)_ease,visibility_0s_linear_var(--fb-fade),background-color_160ms_ease] motion-reduce:[filter:none] motion-reduce:[transition:opacity_var(--fb-fade)_ease,visibility_0s_linear_var(--fb-fade)] group-data-[instant]:[transition-duration:0s] group-data-[phase=armed]:visible group-data-[phase=armed]:opacity-100 group-data-[phase=armed]:[filter:blur(0)] group-data-[phase=armed]:[transition-delay:0s] focus-visible:outline-none [@media(hover:hover)_and_(pointer:fine)]:hover:[background:color-mix(in_srgb,var(--fb-ink)_7%,transparent)]\"\n        aria-describedby={statusId}\n        aria-keyshortcuts=\"Escape\"\n        inert={phase !== 'armed'}\n        onClick={undo}\n      >\n        <span\n          className=\"inline-flex h-[var(--fb-icon)] w-[var(--fb-icon)] [&>svg]:h-full [&>svg]:w-full [transform:rotate(-70deg)] [transition:transform_var(--fb-fade)_var(--fb-ease-out)] group-data-[phase=armed]:[transform:rotate(0deg)] motion-reduce:transition-none motion-reduce:[transform:none]\"\n          aria-hidden=\"true\"\n        >\n          <HugeiconsIcon icon={Undo02Icon} size={preset.icon} strokeWidth={2} />\n        </span>\n        {undoLabel}\n        {fuse !== 'outline' ? line : null}\n      </button>\n      <span\n        className=\"relative [grid-area:1/1] inline-flex h-full w-full min-w-0 items-center justify-center gap-2 m-0 border-0 bg-transparent px-[var(--fb-px)] whitespace-nowrap cursor-pointer invisible opacity-0 [filter:blur(2px)] [color:inherit] [font:inherit] [letter-spacing:inherit] [transition:opacity_var(--fb-fade)_ease,filter_var(--fb-fade)_ease,visibility_0s_linear_var(--fb-fade),background-color_160ms_ease] motion-reduce:[filter:none] motion-reduce:[transition:opacity_var(--fb-fade)_ease,visibility_0s_linear_var(--fb-fade)] group-data-[instant]:[transition-duration:0s] group-data-[phase=settled]:visible group-data-[phase=settled]:opacity-100 group-data-[phase=settled]:[filter:blur(0)] group-data-[phase=settled]:[transition-delay:0s] cursor-default\"\n        inert={phase !== 'settled'}\n      >\n        <span\n          className=\"inline-flex h-[var(--fb-icon)] w-[var(--fb-icon)] [&>svg]:h-full [&>svg]:w-full\"\n          aria-hidden=\"true\"\n        >\n          <HugeiconsIcon icon={Tick02Icon} size={preset.icon} strokeWidth={2.2} />\n        </span>\n        {doneLabel}\n      </span>\n      {fuse === 'outline' ? (\n        <svg\n          className=\"pointer-events-none absolute inset-0 h-full w-full overflow-visible opacity-0 [filter:drop-shadow(0_0_3px_color-mix(in_srgb,var(--fb-fuse)_60%,transparent))] [transition:opacity_var(--fb-fade)_ease] group-data-[phase=armed]:opacity-100 group-data-[instant]:[transition-duration:0s]\"\n          aria-hidden=\"true\"\n        >\n          <rect\n            ref={rimRef}\n            pathLength=\"1\"\n            className=\"fill-none [x:calc(var(--fb-fuse-h)/2)] [y:calc(var(--fb-fuse-h)/2)] [width:calc(100%-var(--fb-fuse-h))] [height:calc(100%-var(--fb-fuse-h))] [rx:max(0px,calc(var(--fb-radius)-var(--fb-fuse-h)/2))] [stroke:var(--fb-fuse)] [stroke-width:var(--fb-fuse-h)] [stroke-linecap:round] [stroke-dasharray:1] forced-colors:[stroke:Highlight]\"\n          />\n        </svg>\n      ) : null}\n      <span className=\"sr-only\" id={statusId} role=\"status\" aria-live=\"polite\">\n        {phase === 'idle' ? '' : doneLabel}\n      </span>\n    </span>\n  );\n};\n\nexport default FuseButton;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"@hugeicons/react@^1.1.10",
		"@hugeicons/core-free-icons@^4.3.3"
	]
}