Skip to content
Merged
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
21 changes: 21 additions & 0 deletions design-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ Design Lab exposes an Overview, a searchable component catalog with interactive

`@bitfun/ui/registry` is the component source of truth. Design Lab derives its component navigation, counts, token scopes, and detail routes from that package export; adding Lab copy or a preview cannot publish a component or keep a removed component alive.

The same entry publishes `ChatComposer`, the shared context-band and
compact/expanded input anatomy used by product composers without importing
their stores, localized menus, editor, model, voice, or host actions.

FlowChat tool-card frameworks are published from `@bitfun/ui/flow-chat` as
`AmbientToolCard` and `ProminentToolCard`. The names describe whether a tool
result should stay in the conversational background or receive deliberate user
attention. Concrete package views cover the reusable FlowChat families for file
and command execution, search and web results, agent and session activity, Git
and review summaries, page lifecycle, code execution, todos, images, and other
routine tool traces. Product adapters continue to own parsing, stores,
localization, host capabilities, and heavy renderers passed through semantic
slots; the package owns each migrated card's anatomy, state presentation, and
interaction structure.

Design Lab exposes FlowChat as its own library category. Both framework and
tool-view entries come from `@bitfun/ui/registry`; its typed preview registry is
exhaustive over those package entries. The tool gallery renders only real
public views used by migrated adapters and lists bespoke product cards
separately instead of substituting generic previews for them.

## Commands

```bash
Expand Down
82 changes: 78 additions & 4 deletions design-system/apps/design-lab/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
PanelTop,
Search,
Settings2,
SquareTerminal,
Sun,
ToggleLeft,
X,
Expand Down Expand Up @@ -71,6 +72,7 @@ type LabRoute =
| { page: "overview" }
| { page: "getting-started" }
| { page: "components" }
| { page: "flow-chat" }
| { page: "colors" }
| { page: "resources" }
| { page: "tokens" }
Expand All @@ -85,12 +87,21 @@ interface SearchDestination {
}

const componentIcons: Record<string, LucideIcon> = {
AmbientToolCard: SquareTerminal,
Button: MousePointerClick,
Modal: AppWindow,
ProminentToolCard: SquareTerminal,
Switch: ToggleLeft,
TabGroup: PanelTop,
};

const flowChatComponents = componentRegistry.filter(
(component) => component.category === "flow-chat",
);
const standardComponents = componentRegistry.filter(
(component) => component.category !== "flow-chat",
);

function getThemeDataName(
colorScheme: ColorScheme,
contrast: ContrastMode,
Expand Down Expand Up @@ -118,6 +129,9 @@ function parseRoute(hash: string): LabRoute {
if (route === "components") {
return { page: "components" };
}
if (route === "flow-chat") {
return { page: "flow-chat" };
}
if (route === "resources") {
return { page: "resources" };
}
Expand Down Expand Up @@ -201,6 +215,13 @@ export function App() {
label: t("nav.components"),
route: { page: "components" },
},
{
detail: t("search.flowChatDetail", { count: flowChatComponents.length }),
icon: SquareTerminal,
keywords: `FlowChat tool cards ambient prominent ${t("nav.flowChat")}`,
label: t("nav.flowChat"),
route: { page: "flow-chat" },
},
{
detail: t("search.resourcesDetail"),
icon: FileText,
Expand Down Expand Up @@ -343,6 +364,10 @@ export function App() {
const activeComponent = route.page === "component"
? componentRegistry.find((component) => component.name === route.componentName)
: undefined;
const isFlowChatRoute = route.page === "flow-chat"
|| activeComponent?.category === "flow-chat";
const isStandardComponentRoute = route.page === "components"
|| Boolean(activeComponent && activeComponent.category !== "flow-chat");

return (
<div
Expand Down Expand Up @@ -420,8 +445,8 @@ export function App() {

<span className="lab-nav-label">{t("nav.library")}</span>
<a
aria-current={route.page === "components" ? "page" : undefined}
data-expanded={route.page === "component" || route.page === "components" || undefined}
aria-current={isStandardComponentRoute ? "page" : undefined}
data-expanded={isStandardComponentRoute || undefined}
href="#components"
onClick={(event) => {
event.preventDefault();
Expand All @@ -432,7 +457,7 @@ export function App() {
<span>{t("nav.components")}</span>
</a>
<div className="lab-component-links">
{componentRegistry.map((component) => {
{standardComponents.map((component) => {
const Icon = componentIcons[component.name] ?? Blocks;
const active = route.page === "component" && route.componentName === component.name;
return (
Expand All @@ -451,6 +476,39 @@ export function App() {
);
})}
</div>
<a
aria-current={isFlowChatRoute ? "page" : undefined}
data-expanded={isFlowChatRoute || undefined}
href="#flow-chat"
onClick={(event) => {
event.preventDefault();
navigate({ page: "flow-chat" });
}}
>
<SquareTerminal aria-hidden="true" size={17} />
<span>{t("nav.flowChat")}</span>
<small>{flowChatComponents.length}</small>
</a>
<div className="lab-component-links">
{flowChatComponents.map((component) => {
const Icon = componentIcons[component.name] ?? SquareTerminal;
const active = route.page === "component" && route.componentName === component.name;
return (
<a
aria-current={active ? "page" : undefined}
href={`#component/${component.name.toLowerCase()}`}
key={component.name}
onClick={(event) => {
event.preventDefault();
navigate({ componentName: component.name, page: "component" });
}}
>
<Icon aria-hidden="true" size={15} />
<span>{component.name}</span>
</a>
);
})}
</div>
</nav>

