"use client"
/**
* AskLeoButton — chart-card Leo CTA (outline sm, duotone star + label).
*
* Default `onClick` toggles the Ask Leo sidebar (`useAskLeo`). Pass `onClick`
* to override (e.g. inline draft on the new-question composer).
*
* @see charts-overview.tsx — ChartCard header (canonical usage)
* @see AGENTS.md / charts-overview file header — Ask Leo icon guideline
*/
import * as React from "react"
import { AskLeoShortcutKbds, useAskLeo } from "@/components/ask-leo-context"
import { Button } from "@/components/ui/button"
import {
LeoIcon,
type LeoIconMotionState,
type LeoIconSize,
} from "@/components/ui/leo-icon"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
export interface AskLeoButtonProps {
/** Matches chart card header (`sm`) or page header actions (`lg`, e.g. Save question). */
size?: "sm" | "lg"
/** When true, hides the text label (chart selector variant). */
iconOnly?: boolean
/** Button label. Default `"Ask Leo"`. */
label?: string
/** Overrides sidebar toggle — use for route-local Leo actions. */
onClick?: () => void
disabled?: boolean
"aria-busy"?: boolean
/** Accessible name; defaults to `label`. */
ariaLabel?: string
/** Shown instead of `label` when `aria-busy` is true. */
busyLabel?: string
/** Tooltip body before shortcut chips. Defaults to `label`. */
tooltipLabel?: string
/** When false, tooltip omits ⌘⌥K hints (non-sidebar actions). Default true. */
showShortcut?: boolean
/**
* Animated Leo star (`LeoIcon` ambient). Defaults to true when `size="lg"`.
* Chart cards keep the static duotone glyph (`size="sm"`).
*/
animatedStar?: boolean
/**
* Play one `invited` gesture when the button appears, then settle to `rest`.
* For entry points that arrive unannounced (the floating launcher) rather
* than ones the user already sees in a toolbar.
*/
introduceOnMount?: boolean
/**
* `brand` fills the surface with the brand colour and flips the star to
* `--brand-foreground`. Paired here rather than left to callers so the star
* can never end up brand-on-brand.
*/
tone?: "default" | "brand"
/** Star box. Defaults to `xs` (20px), which fits the `sm` / `lg` buttons. */
starSize?: LeoIconSize
className?: string
}
export function AskLeoButton({
size = "sm",
iconOnly = false,
label = "Ask Leo",
onClick: onClickProp,
disabled = false,
"aria-busy": ariaBusy,
ariaLabel,
busyLabel,
tooltipLabel,
showShortcut = true,
animatedStar,
introduceOnMount = false,
tone = "default",
starSize = "xs",
className,
}: AskLeoButtonProps) {
const { toggle } = useAskLeo()
const displayLabel = ariaBusy && busyLabel ? busyLabel : label
const tipText = tooltipLabel ?? label
const useAnimatedStar = animatedStar ?? size === "lg"
// Dropping the label is not enough to make a square trigger: the text sizes
// keep their inline padding, which left `lg` iconOnly as a 70x40 pill with a
// floating star. Switch to the aspect-square icon sizes instead.
const buttonSize = iconOnly ? (size === "lg" ? "icon-lg" : "icon-sm") : size
const leoFillClass =
tone === "brand"
? // On a brand fill the star has to be the on-brand foreground, not a
// brand shade — brand-on-brand would disappear.
"[--leo-icon-fill:var(--brand-foreground)]"
: "[--leo-icon-fill:var(--brand-color-dark)] dark:[--leo-icon-fill:var(--brand-color-light)]"
const toneClass =
tone === "brand"
? "border-transparent bg-brand text-brand-foreground hover:bg-brand/90 hover:text-brand-foreground dark:bg-brand dark:hover:bg-brand/90"
: undefined
// Pointer and keyboard both count as intent, so the star reacts for keyboard
// users too rather than rewarding only the mouse.
const [invited, setInvited] = React.useState(false)
// `invited` is a one-shot gesture, so playing it once means holding the state
// for its duration and then settling. Slightly longer than LEO_GESTURE (0.42s)
// so the keyframes finish before the state changes under them.
const [introducing, setIntroducing] = React.useState(introduceOnMount)
React.useEffect(() => {
if (!introducing) return
const timer = window.setTimeout(() => setIntroducing(false), 520)
return () => window.clearTimeout(timer)
}, [introducing])
const leoState: LeoIconMotionState = ariaBusy
? "working"
: invited || introducing
? "invited"
: "rest"
const starMark = useAnimatedStar ? (
) : (
)
return (
{tipText}
{showShortcut ? : null}
)
}