feat(ui): expand workflow-ready components

This commit is contained in:
2026-03-20 18:11:48 +08:00
parent 36822f05e0
commit a8c1d3f256
27 changed files with 1562 additions and 85 deletions
@@ -62,6 +62,83 @@ function ControlledDemo() {
);
}
function RecentAndSuggestedDemo() {
const [value, setValue] = useState("");
const items = [
{
value: "recent-legal",
label: "Legal review",
group: "Recent",
description: "Last used in yesterdays policy update."
},
{
value: "recent-design",
label: "Design review",
group: "Recent",
description: "Common pick for UI launches."
},
...teamItems
];
return (
<div className="grid w-[420px] gap-4">
<Combobox
aria-label="Suggested routing team"
emptyMessage={(query) => `No team named “${query}”. Create a custom routing lane instead.`}
footer={
<div className="flex items-center justify-between gap-3">
<p className="text-xs text-[var(--color-muted-foreground)]">
Need a specialist lane?
</p>
<Button size="sm" variant="ghost">
Create lane
</Button>
</div>
}
items={items}
onValueChange={setValue}
searchPlaceholder="Search recent and suggested teams"
value={value}
/>
<div className="rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-card)] px-4 py-3 text-sm text-[var(--color-muted-foreground)] shadow-[var(--shadow-xs)]">
Current routing lane:{" "}
<span className="font-medium text-[var(--color-foreground)]">
{value || "No lane selected"}
</span>
</div>
</div>
);
}
function AsyncResultsDemo() {
const [searchValue, setSearchValue] = useState("");
const trimmedSearch = searchValue.trim().toLowerCase();
const isSearching = trimmedSearch.length > 0 && trimmedSearch.length < 3;
return (
<div className="grid w-[420px] gap-4">
<Combobox
aria-label="Async routing search"
emptyMessage={(query) =>
`No routing lane matched “${query}”. Try a broader keyword or create a new lane.`
}
items={[...teamItems]}
loading={isSearching}
loadingMessage="Searching routing lanes…"
onSearchValueChange={setSearchValue}
searchPlaceholder="Type at least 3 characters"
searchValue={searchValue}
/>
<p className="m-0 text-sm leading-6 text-[var(--color-muted-foreground)]">
This pattern is useful when the results come from an API and you need a clear
transition between loading, empty, and selectable states.
</p>
</div>
);
}
function LaunchRoutingForm() {
const [submitted, setSubmitted] = useState<Record<string, string> | null>(null);
const form = useForm<{ team: string }>({
@@ -158,6 +235,14 @@ export const Controlled: Story = {
render: () => <ControlledDemo />
};
export const RecentAndSuggested: Story = {
render: () => <RecentAndSuggestedDemo />
};
export const AsyncResults: Story = {
render: () => <AsyncResultsDemo />
};
export const WithForm: Story = {
render: () => <LaunchRoutingForm />
};