Input Message
fluid-functionalism/input-messageFreeComponent
Chat-style message composer with auto-resizing textarea, drag-and-drop file attachments (PNG / JPEG / PDF), flexible left/right action slots, and a built-in send button on a Surface-2 substrate.
Install it
npx shadcn@latest add https://fluidfunctionalism.com/r/input-message.jsonSource
1"use client";23import {4 forwardRef,5 useCallback,6 useEffect,7 useLayoutEffect,8 useMemo,9 useRef,10 useState,11 type ChangeEvent,12 type DragEvent as ReactDragEvent,13 type HTMLAttributes,14 type KeyboardEvent as ReactKeyboardEvent,15 type ReactNode,16 type TextareaHTMLAttributes,17} from "react";18import { AnimatePresence, motion, Reorder, useReducedMotion } from "framer-motion";19import { cn } from "@/lib/utils";20import { fontWeights } from "@/lib/font-weight";21import { spring } from "@/lib/springs";22import { useShape } from "@/lib/shape-context";23import { useIcon } from "@/lib/icon-context";24import { surfaceClasses } from "@/lib/surface-classes";25import { SurfaceProvider } from "@/lib/surface-context";26import { FileThumbnail } from "@/registry/default/file-thumbnail";27import { Button } from "@/registry/radix/button";28import { Tooltip } from "@/registry/radix/tooltip";2930const useIsoLayoutEffect =31 typeof window !== "undefined" ? useLayoutEffect : useEffect;3233// Touch devices have no hover, so hover-revealed affordances (like a queued34// row's × button) would never appear. `(hover: none)` flags those so they can35// be shown persistently instead. SSR-safe: starts false, resolves on mount.36function useIsTouch() {37 const [isTouch, setIsTouch] = useState(false);38 useEffect(() => {39 const mq = window.matchMedia("(hover: none)");40 const update = () => setIsTouch(mq.matches);41 update();42 mq.addEventListener("change", update);43 return () => mq.removeEventListener("change", update);44 }, []);45 return isTouch;46}4748const DEFAULT_ACCEPT = "image/png,image/jpeg,application/pdf";4950interface InputMessageSlotContext {51 /** Opens the native file picker via the hidden `<input type="file">`.52 * Pass `acceptOverride` (e.g. `"image/*"`) to scope the picker to a53 * subset of the component's accept types just for this invocation. */54 openFilePicker: (acceptOverride?: string) => void;55 /** Currently-attached files (controlled). */56 files: File[];57}5859type InputMessageSlot =60 | ReactNode61 | ((ctx: InputMessageSlotContext) => ReactNode);6263/** A message held in the queue while the assistant is responding. Carries the64 * trimmed text plus a snapshot of the files attached when it was queued, so65 * double-click-to-edit can restore both. `id` is a stable key minted on enqueue. */66interface QueuedMessage {67 id: string;68 text: string;69 files: File[];70}7172interface InputMessageProps73 extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {74// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from fluid-functionalism
All 53 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | fluid-functionalism | Components | Free | 3 deps | |
| Accordion (Base UI)accordion-base | fluid-functionalism | Components | Free | 3 deps | |
| Ask User Questionsask-user-questions | fluid-functionalism | Components | Free | 2 deps | |
| Badgebadge | fluid-functionalism | Components | Free | 1 dep | |
| Buttonbutton | fluid-functionalism | Components | Free | 4 deps | |
| Button (Base UI)button-base | fluid-functionalism | Components | Free | 4 deps | |
| Cardcard | fluid-functionalism | Components | Free | 1 dep | |
| Chat Messagechat-message | fluid-functionalism | Components | Free | 1 dep |
