DesignX
AI Chat@paxeer/ai-chat/composerimplemented

Composer

Chat composer using Textarea + Button.

Install

$pnpm exec designx add @paxeer/ai-chat/composer

Preview

Loading preview...

Source code

components/designx/ai-chat/composer.tsxtsx
"use client";

import { type FormEvent, useState } from "react";
import { Button, Card, CardContent, Textarea } from "@paxeer/core";

export interface ComposerProps {
  placeholder?: string;
  disabled?: boolean;
  onSend?: (text: string) => void;
}

export function Composer({
  placeholder = "Ask about a file, order, or run",
  disabled,
  onSend,
}: ComposerProps) {
  const [value, setValue] = useState("");

  function submit(event?: FormEvent) {
    event?.preventDefault();
    const text = value.trim();
    if (!text) return;
    onSend?.(text);
    setValue("");
  }

  return (
    <Card className="gap-0 py-3" data-slot="composer">
      <CardContent className="px-3">
        <form onSubmit={submit} className="flex flex-col gap-2">
          <label className="sr-only" htmlFor="designx-composer">
            Message
          </label>
          <Textarea
            id="designx-composer"
            rows={3}
            disabled={disabled}
            value={value}
            placeholder={placeholder}
            onChange={(event) => setValue(event.target.value)}
            onKeyDown={(event) => {
              if (event.key === "Enter" && !event.shiftKey) {
                event.preventDefault();
                submit();
              }
            }}
          />
          <div className="flex items-center justify-between gap-2">
            <p className="text-[length:var(--md-sys-typescale-label-small-size)] text-on-surface-variant">
              Enter to send · Shift+Enter for a new line
            </p>
            <Button type="submit" size="dense" disabled={disabled || value.trim().length === 0}>
              Send
            </Button>
          </div>
        </form>
      </CardContent>
    </Card>
  );
}