{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flux-button",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": [
    "@base-ui/react/button",
    "class-variance-authority",
    "lucide-react",
    "motion"
  ],
  "devDependencies": [],
  "files": [
    {
      "path": "flux-button.tsx",
      "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Loader2 } from \"lucide-react\";\nimport { animate, motion, useMotionValue } from \"motion/react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonThemeClassName =\n  \"[--button-destructive:#dc2626] [--button-input:#e3e7ec] [--button-primary-foreground:#ffffff] [--button-ring:rgba(17,17,17,0.16)] [--button-ripple:color-mix(in_oklch,var(--foreground),transparent_52%)] [--button-secondary:#eceff3] [--button-secondary-foreground:#111111] dark:[--button-destructive:#f87171] dark:[--button-input:#2b2a25] dark:[--button-primary-foreground:#111111] dark:[--button-ring:rgba(246,243,236,0.18)] dark:[--button-ripple:color-mix(in_oklch,var(--foreground),transparent_30%)] dark:[--button-secondary:#2a2a27] dark:[--button-secondary-foreground:#f6f3ec]\";\n\nconst buttonCornerClassName =\n  \"rounded-lg supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[11px]\";\n\nconst buttonCornerXsClassName =\n  \"rounded-[min(var(--radius-md),10px)] supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[12px]\";\n\nconst buttonCornerSmClassName =\n  \"rounded-[min(var(--radius-md),12px)] supports-[corner-shape:squircle]:corner-squircle supports-[corner-shape:squircle]:rounded-[14px]\";\n\nconst buttonGroupCornerClassName =\n  \"in-data-[slot=button-group]:rounded-lg in-data-[slot=button-group]:supports-[corner-shape:squircle]:corner-squircle in-data-[slot=button-group]:supports-[corner-shape:squircle]:rounded-[11px]\";\n\nconst SUCCESS_HOLD_MS = 1000;\n\nconst FLUID_WIDTH = {\n  type: \"spring\" as const,\n  stiffness: 210,\n  damping: 28,\n  mass: 1.08,\n};\n\nconst FLUID_CONTENT = {\n  type: \"spring\" as const,\n  stiffness: 340,\n  damping: 32,\n  mass: 0.92,\n};\n\nconst FLUID_ICON = {\n  type: \"spring\" as const,\n  stiffness: 400,\n  damping: 30,\n  mass: 0.76,\n};\n\nconst FLUID_REVEAL = {\n  type: \"spring\" as const,\n  stiffness: 260,\n  damping: 30,\n  mass: 0.98,\n};\n\nconst FLUID_TAP = {\n  type: \"spring\" as const,\n  stiffness: 520,\n  damping: 34,\n  mass: 0.72,\n};\n\nconst NO_TRANSITION = { duration: 0 } as const;\n\ntype FlowPhase = \"idle\" | \"loading\" | \"success\" | \"shrinking\";\n\nfunction getWidthTarget(\n  phase: FlowPhase,\n  widths: { idle: number; loading: number; success: number }\n) {\n  switch (phase) {\n    case \"loading\":\n      return widths.loading;\n    case \"success\":\n      return widths.success;\n    default:\n      return widths.idle;\n  }\n}\n\nconst SIZE_METRICS = {\n  xs: { height: 24, paddingX: 8 },\n  sm: { height: 28, paddingX: 10 },\n  default: { height: 32, paddingX: 10 },\n  lg: { height: 40, paddingX: 16 },\n} as const;\n\nconst LINK_PADDING_X = {\n  xs: 6,\n  sm: 8,\n  default: 8,\n  lg: 10,\n} as const;\n\nconst ICON_SLOT_CLASS = {\n  xs: \"size-3\",\n  sm: \"size-3.5\",\n  default: \"size-4\",\n  lg: \"size-4\",\n} as const;\n\nconst BUSY_GAP_CLASS = {\n  xs: \"gap-1\",\n  sm: \"gap-1\",\n  default: \"gap-1.5\",\n  lg: \"gap-1.5\",\n} as const;\n\nconst SHRINK_FALLBACK_MS = 900;\n\nconst fluxButtonVariants = cva(\n  cn(\n    buttonThemeClassName,\n    buttonCornerClassName,\n    \"relative inline-flex shrink-0 cursor-pointer touch-manipulation select-none items-center justify-center overflow-hidden whitespace-nowrap border border-transparent bg-clip-padding font-medium text-sm no-underline outline-none [-webkit-tap-highlight-color:transparent] focus-visible:border-[color:var(--button-ring)] focus-visible:ring-3 focus-visible:ring-[color:color-mix(in_oklch,var(--button-ring),transparent_50%)] active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\"\n  ),\n  {\n    variants: {\n      variant: {\n        default:\n          \"visited:!text-primary-foreground hover:!text-primary-foreground focus-visible:!text-primary-foreground active:!text-primary-foreground bg-primary text-primary-foreground hover:bg-primary dark:[--button-ripple:color-mix(in_oklch,#111111,transparent_10%)] [&_svg]:text-primary-foreground\",\n        outline:\n          \"visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground active:!text-foreground border-border bg-background text-foreground hover:bg-accent/60 dark:border-[color:var(--button-input)] dark:bg-[color-mix(in_oklch,var(--button-input),transparent_70%)] dark:hover:bg-[color-mix(in_oklch,var(--button-input),transparent_50%)]\",\n        secondary:\n          \"visited:!text-[color:var(--button-secondary-foreground)] hover:!text-[color:var(--button-secondary-foreground)] focus-visible:!text-[color:var(--button-secondary-foreground)] active:!text-[color:var(--button-secondary-foreground)] bg-[color:var(--button-secondary)] text-[color:var(--button-secondary-foreground)] hover:bg-[color-mix(in_oklch,var(--button-secondary),var(--foreground)_7%)] dark:hover:bg-[color-mix(in_oklch,var(--button-secondary),var(--foreground)_11%)] [&_svg]:text-[color:var(--button-secondary-foreground)]\",\n        ghost:\n          \"visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground active:!text-foreground text-foreground hover:bg-accent/60 dark:hover:bg-muted/50\",\n        destructive:\n          \"visited:!text-[color:var(--button-destructive)] hover:!text-[color:var(--button-destructive)] focus-visible:!text-[color:var(--button-destructive)] active:!text-[color:var(--button-destructive)] bg-[color-mix(in_oklch,var(--button-destructive),transparent_90%)] text-[color:var(--button-destructive)] hover:bg-[color-mix(in_oklch,var(--button-destructive),transparent_80%)] focus-visible:border-[color:color-mix(in_oklch,var(--button-destructive),transparent_60%)] focus-visible:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_80%)] dark:bg-[color-mix(in_oklch,var(--button-destructive),transparent_80%)] dark:focus-visible:ring-[color:color-mix(in_oklch,var(--button-destructive),transparent_60%)] dark:hover:bg-[color-mix(in_oklch,var(--button-destructive),transparent_70%)]\",\n        link: \"visited:!text-foreground hover:!text-foreground focus-visible:!text-foreground text-foreground underline-offset-4 shadow-none hover:underline active:translate-y-0\",\n      },\n      size: {\n        default:\n          \"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: cn(\n          \"h-6 gap-1 px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n          buttonGroupCornerClassName,\n          buttonCornerXsClassName\n        ),\n        sm: cn(\n          \"h-7 gap-1 px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n          buttonGroupCornerClassName,\n          buttonCornerSmClassName\n        ),\n        lg: \"h-10 gap-1.5 px-4 text-base leading-5 has-data-[icon=inline-end]:pr-3.5 has-data-[icon=inline-start]:pl-3.5\",\n      },\n    },\n    compoundVariants: [\n      {\n        variant: \"link\",\n        size: \"default\",\n        class: \"h-8 px-2 text-sm leading-5\",\n      },\n      {\n        variant: \"link\",\n        size: \"lg\",\n        class: \"h-10 px-2.5 text-base leading-5\",\n      },\n      {\n        variant: \"link\",\n        size: \"sm\",\n        class: \"h-7 px-2 text-[0.8rem]\",\n      },\n      {\n        variant: \"link\",\n        size: \"xs\",\n        class: \"h-6 px-1.5 text-xs\",\n      },\n    ],\n    defaultVariants: {\n      size: \"default\",\n      variant: \"default\",\n    },\n  }\n);\n\ntype ButtonHTMLAttributesForMotion = Omit<\n  React.ButtonHTMLAttributes<HTMLButtonElement>,\n  | \"onAnimationEnd\"\n  | \"onAnimationIteration\"\n  | \"onAnimationStart\"\n  | \"onDrag\"\n  | \"onDragEnd\"\n  | \"onDragEnter\"\n  | \"onDragExit\"\n  | \"onDragLeave\"\n  | \"onDragOver\"\n  | \"onDragStart\"\n  | \"onDrop\"\n>;\n\ntype ButtonRenderProps = React.HTMLAttributes<HTMLButtonElement> & {\n  className?: string;\n  ref?: React.Ref<HTMLButtonElement>;\n  style?: React.CSSProperties;\n};\n\nfunction setRef<T>(ref: React.Ref<T> | undefined, value: T) {\n  if (typeof ref === \"function\") {\n    ref(value);\n    return;\n  }\n\n  if (ref) {\n    (ref as React.MutableRefObject<T>).current = value;\n  }\n}\n\nfunction resolvePrimitiveButtonProps(buttonProps: ButtonRenderProps) {\n  const {\n    children: _buttonChildren,\n    className: primitiveClassName,\n    onAnimationEnd: _onAnimationEnd,\n    onAnimationIteration: _onAnimationIteration,\n    onAnimationStart: _onAnimationStart,\n    onDrag: _onDrag,\n    onDragEnd: _onDragEnd,\n    onDragEnter: _onDragEnter,\n    onDragExit: _onDragExit,\n    onDragLeave: _onDragLeave,\n    onDragOver: _onDragOver,\n    onDragStart: _onDragStart,\n    onDrop: _onDrop,\n    ref: primitiveRef,\n    style: primitiveStyle,\n    ...resolvedButtonProps\n  } = buttonProps;\n\n  return {\n    primitiveClassName,\n    primitiveRef,\n    primitiveStyle,\n    resolvedButtonProps,\n  };\n}\n\ntype FluxButtonProps = Omit<ButtonHTMLAttributesForMotion, \"type\"> &\n  VariantProps<typeof fluxButtonVariants> & {\n    idleLabel: string;\n    loadingLabel: string;\n    successLabel: string;\n    successIcon?: React.ReactNode;\n    onAction: () => void | Promise<void>;\n    successHold?: number;\n    type?: \"button\" | \"submit\";\n  };\n\nfunction getLayoutMetrics(\n  size: NonNullable<FluxButtonProps[\"size\"]>,\n  variant: NonNullable<FluxButtonProps[\"variant\"]>\n) {\n  const metrics = SIZE_METRICS[size];\n\n  return {\n    height: metrics.height,\n    paddingX: variant === \"link\" ? LINK_PADDING_X[size] : metrics.paddingX,\n  };\n}\n\nfunction useMeasuredWidths(paddingX: number, minWidth: number) {\n  const measureRef = React.useRef<HTMLDivElement>(null);\n  const [widths, setWidths] = React.useState({\n    idle: minWidth,\n    loading: minWidth,\n    success: minWidth,\n  });\n\n  React.useLayoutEffect(() => {\n    const root = measureRef.current;\n    if (!root) {\n      return;\n    }\n\n    const readWidth = (selector: string) => {\n      const node = root.querySelector<HTMLElement>(selector);\n      return Math.max(minWidth, (node?.offsetWidth ?? 0) + paddingX * 2);\n    };\n\n    const measure = () => {\n      setWidths({\n        idle: readWidth('[data-width=\"idle\"]'),\n        loading: readWidth('[data-width=\"loading\"]'),\n        success: readWidth('[data-width=\"success\"]'),\n      });\n    };\n\n    measure();\n\n    const resizeObserver = new ResizeObserver(measure);\n    resizeObserver.observe(root);\n\n    return () => resizeObserver.disconnect();\n  }, [minWidth, paddingX]);\n\n  return { measureRef, widths };\n}\n\ntype FlowContentProps = {\n  animateEnabled: boolean;\n  labels: { idle: string; loading: string; success: string };\n  phase: FlowPhase;\n  size: NonNullable<FluxButtonProps[\"size\"]>;\n  successIcon?: React.ReactNode;\n};\n\nfunction useFlowContentMotion(animateEnabled: boolean, phase: FlowPhase) {\n  const motionEnabled = animateEnabled;\n  const contentTransition = motionEnabled ? FLUID_CONTENT : NO_TRANSITION;\n  const iconTransition = motionEnabled ? FLUID_ICON : NO_TRANSITION;\n  const shrinkTransition = motionEnabled ? FLUID_REVEAL : NO_TRANSITION;\n  const isShrinking = phase === \"shrinking\";\n\n  return {\n    contentTransition,\n    iconTransition,\n    idleTransition: isShrinking ? shrinkTransition : contentTransition,\n    busyTransition: isShrinking ? NO_TRANSITION : contentTransition,\n  };\n}\n\nfunction FlowIdleLayer({\n  idleLabel,\n  idleOpacity,\n  idleTransition,\n  isIdle,\n  isShrinking,\n}: {\n  idleLabel: string;\n  idleOpacity: number;\n  idleTransition:\n    | typeof FLUID_CONTENT\n    | typeof FLUID_REVEAL\n    | typeof NO_TRANSITION;\n  isIdle: boolean;\n  isShrinking: boolean;\n}) {\n  return (\n    <motion.span\n      animate={{ opacity: idleOpacity }}\n      aria-hidden={!(isIdle || isShrinking)}\n      className=\"absolute inset-0 inline-flex items-center justify-center whitespace-nowrap\"\n      initial={false}\n      style={{ pointerEvents: \"none\", zIndex: idleOpacity > 0.5 ? 1 : 0 }}\n      transition={idleTransition}\n    >\n      {idleLabel}\n    </motion.span>\n  );\n}\n\nfunction FlowIconSlot({\n  iconSlotClassName,\n  iconTransition,\n  isLoading,\n  isSuccess,\n  showSuccessIcon,\n  successIcon,\n}: {\n  iconSlotClassName: string;\n  iconTransition: typeof FLUID_ICON | typeof NO_TRANSITION;\n  isLoading: boolean;\n  isSuccess: boolean;\n  showSuccessIcon: boolean;\n  successIcon?: React.ReactNode;\n}) {\n  return (\n    <span\n      className={cn(\"relative shrink-0 [&_svg]:size-[1em]\", iconSlotClassName)}\n    >\n      <motion.span\n        animate={{\n          opacity: isLoading ? 1 : 0,\n          rotate: isLoading ? 0 : 72,\n          scale: isLoading ? 1 : 0.82,\n        }}\n        aria-hidden={!isLoading}\n        className=\"absolute inset-0 inline-flex items-center justify-center\"\n        initial={false}\n        transition={iconTransition}\n      >\n        <Loader2\n          aria-hidden\n          className={cn(iconSlotClassName, isLoading && \"animate-spin\")}\n        />\n      </motion.span>\n\n      {showSuccessIcon ? (\n        <motion.span\n          animate={{\n            opacity: isSuccess ? 1 : 0,\n            rotate: isSuccess ? 0 : -72,\n            scale: isSuccess ? 1 : 0.82,\n          }}\n          aria-hidden={!isSuccess}\n          className=\"absolute inset-0 inline-flex items-center justify-center\"\n          initial={false}\n          transition={iconTransition}\n        >\n          {successIcon}\n        </motion.span>\n      ) : null}\n    </span>\n  );\n}\n\nfunction FlowBusyLabels({\n  contentTransition,\n  isLoading,\n  isSuccess,\n  loadingLabel,\n  successLabel,\n}: {\n  contentTransition: typeof FLUID_CONTENT | typeof NO_TRANSITION;\n  isLoading: boolean;\n  isSuccess: boolean;\n  loadingLabel: string;\n  successLabel: string;\n}) {\n  return (\n    <span className=\"relative inline-grid [grid-template-columns:max-content] [grid-template-rows:1.25em]\">\n      <motion.span\n        animate={{ opacity: isLoading ? 1 : 0 }}\n        aria-hidden={!isLoading}\n        className=\"col-start-1 row-start-1 inline-flex items-center whitespace-nowrap\"\n        initial={false}\n        style={{ zIndex: isLoading ? 1 : 0 }}\n        transition={contentTransition}\n      >\n        {loadingLabel}\n      </motion.span>\n      <motion.span\n        animate={{ opacity: isSuccess ? 1 : 0 }}\n        aria-hidden={!isSuccess}\n        className=\"col-start-1 row-start-1 inline-flex items-center whitespace-nowrap\"\n        initial={false}\n        style={{ zIndex: isSuccess ? 1 : 0 }}\n        transition={contentTransition}\n      >\n        {successLabel}\n      </motion.span>\n    </span>\n  );\n}\n\nfunction FlowContent({\n  animateEnabled,\n  labels,\n  phase,\n  size,\n  successIcon,\n}: FlowContentProps) {\n  const { busyTransition, contentTransition, iconTransition, idleTransition } =\n    useFlowContentMotion(animateEnabled, phase);\n\n  const isIdle = phase === \"idle\";\n  const isShrinking = phase === \"shrinking\";\n  const isLoading = phase === \"loading\";\n  const isSuccess = phase === \"success\";\n  const isBusy = isLoading || isSuccess;\n  const showSuccessIcon = Boolean(successIcon);\n  const showIconSlot = isLoading || (isSuccess && showSuccessIcon);\n  const iconSlotClassName = ICON_SLOT_CLASS[size];\n  const busyGapClassName = BUSY_GAP_CLASS[size];\n  const idleOpacity = isIdle || isShrinking ? 1 : 0;\n  const busyOpacity = isBusy ? 1 : 0;\n\n  return (\n    <span className=\"relative h-[1.25em] w-full leading-none\">\n      <FlowIdleLayer\n        idleLabel={labels.idle}\n        idleOpacity={idleOpacity}\n        idleTransition={idleTransition}\n        isIdle={isIdle}\n        isShrinking={isShrinking}\n      />\n\n      <motion.span\n        animate={{ opacity: busyOpacity }}\n        aria-hidden={!isBusy}\n        className={cn(\n          \"absolute inset-0 inline-flex items-center justify-center whitespace-nowrap\",\n          showIconSlot && busyGapClassName\n        )}\n        initial={false}\n        style={{ pointerEvents: \"none\", zIndex: busyOpacity > 0.5 ? 1 : 0 }}\n        transition={busyTransition}\n      >\n        {showIconSlot ? (\n          <FlowIconSlot\n            iconSlotClassName={iconSlotClassName}\n            iconTransition={iconTransition}\n            isLoading={isLoading}\n            isSuccess={isSuccess}\n            showSuccessIcon={showSuccessIcon}\n            successIcon={successIcon}\n          />\n        ) : null}\n\n        <FlowBusyLabels\n          contentTransition={contentTransition}\n          isLoading={isLoading}\n          isSuccess={isSuccess}\n          loadingLabel={labels.loading}\n          successLabel={labels.success}\n        />\n      </motion.span>\n    </span>\n  );\n}\n\nconst FluxButton = React.forwardRef<HTMLButtonElement, FluxButtonProps>(\n  (\n    {\n      className,\n      disabled = false,\n      idleLabel,\n      loadingLabel,\n      onAction,\n      onClick,\n      onKeyDown,\n      onPointerDown,\n      size = \"default\",\n      successHold = SUCCESS_HOLD_MS,\n      successIcon,\n      successLabel,\n      type = \"button\",\n      variant = \"default\",\n      ...props\n    },\n    ref\n  ) => {\n    const resetTimeoutRef = React.useRef<number | null>(null);\n    const widthAnimationRef = React.useRef<ReturnType<typeof animate> | null>(\n      null\n    );\n    const mountedRef = React.useRef(true);\n    const phaseRef = React.useRef<FlowPhase>(\"idle\");\n    const actionGenerationRef = React.useRef(0);\n    const onActionRef = React.useRef(onAction);\n    const [hasInteracted, setHasInteracted] = React.useState(false);\n    const [phase, setPhase] = React.useState<FlowPhase>(\"idle\");\n\n    const resolvedSize = size ?? \"default\";\n    const resolvedVariant = variant ?? \"default\";\n    const layout = getLayoutMetrics(resolvedSize, resolvedVariant);\n    const initialWidth: number = layout.height;\n    const widthMv = useMotionValue(initialWidth);\n    const iconSlotClassName = ICON_SLOT_CLASS[resolvedSize];\n    const busyGapClassName = BUSY_GAP_CLASS[resolvedSize];\n    const resolvedSuccessHold = Math.max(0, successHold);\n\n    phaseRef.current = phase;\n    onActionRef.current = onAction;\n\n    const safeSetPhase = React.useCallback((nextPhase: FlowPhase) => {\n      if (mountedRef.current) {\n        setPhase(nextPhase);\n      }\n    }, []);\n\n    const labels = React.useMemo(\n      () => ({\n        idle: idleLabel,\n        loading: loadingLabel,\n        success: successLabel,\n      }),\n      [idleLabel, loadingLabel, successLabel]\n    );\n\n    const { measureRef, widths } = useMeasuredWidths(\n      layout.paddingX,\n      layout.height\n    );\n\n    const animateEnabled = hasInteracted;\n    const isBusy = phase !== \"idle\";\n    const isLocked = Boolean(disabled || isBusy);\n    const buttonClassName = cn(\n      fluxButtonVariants({ size, variant }),\n      isBusy && \"pointer-events-none\",\n      className\n    );\n\n    React.useLayoutEffect(() => {\n      if (phase !== \"idle\" && hasInteracted) {\n        return;\n      }\n\n      widthMv.set(widths.idle);\n    }, [hasInteracted, phase, widthMv, widths.idle]);\n\n    React.useEffect(() => {\n      const target = getWidthTarget(phase, widths);\n\n      widthAnimationRef.current?.stop();\n\n      const finishShrinking = () => {\n        if (phaseRef.current === \"shrinking\") {\n          safeSetPhase(\"idle\");\n        }\n      };\n\n      if (Math.abs(widthMv.get() - target) < 0.5) {\n        widthMv.set(target);\n        finishShrinking();\n        return;\n      }\n\n      if (!animateEnabled) {\n        widthMv.set(target);\n        finishShrinking();\n        return;\n      }\n\n      widthAnimationRef.current = animate(widthMv, target, {\n        ...FLUID_WIDTH,\n        velocity: widthMv.getVelocity(),\n      });\n\n      if (phase !== \"shrinking\") {\n        return;\n      }\n\n      let cancelled = false;\n\n      widthAnimationRef.current\n        .then(() => {\n          if (!cancelled) {\n            finishShrinking();\n          }\n        })\n        .catch(() => undefined);\n\n      return () => {\n        cancelled = true;\n        widthAnimationRef.current?.stop();\n      };\n    }, [animateEnabled, phase, safeSetPhase, widthMv, widths]);\n\n    React.useEffect(() => {\n      if (phase !== \"shrinking\") {\n        return;\n      }\n\n      const fallback = window.setTimeout(() => {\n        safeSetPhase(\"idle\");\n      }, SHRINK_FALLBACK_MS);\n\n      return () => window.clearTimeout(fallback);\n    }, [phase, safeSetPhase]);\n\n    React.useEffect(() => {\n      mountedRef.current = true;\n\n      return () => {\n        mountedRef.current = false;\n\n        if (resetTimeoutRef.current !== null) {\n          window.clearTimeout(resetTimeoutRef.current);\n        }\n\n        widthAnimationRef.current?.stop();\n      };\n    }, []);\n\n    const beginShrink = React.useCallback(() => {\n      safeSetPhase(\"shrinking\");\n    }, [safeSetPhase]);\n\n    const runAction = React.useCallback(async () => {\n      if (phaseRef.current !== \"idle\" || disabled) {\n        return;\n      }\n\n      const generation = actionGenerationRef.current + 1;\n      actionGenerationRef.current = generation;\n\n      if (resetTimeoutRef.current !== null) {\n        window.clearTimeout(resetTimeoutRef.current);\n        resetTimeoutRef.current = null;\n      }\n\n      setHasInteracted(true);\n      safeSetPhase(\"loading\");\n\n      try {\n        await onActionRef.current();\n\n        if (!mountedRef.current || actionGenerationRef.current !== generation) {\n          return;\n        }\n\n        safeSetPhase(\"success\");\n\n        resetTimeoutRef.current = window.setTimeout(() => {\n          resetTimeoutRef.current = null;\n\n          if (\n            !mountedRef.current ||\n            actionGenerationRef.current !== generation\n          ) {\n            return;\n          }\n\n          beginShrink();\n        }, resolvedSuccessHold);\n      } catch {\n        if (!mountedRef.current || actionGenerationRef.current !== generation) {\n          return;\n        }\n\n        beginShrink();\n      }\n    }, [beginShrink, disabled, resolvedSuccessHold, safeSetPhase]);\n\n    return (\n      <>\n        <div\n          aria-hidden\n          className={cn(\n            buttonClassName,\n            \"pointer-events-none invisible absolute -z-10 inline-flex flex-col whitespace-nowrap\"\n          )}\n          ref={measureRef}\n        >\n          <span data-width=\"idle\">{labels.idle}</span>\n          <span\n            className={cn(\"inline-flex items-center\", busyGapClassName)}\n            data-width=\"loading\"\n          >\n            <span className={cn(\"inline-flex shrink-0\", iconSlotClassName)} />\n            <span>{labels.loading}</span>\n          </span>\n          <span\n            className={cn(\n              \"inline-flex items-center\",\n              successIcon && busyGapClassName\n            )}\n            data-width=\"success\"\n          >\n            {successIcon ? (\n              <span className={cn(\"inline-flex shrink-0\", iconSlotClassName)} />\n            ) : null}\n            <span>{labels.success}</span>\n          </span>\n        </div>\n\n        <ButtonPrimitive\n          {...props}\n          aria-busy={phase === \"loading\" || undefined}\n          aria-disabled={isLocked || undefined}\n          aria-live=\"polite\"\n          className={buttonClassName}\n          data-slot=\"button\"\n          disabled={Boolean(disabled)}\n          onClick={(event) => {\n            onClick?.(event);\n            if (event.defaultPrevented || isLocked) {\n              return;\n            }\n            event.preventDefault();\n            runAction().catch(() => undefined);\n          }}\n          onKeyDown={(event) => {\n            onKeyDown?.(event);\n            if (\n              event.defaultPrevented ||\n              isLocked ||\n              event.repeat ||\n              (event.key !== \" \" && event.key !== \"Enter\")\n            ) {\n              return;\n            }\n            if (event.key === \" \") {\n              event.preventDefault();\n            }\n            runAction().catch(() => undefined);\n          }}\n          onPointerDown={onPointerDown}\n          render={(buttonProps) => {\n            const {\n              primitiveClassName,\n              primitiveRef,\n              primitiveStyle,\n              resolvedButtonProps,\n            } = resolvePrimitiveButtonProps(buttonProps);\n\n            return (\n              <motion.button\n                {...resolvedButtonProps}\n                className={primitiveClassName}\n                ref={(node) => {\n                  setRef(primitiveRef, node);\n                  setRef(ref, node);\n                }}\n                style={{\n                  ...primitiveStyle,\n                  height: layout.height,\n                  minWidth: layout.height,\n                  width: widthMv,\n                }}\n                transition={FLUID_TAP}\n                type={type}\n                whileTap={\n                  isLocked || !animateEnabled || variant === \"link\"\n                    ? undefined\n                    : { scale: 0.96 }\n                }\n              >\n                <span className=\"relative z-10 inline-flex w-full items-center justify-center\">\n                  <FlowContent\n                    animateEnabled={hasInteracted}\n                    labels={labels}\n                    phase={phase}\n                    size={resolvedSize}\n                    successIcon={successIcon}\n                  />\n                </span>\n              </motion.button>\n            );\n          }}\n          type={type}\n        />\n      </>\n    );\n  }\n);\nFluxButton.displayName = \"FluxButton\";\n\nexport { FluxButton, fluxButtonVariants };\nexport type { FluxButtonProps };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Flux Button",
  "description": "Async button with idle, loading, and success states."
}
