sidiora-labs

Chat App

AI chats

About

A complete agent chat shell that docks a collapsible sidebar beside a message thread and a composer, and automatically folds the sidebar when the shell gets too narrow.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Chat App to your project and fits it to your theme.

Code

How Chat App is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import { ChatApp } from "@/components/ui/chat-app";
import { useRef, useState } from "react";
import {
  Bot,
  Clock3,
  MessageSquarePlus,
  PanelLeft,
  Search,
  Send,
  User,
} from "lucide-react";
import {
  AnimatedSidebar,
  AnimatedSidebarContent,
  AnimatedSidebarGroup,
  AnimatedSidebarGroupContent,
  AnimatedSidebarInset,
  AnimatedSidebarMenu,
  AnimatedSidebarMenuButton,
  AnimatedSidebarMenuItem,
  AnimatedSidebarRail,
  AnimatedSidebarTrigger,
} from "@/components/ui/chat-app-utils/animated-sidebar";
import { cn } from "@/lib/utils";

interface ThreadMessage {
  id: string;
  from: "user" | "assistant";
  content: string;
}

const initialMessages: ThreadMessage[] = [
  {
    id: "m1",
    from: "user",
    content: "Audit the checkout flow and prepare a release-ready patch.",
  },
  {
    id: "m2",
    from: "assistant",
    content:
      "I traced the validation gap in checkout/submit.ts and prepared a focused patch. Running the checks now.",
  },
];

export default function ChatAppDemo() {
  const [messages, setMessages] = useState<ThreadMessage[]>(initialMessages);
  const [input, setInput] = useState("");
  const [pending, setPending] = useState(false);
  const runId = useRef(0);

  const submit = () => {
    const value = input.trim();
    if (!value || pending) return;
    const id = runId.current++;
    setMessages((current) => [
      ...current,
      { id: `user-${id}`, from: "user", content: value },
    ]);
    setInput("");
    setPending(true);
    window.setTimeout(() => {
      setMessages((current) => [
        ...current,
        {
          id: `assistant-${id}`,
          from: "assistant",
          content:
            "Got it — I'll keep this change focused and run the same checks before shipping.",
        },
      ]);
      setPending(false);
    }, 900);
  };

  return (
    <ChatApp sidebarWidth="16rem" className="h-[640px]">
      <AnimatedSidebar
        ariaLabel="Agent workspace"
        collapsible="offcanvas"
        panelClassName="h-full bg-background"
      >
        <AnimatedSidebarContent className="gap-1 px-2 py-4">
          <AnimatedSidebarGroup>
            <AnimatedSidebarGroupContent>
              <AnimatedSidebarMenu className="gap-1">
                {[
                  { label: "New task", icon: MessageSquarePlus },
                  { label: "Search", icon: Search },
                  { label: "Runs", icon: Clock3 },
                ].map(({ label, icon: Icon }) => (
                  <AnimatedSidebarMenuItem key={label}>
                    <AnimatedSidebarMenuButton
                      icon={<Icon className="size-4" />}
                      className="font-normal"
                    >
                      {label}
                    </AnimatedSidebarMenuButton>
                  </AnimatedSidebarMenuItem>
                ))}
              </AnimatedSidebarMenu>
            </AnimatedSidebarGroupContent>
          </AnimatedSidebarGroup>
        </AnimatedSidebarContent>
        <AnimatedSidebarRail />
      </AnimatedSidebar>

      <AnimatedSidebarInset className="bg-background">
        <header className="flex h-14 shrink-0 items-center gap-2.5 border-border border-b px-4">
          <AnimatedSidebarTrigger className="text-muted-foreground hover:bg-muted hover:text-foreground">
            <PanelLeft className="size-4" />
          </AnimatedSidebarTrigger>
          <div className="min-w-0">
            <p className="truncate text-sm font-medium text-foreground">
              Checkout release
            </p>
            <p className="truncate text-[11px] text-muted-foreground">
              Agent workspace
            </p>
          </div>
        </header>

        <div className="flex-1 overflow-y-auto px-4 py-5">
          <div className="mx-auto flex max-w-2xl flex-col gap-4">
            {messages.map((message) => (
              <div
                key={message.id}
                className={cn(
                  "flex items-start gap-2.5",
                  message.from === "user" && "flex-row-reverse",
                )}
              >
                <span className="grid size-7 shrink-0 place-items-center rounded-full bg-muted text-muted-foreground">
                  {message.from === "assistant" ? (
                    <Bot className="size-4" />
                  ) : (
                    <User className="size-4" />
                  )}
                </span>
                <p
                  className={cn(
                    "max-w-[75%] rounded-2xl px-3.5 py-2 text-sm",
                    message.from === "user"
                      ? "bg-foreground text-background"
                      : "bg-muted text-foreground",
                  )}
                >
                  {message.content}
                </p>
              </div>
            ))}
            {pending ? (
              <div className="flex items-center gap-2 text-sm text-muted-foreground">
                <span className="grid size-7 shrink-0 place-items-center rounded-full bg-muted">
                  <Bot className="size-4" />
                </span>
                Thinking…
              </div>
            ) : null}
          </div>
        </div>

        <div className="shrink-0 border-border border-t bg-background p-3">
          <form
            className="mx-auto flex max-w-2xl items-end gap-2"
            onSubmit={(event) => {
              event.preventDefault();
              submit();
            }}
          >
            <textarea
              value={input}
              onChange={(event) => setInput(event.target.value)}
              placeholder="Ask the agent to continue…"
              rows={1}
              className="flex-1 resize-none rounded-xl border border-border bg-background px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
            />
            <button
              type="submit"
              disabled={pending || !input.trim()}
              className="grid size-9 shrink-0 place-items-center rounded-full bg-foreground text-background disabled:opacity-40"
            >
              <Send className="size-4" />
            </button>
          </form>
        </div>
      </AnimatedSidebarInset>
    </ChatApp>
  );
}

Dependencies

More by sidiora-labs

View all
  • Prediction Market

    Prediction Market

  • Scroll To

    Scroll To

More in AI chats

View all
  • AI Actions

    AI Actions

    319
  • Color Picker

    Color Picker

    82
  • Agent Chat

    Agent Chat

    17
  • Prompt with Model Picker

    Prompt with Model Picker

    2
  • System Message

    System Message

    54
  • Message Thread

    Message Thread

    36
  • Suggestions

    Suggestions

    28
  • Message With Header And Delivery Status

    Message With Header And Delivery Status

    38