Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ jobs:
mkdir -p "$RUNNER_TEMP/c" && cd "$RUNNER_TEMP/c"
npm init -y >/dev/null && npm pkg set type=module >/dev/null
npm install "$TARBALL"
node -e 'import("@corbits/react-ui").then((m) => { for (const n of ["Button", "ChatThread", "Badge"]) if (typeof m[n] !== "function") throw new Error("missing export: " + n); })'
node -e 'import("@corbits/react-ui").then((m) => { for (const n of ["Button", "Conversation", "Badge"]) if (typeof m[n] !== "function") throw new Error("missing export: " + n); })'
206 changes: 160 additions & 46 deletions README.md

Large diffs are not rendered by default.

21 changes: 0 additions & 21 deletions e2e/approval-card.spec.ts

This file was deleted.

23 changes: 0 additions & 23 deletions e2e/chat-thread.spec.ts

This file was deleted.

59 changes: 0 additions & 59 deletions e2e/fixtures.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { useEffect, useLayoutEffect, useRef, useState, Profiler } from "react";
import type { ReactNode } from "react";

import {
ApprovalCard,
AuthLayout,
Button,
ChatThread,
Conversation,
ConversationContent,
ConversationItem,
Expand All @@ -17,8 +15,6 @@ import {
ThemeProvider,
ThemeToggle,
ThinkingMark,
type ApprovalRequest,
type ChatMessage,
type ConversationScrollPolicy,
useConversation,
PromptInput,
Expand Down Expand Up @@ -88,13 +84,6 @@ import {
type CommandPaletteGroup,
} from "@corbits/react-ui/ui/command-palette";

const REQUEST: ApprovalRequest = {
id: "req-1",
headline: "Post to #sales (Slack)",
requestedBy: "Scout",
details: [{ label: "Message", value: "Q3 numbers are in." }],
};

const GROUPS: CommandPaletteGroup[] = [
{
id: "pages",
Expand All @@ -116,21 +105,6 @@ function useEventLog() {
};
}

function Approval({ state }: { state: "idle" | "approving" }) {
const { log, record } = useEventLog();
return (
<>
<ApprovalCard
request={REQUEST}
state={state}
onApprove={() => record("approve")}
onReject={() => record("reject")}
/>
{log}
</>
);
}

function Palette() {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
Expand All @@ -154,32 +128,6 @@ function Palette() {
);
}

const CHAT: ChatMessage[] = [
{
id: "m1",
role: "user",
createdAt: "2026-01-01T09:00:00Z",
parts: [{ type: "text", text: "What changed today?" }],
},
{
id: "m2",
role: "agent",
createdAt: "2026-01-01T09:00:04Z",
parts: [
{ type: "reasoning", text: "Check the commit log first." },
{
type: "tool",
toolCallId: "t1",
toolName: "git__log",
label: "Read today's commits",
state: "done",
output: "3 commits",
},
{ type: "text", text: "Three commits landed today." },
],
},
];

function SelectField() {
const [value, setValue] = useState("a");
const { log, record } = useEventLog();
Expand Down Expand Up @@ -1433,14 +1381,7 @@ function ConversationThreadScenario() {

export const scenarios: Record<string, () => ReactNode> = {
smoke: () => <Button>Ready</Button>,
"approval-card": () => <Approval state="idle" />,
"approval-card-busy": () => <Approval state="approving" />,
"command-palette": () => <Palette />,
"chat-thread": () => (
<div style={{ display: "flex", flexDirection: "column", height: 480 }}>
<ChatThread messages={CHAT} identity={{ name: "Scout" }} />
</div>
),
select: () => <SelectField />,
"filter-chip": () => <Chip />,
"dictation-denied": () => <DictationStatusLine state="denied" />,
Expand Down
2 changes: 0 additions & 2 deletions e2e/stories-a11y.known.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
"chat-prompt-input--suggestions-open (light): color-contrast-unmeasured",
"chat-prompt-input--with-tokens (dark): color-contrast-unmeasured",
"chat-prompt-input--with-tokens (light): color-contrast-unmeasured",
"chat-tool-block--long-output-scrolls-in-place (dark): scrollable-region-focusable",
"chat-tool-block--long-output-scrolls-in-place (light): scrollable-region-focusable",
"primitives-avatar--orbit (dark): color-contrast-unmeasured",
"primitives-avatar--orbit (light): color-contrast-unmeasured",
"primitives-avatar--stack-with-status (dark): color-contrast-unmeasured",
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/use-attachments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { Activity, StrictMode, act, createElement, useState } from "react";
import { createRoot } from "react-dom/client";

import type { PartFile } from "../lib/chat-parts.js";
import type { PartFile } from "../lib/conversation.js";
import type { AttachmentData } from "../ui/attachments.js";
import {
useAttachments,
Expand Down
5 changes: 2 additions & 3 deletions src/hooks/use-controllable-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ const isProduction =
/**
* The controlled-or-uncontrolled resolution every prop pair shaped like
* `open`/`onOpenChange` needs: track state internally, but defer to `value`
* whenever the caller supplies one. `useDismissablePopover`, `ToolBlock` and
* `ToolNarrative` each want exactly this and previously each wrote their own
* ternary for it; this is the one place it lives now.
* whenever the caller supplies one. Every such pair resolves through here
* rather than writing its own ternary.
*
* The returned setter accepts a value or a React-style updater
* (`setOpen((prev) => !prev)`), same as `useState`.
Expand Down
17 changes: 10 additions & 7 deletions src/hooks/use-delayed-autofocus.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { useEffect, type RefObject } from "react";

import { CHAT_DOCK_AUTOFOCUS_DELAY_MS } from "../ui/chat-dock-timing.js";
/**
* Roughly a typical panel entrance (a few hundred milliseconds), so focus
* never lands on an element that is still moving.
*/
const DEFAULT_DELAY_MS = 280;

/**
* Focuses the given element shortly after `active` turns true — the composer
* wants to autofocus once the dock has finished popping open, not while it is
* still mid-animation, so the delay is deliberate rather than a magic number.
* `delayMs` defaults to `CHAT_DOCK_AUTOFOCUS_DELAY_MS`, which is pinned `>=`
* the dock's own entrance duration so focus never lands mid-animation.
* Focuses the given element shortly after `active` turns true: an element
* that animates in takes focus once it has settled, not while it is still
* mid-animation. Pass a `delayMs` at least as long as the entrance it waits
* for.
*/
export function useDelayedAutofocus<T extends HTMLElement>(
ref: RefObject<T | null>,
active: boolean,
delayMs = CHAT_DOCK_AUTOFOCUS_DELAY_MS,
delayMs = DEFAULT_DELAY_MS,
): void {
useEffect(() => {
if (!active) return;
Expand Down
14 changes: 0 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ export * from "./hooks/use-file-drop.js";
export * from "./hooks/use-media-viewer.js";
export * from "./lib/artifact.js";
export * from "./lib/chart-palette.js";
export * from "./lib/chat-message.js";
export * from "./lib/chat-parts.js";
export * from "./lib/metrics.js";
export * from "./lib/now-item.js";
export * from "./lib/relative-time.js";
Expand All @@ -46,9 +44,7 @@ export * from "./lib/conversation-activity.js";
export * from "./lib/media-kind.js";
export * from "./lib/attachment-limits.js";
export * from "./lib/prompt-tokens.js";
export * from "./ui/agent-turn.js";
export * from "./ui/animated-number.js";
export * from "./ui/approval-card.js";
export * from "./ui/artifact-body.js";
export * from "./ui/artifact-notice.js";
export * from "./ui/avatar.js";
Expand All @@ -61,10 +57,6 @@ export * from "./ui/button.js";
export * from "./ui/card.js";
export * from "./ui/category-bars.js";
export * from "./ui/chart-frame.js";
export * from "./ui/chat-composer.js";
export * from "./ui/chat-input.js";
export * from "./ui/chat-panel.js";
export * from "./ui/chat-thread.js";
export * from "./ui/checkbox.js";
export * from "./ui/compare-body.js";
export * from "./ui/confirm-button.js";
Expand All @@ -87,19 +79,15 @@ export * from "./ui/library-page-header.js";
export * from "./ui/list-detail.js";
export * from "./ui/live-run-banner.js";
export * from "./ui/live-status-line.js";
export * from "./ui/message-bubble.js";
export * from "./ui/mic-button.js";
export * from "./ui/notifications-bell.js";
export * from "./ui/now-cards.js";
export * from "./ui/page-panel.js";
export * from "./ui/page-shell.js";
export * from "./ui/parts-renderer.js";
export * from "./ui/profile-card.js";
export * from "./ui/progress-checklist.js";
export * from "./ui/provider-mark.js";
export * from "./ui/quick-reply-chips.js";
export * from "./ui/quote-card.js";
export * from "./ui/reasoning-block.js";
export * from "./ui/research-body.js";
export * from "./ui/rich-empty-state.js";
export * from "./ui/run-now-button.js";
Expand Down Expand Up @@ -128,8 +116,6 @@ export * from "./ui/thinking-label.js";
export * from "./ui/thinking-mark.js";
export * from "./ui/time-series-chart.js";
export * from "./ui/token-mosaic.js";
export * from "./ui/tool-block.js";
export * from "./ui/tool-narrative.js";
export * from "./ui/tool-picker.js";
export * from "./ui/top-bar.js";
export * from "./ui/trace-waterfall.js";
Expand Down
Loading
Loading