"use client"
/**
* FAB catalog previews — composition patterns, not a second button primitive.
* Product Leo launcher: `LeoLauncherFab` → `AskLeoButton`.
*/
import * as React from "react"
import { AskLeoButton } from "@/components/ask-leo-button"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Tip } from "@/components/ui/tip"
import {
LEO_FAB_DEFAULT_OFFSET,
LEO_FAB_NUDGE,
LEO_FAB_NUDGE_COARSE,
clampLeoFabOffset,
leoFabOffsetFromKeyboard,
leoFabOffsetFromPointerDelta,
type LeoFabOffset,
} from "@/lib/leo-launcher-fab-geometry"
import { cn } from "@/lib/utils"
const DRAG_THRESHOLD_PX = 4
function FabStage({
children,
className,
label,
}: {
children: React.ReactNode
className?: string
label: string
}) {
return (
)
}
/** Brand circle — product Leo launcher chrome. */
export function FabBrandPreview() {
return (
undefined}
className="size-14 rounded-full shadow-[var(--shadow-sheet-panel)]"
/>
)
}
/** Outline circle for secondary floating actions. */
export function FabOutlinePreview() {
return (
)
}
/** Extended FAB with a visible label. */
export function FabExtendedPreview() {
return (
undefined}
className="h-14 rounded-full px-5 shadow-[var(--shadow-sheet-panel)]"
/>
)
}
/** Badge affordance for unread / pending count — solid count chip above the FAB. */
export function FabBadgePreview() {
return (
undefined}
className="size-14 overflow-visible rounded-full shadow-[var(--shadow-sheet-panel)]"
/>
3
)
}
/** Thinking — star in working motion while Leo is composing. */
export function FabThinkingPreview() {
return (
undefined}
className="size-14 rounded-full shadow-[var(--shadow-sheet-panel)]"
/>
)
}
const SUGGESTION_CARD_CLASS =
"group pointer-events-auto flex w-full max-w-[16rem] cursor-pointer items-start gap-2.5 rounded-xl border border-border/80 bg-background p-3 text-start text-[0.8125rem] leading-snug shadow-md transition-[border-color,box-shadow,background-color] hover:border-brand/35 hover:bg-interactive-hover/80 hover:shadow-lg"
/** Marketing-style suggestion card docked above the FAB. */
export function FabSuggestionPreview() {
return (
undefined}
className="size-14 rounded-full shadow-[var(--shadow-sheet-panel)]"
/>
)
}
/** Drag + keyboard move demo — same chords and drag as the product Leo launcher. */
export function FabKeyboardNudgePreview() {
const stageRef = React.useRef(null)
const [offset, setOffset] = React.useState(LEO_FAB_DEFAULT_OFFSET)
const [dragging, setDragging] = React.useState(false)
const [status, setStatus] = React.useState(
"Drag the FAB, or focus it and use arrow keys. Shift for a larger step. Home resets.",
)
const draggedRef = React.useRef(false)
const offsetRef = React.useRef(offset)
React.useEffect(() => {
offsetRef.current = offset
})
const hostSize = { width: 360, height: 160 }
const onKeyDown = (event: React.KeyboardEvent) => {
const next = leoFabOffsetFromKeyboard(
offset,
event.key,
event.shiftKey,
hostSize,
)
if (!next) return
event.preventDefault()
setOffset(next)
setStatus(
event.key === "Home"
? "Reset to the default corner."
: `Moved · end ${next.end}px · bottom ${next.bottom}px`,
)
}
const onPointerDownCapture = (event: React.PointerEvent) => {
if (event.button !== 0) return
const target = event.currentTarget
target.setPointerCapture(event.pointerId)
const startX = event.clientX
const startY = event.clientY
const startOffset = offsetRef.current
draggedRef.current = false
setDragging(true)
const onMove = (ev: PointerEvent) => {
const dx = ev.clientX - startX
const dy = ev.clientY - startY
if (Math.abs(dx) > DRAG_THRESHOLD_PX || Math.abs(dy) > DRAG_THRESHOLD_PX) {
draggedRef.current = true
}
if (!draggedRef.current) return
const next = leoFabOffsetFromPointerDelta(
startOffset,
dx,
dy,
hostSize,
false,
)
setOffset(next)
setStatus(`Dragged · end ${next.end}px · bottom ${next.bottom}px`)
}
const release = () => {
setDragging(false)
window.removeEventListener("pointermove", onMove)
window.removeEventListener("pointerup", release)
window.removeEventListener("pointercancel", release)
}
window.addEventListener("pointermove", onMove)
window.addEventListener("pointerup", release)
window.addEventListener("pointercancel", release)
}
const clamped = clampLeoFabOffset(offset, hostSize)
return (
{status} Fine step {LEO_FAB_NUDGE}px · coarse {LEO_FAB_NUDGE_COARSE}px.
Drag or keyboard
{
if (draggedRef.current) {
draggedRef.current = false
}
}}
className={cn(
"size-14 rounded-full shadow-[var(--shadow-sheet-panel)]",
dragging ? "cursor-grabbing" : "cursor-grab",
)}
/>
)
}
/** Default / outline / brand / extended row for anatomy. */
export function FabKindsPreview() {
return (
undefined}
className="size-14 rounded-full shadow-[var(--shadow-sheet-panel)]"
/>
undefined}
className="h-14 rounded-full px-5 shadow-[var(--shadow-sheet-panel)]"
/>
)
}