Skip to content

Commit f264d08

Browse files
sdignumclaude
andcommitted
feat(apollo-vertex): add invalid state to Combobox with alert icon
Show an AlertTriangleIcon and destructive border/ring when aria-invalid is set on ComboboxTrigger. Add invalid example to docs and templates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e5747ea commit f264d08

3 files changed

Lines changed: 80 additions & 3 deletions

File tree

apps/apollo-vertex/app/shadcn-components/combobox/page.mdx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ComboboxSingleSelectTemplate, ComboboxMultiSelectTemplate } from '@/templates/ComboboxTemplate'
1+
import { ComboboxSingleSelectTemplate, ComboboxMultiSelectTemplate, ComboboxInvalidTemplate } from '@/templates/ComboboxTemplate'
22

33
# Combobox
44

@@ -16,6 +16,12 @@ An autocomplete input with a filterable list, supporting single and multi-select
1616
<ComboboxMultiSelectTemplate />
1717
</div>
1818

19+
### Invalid
20+
21+
<div className="not-prose my-8 rounded-lg border overflow-hidden">
22+
<ComboboxInvalidTemplate />
23+
</div>
24+
1925
## Installation
2026

2127
```bash
@@ -93,3 +99,24 @@ const [values, setValues] = useState<string[]>([])
9399
</ComboboxContent>
94100
</Combobox>
95101
```
102+
103+
### Invalid
104+
105+
```tsx
106+
<Combobox value={value} onValueChange={setValue}>
107+
<ComboboxTrigger placeholder="Select a framework..." aria-invalid="true">
108+
{selectedLabel}
109+
</ComboboxTrigger>
110+
<ComboboxContent>
111+
<ComboboxInput placeholder="Search frameworks..." />
112+
<ComboboxList>
113+
<ComboboxEmpty>No results found.</ComboboxEmpty>
114+
<ComboboxGroup>
115+
<ComboboxItem value="next">Next.js</ComboboxItem>
116+
<ComboboxItem value="svelte">SvelteKit</ComboboxItem>
117+
<ComboboxItem value="nuxt">Nuxt</ComboboxItem>
118+
</ComboboxGroup>
119+
</ComboboxList>
120+
</ComboboxContent>
121+
</Combobox>
122+
```

apps/apollo-vertex/registry/combobox/combobox.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
"use client";
22

3-
import { CheckIcon, ChevronsUpDownIcon, XIcon } from "lucide-react";
3+
import {
4+
AlertTriangleIcon,
5+
CheckIcon,
6+
ChevronsUpDownIcon,
7+
XIcon,
8+
} from "lucide-react";
49
import * as React from "react";
510
import { Badge } from "@/components/ui/badge";
611
import { buttonVariants } from "@/components/ui/button";
@@ -98,9 +103,11 @@ function ComboboxTrigger({
98103
className,
99104
placeholder,
100105
children,
106+
"aria-invalid": ariaInvalid,
101107
...props
102108
}: ComboboxTriggerProps) {
103109
const { open } = useComboboxContext();
110+
const isInvalid = ariaInvalid === "true" || ariaInvalid === true;
104111

105112
return (
106113
<PopoverTrigger asChild>
@@ -110,6 +117,7 @@ function ComboboxTrigger({
110117
tabIndex={0}
111118
aria-expanded={open}
112119
aria-haspopup="listbox"
120+
aria-invalid={ariaInvalid}
113121
className={cn(
114122
buttonVariants({ variant: "outline" }),
115123
"w-full justify-between font-normal",
@@ -119,7 +127,12 @@ function ComboboxTrigger({
119127
{...props}
120128
>
121129
{children ?? <span className="truncate">{placeholder}</span>}
122-
<ChevronsUpDownIcon className="ml-auto size-4 shrink-0 opacity-50" />
130+
<span className="ml-auto flex items-center gap-1">
131+
{isInvalid && (
132+
<AlertTriangleIcon className="size-4 shrink-0 text-destructive" />
133+
)}
134+
<ChevronsUpDownIcon className="size-4 shrink-0 opacity-50" />
135+
</span>
123136
</div>
124137
</PopoverTrigger>
125138
);

apps/apollo-vertex/templates/ComboboxTemplate.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,38 @@ function MultiSelectContent() {
9292
);
9393
}
9494

95+
function InvalidContent() {
96+
const [value, setValue] = useState<string[]>([]);
97+
const selectedLabel = frameworks.find((f) => f.value === value[0])?.label;
98+
99+
return (
100+
<div className="p-4">
101+
<Combobox value={value} onValueChange={setValue}>
102+
<ComboboxTrigger
103+
className="w-[240px]"
104+
placeholder={"Select a framework"}
105+
aria-invalid="true"
106+
>
107+
{selectedLabel}
108+
</ComboboxTrigger>
109+
<ComboboxContent>
110+
<ComboboxInput placeholder="Search frameworks..." />
111+
<ComboboxList>
112+
<ComboboxEmpty>{"No frameworks found."}</ComboboxEmpty>
113+
<ComboboxGroup>
114+
{frameworks.map((f) => (
115+
<ComboboxItem key={f.value} value={f.value}>
116+
{f.label}
117+
</ComboboxItem>
118+
))}
119+
</ComboboxGroup>
120+
</ComboboxList>
121+
</ComboboxContent>
122+
</Combobox>
123+
</div>
124+
);
125+
}
126+
95127
export const ComboboxSingleSelectTemplate = dynamic(
96128
() => Promise.resolve(SingleSelectContent),
97129
{ ssr: false },
@@ -101,3 +133,8 @@ export const ComboboxMultiSelectTemplate = dynamic(
101133
() => Promise.resolve(MultiSelectContent),
102134
{ ssr: false },
103135
);
136+
137+
export const ComboboxInvalidTemplate = dynamic(
138+
() => Promise.resolve(InvalidContent),
139+
{ ssr: false },
140+
);

0 commit comments

Comments
 (0)