feat(ui): expand workflow-ready components
This commit is contained in:
@@ -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 yesterday’s 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 />
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user