<div className="lab-sidebar-footer">
Expand Down Expand Up @@ -643,14 +701,30 @@ export function App() {
/>
)}

{route.page === "flow-chat" && (
<ComponentsPage
category="flow-chat"
colorScheme={colorScheme}
contrast={contrast}
density={density}
onInspectTokens={() => openTokenPage()}
onOpenComponent={(name) => navigate({ componentName: name, page: "component" })}
tokenOverrides={tokenOverrides}
/>
)}

{route.page === "component" && activeComponent && (
<ComponentDetailPage
colorScheme={colorScheme}
component={activeComponent}
contrast={contrast}
density={density}
key={activeComponent.name}
onBack={() => navigate({ page: "components" })}
onBack={() => navigate({
page: activeComponent.category === "flow-chat"
? "flow-chat"
: "components",
})}
onInspectTokens={openTokenPage}
tokenOverrides={tokenOverrides}
/>
Expand Down
28 changes: 28 additions & 0 deletions design-system/apps/design-lab/src/i18n/componentMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type Translate = (key: MessageKey, params?: TranslateParams) => string;
const categoryKeys: Readonly<Record<string, MessageKey>> = {
action: "meta.category.action",
feedback: "meta.category.feedback",
"flow-chat": "meta.category.flowChat",
form: "meta.category.form",
navigation: "meta.category.navigation",
primitive: "meta.category.primitive",
Expand All @@ -15,24 +16,51 @@ const descriptionKeys: Readonly<Record<string, MessageKey>> = {
ActionCard: "component.ActionCard.description",
ActionItem: "component.ActionItem.description",
ActivityItem: "component.ActivityItem.description",
AgentControlToolCard: "component.AgentControlToolCard.description",
AgentWaitToolCard: "component.AgentWaitToolCard.description",
AmbientToolCard: "component.AmbientToolCard.description",
Button: "component.Button.description",
Card: "component.Card.description",
CommandToolCard: "component.CommandToolCard.description",
Composer: "component.Composer.description",
ConfirmDialog: "component.ConfirmDialog.description",
ContextCompressionToolCard: "component.ContextCompressionToolCard.description",
DefaultToolCard: "component.DefaultToolCard.description",
DirectoryListToolCard: "component.DirectoryListToolCard.description",
Field: "component.Field.description",
FieldGroup: "component.FieldGroup.description",
FileDiffToolCard: "component.FileDiffToolCard.description",
FileOperationToolCard: "component.FileOperationToolCard.description",
GetToolSpecToolCard: "component.GetToolSpecToolCard.description",
GitToolCard: "component.GitToolCard.description",
GlobSearchToolCard: "component.GlobSearchToolCard.description",
GrepSearchToolCard: "component.GrepSearchToolCard.description",
IconButton: "component.IconButton.description",
Input: "component.Input.description",
KeyHint: "component.KeyHint.description",
Menu: "component.Menu.description",
Modal: "component.Modal.description",
NavigationPanel: "component.NavigationPanel.description",
PageDeployToolCard: "component.PageDeployToolCard.description",
PageHeader: "component.PageHeader.description",
PagePublishToolCard: "component.PagePublishToolCard.description",
ProminentToolCard: "component.ProminentToolCard.description",
ReadFileToolCard: "component.ReadFileToolCard.description",
ReviewSummaryToolCard: "component.ReviewSummaryToolCard.description",
RunCodeToolCard: "component.RunCodeToolCard.description",
SearchField: "component.SearchField.description",
SegmentedControl: "component.SegmentedControl.description",
SessionControlToolCard: "component.SessionControlToolCard.description",
SessionMessageToolCard: "component.SessionMessageToolCard.description",
SkillToolCard: "component.SkillToolCard.description",
Switch: "component.Switch.description",
TabGroup: "component.TabGroup.description",
TerminalControlToolCard: "component.TerminalControlToolCard.description",
TodoToolCard: "component.TodoToolCard.description",
Toolbar: "component.Toolbar.description",
ViewImageToolCard: "component.ViewImageToolCard.description",
WebFetchToolCard: "component.WebFetchToolCard.description",
WebSearchToolCard: "component.WebSearchToolCard.description",
};

export function getComponentCategoryLabel(category: string, t: Translate): string {
Expand Down
Loading
Loading