import { submitState } from "./submit-state"; import { type ComponentPropsWithRef, useState } from "react"; import { AnimatePresence, m as motion } from "motion/react"; import { cn } from "../../lib/utils"; import { IconCheckMicro, IconListTodoMicro, IconLoader, IconMicMicro, IconSendMicro, IconStopFilledMicro, } from "../../icons"; const EASE_IN: [number, number, number, number] = [0.55, 0, 0.85, 0.2]; const NONE = { initial: {}, animate: {}, exit: {} } as const; const SLIDE = { initial: { y: "120%", opacity: 0 }, animate: { y: 0, opacity: 1, transition: { y: { type: "spring", stiffness: 520, damping: 30, mass: 0.6, delay: 0.12 }, opacity: { duration: 0.15, delay: 0.12 }, }, }, exit: { y: "-120%", opacity: 0, transition: { duration: 0.22, ease: EASE_IN }, }, } as const; type SubmitProps = Omit, "children" | "onClick"> & { /** Whether the input is empty (controls mic vs send icon). */ empty?: boolean; /** Whether the input is ready to submit. */ ready?: boolean; /** Whether the composer is in recording mode. */ recording?: boolean; /** Whether the composer is saving an edited message. */ editing?: boolean; /** Whether submission is in progress. */ busy?: boolean; /** Whether the composer is in plan mode (swaps paper plane for a checklist icon). */ plan?: boolean; /** Called when the mic button is clicked to start recording. */ onRecord?: () => void; /** Called when the stop button is clicked to stop recording. */ onStop?: () => void; /** Called when the send button is clicked to submit. */ onSend?: () => void; }; const ICONS = { mic: IconMicMicro, send: IconSendMicro, plan: IconListTodoMicro, stop: IconStopFilledMicro, edit: IconCheckMicro, loading: IconLoader, }; function SubmitIcon({ mode }: { mode: ReturnType["mode"] }) { let Icon = ICONS[mode]; return ( ); } /** Circular submit button that morphs between mic, send, plan, edit, and stop icons. The send (paper plane) icon slides up on click and the next icon rises in from below; other transitions swap instantly. */ function ComposerSubmit( { empty = true, ready = false, recording = false, editing = false, busy = false, plan = false, onRecord, onStop, onSend, className, disabled, ref, ...props }: SubmitProps, ) { let [tick, setTick] = useState(0); let state = submitState({ busy, editing, empty, plan, recording }); let { mode, label } = state; let variant = mode === "send" ? SLIDE : NONE; function click() { if (state.disabled) return; if (mode === "mic") return onRecord?.(); if (mode === "stop") return onStop?.(); if (mode === "send") setTick(t => t + 1); onSend?.(); } return ( ); } export { ComposerSubmit, type SubmitProps };