delete the combobox instead of giving it anchor semantics
audit m7 ranked this medium because "every caller inherits the opaque click". there are no callers. nothing has imported Combobox since the initial commit, there is no barrel that re-exports it, and nothing anywhere sets `href` on a SelectOption — so the navigate, the separator that only appeared for href options, and the href field on both declarations of the type were all unreachable. writing anchor semantics into a component that is never rendered is building, not fixing. the Command primitives it used stay; AIHarnessesSection needs them.
This commit is contained in:
@@ -19,7 +19,6 @@ Location: `src/workspaces/components/`
|
||||
Built on top of shadcn primitives:
|
||||
- `Avatar.tsx` - User avatars
|
||||
- `Card.tsx` - Custom card wrapper
|
||||
- `Combobox.tsx` - Searchable select
|
||||
- `ColorPicker.tsx` - Color selection
|
||||
- `DataTable/` - Table with sorting, filtering, pagination (see below)
|
||||
- `Dialogs/` - Common dialog patterns
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import type { SelectOption } from 'types';
|
||||
import * as React from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { cn } from 'helpers/cn';
|
||||
import { Check, ChevronsUpDown } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@/components/ui/command';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
|
||||
type ComboboxProps<T> = {
|
||||
options: SelectOption[];
|
||||
value: string | number;
|
||||
onChange: (value: T) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
};
|
||||
export const Combobox = <T extends string | number>({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
placeholder = 'Select...',
|
||||
className,
|
||||
}: ComboboxProps<T>) => {
|
||||
const navigate = useNavigate();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn('w-[200px] justify-between', className)}
|
||||
>
|
||||
<span className="block truncate">
|
||||
{value ? options?.find((option) => option.value === value)?.label : placeholder}
|
||||
</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className={cn('w-[200px] justify-between', className)}>
|
||||
<Command>
|
||||
<CommandInput placeholder={placeholder} />
|
||||
<CommandEmpty></CommandEmpty>
|
||||
<CommandGroup className="max-h-96 overflow-auto">
|
||||
{options?.map((option) => (
|
||||
<div key={option.value}>
|
||||
<CommandItem
|
||||
value={option?.label || '' + option.value}
|
||||
onSelect={(currentValue) => {
|
||||
if (option.href) {
|
||||
return navigate(option.href);
|
||||
}
|
||||
const newValue = options.find(
|
||||
(option) => option.label?.toLowerCase() === currentValue.toLowerCase(),
|
||||
)?.value;
|
||||
if (value && value !== newValue) {
|
||||
const setValue = typeof value === 'number' ? Number(newValue) : '' + newValue;
|
||||
onChange(setValue as T);
|
||||
}
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<Check className={cn('mr-2 h-4 w-4', value === option.value ? 'opacity-100' : 'opacity-0')} />
|
||||
{option.label}
|
||||
</CommandItem>
|
||||
{option.href && <Separator className="my-2" />}
|
||||
</div>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
declare global {
|
||||
type SelectOption = { value: number | string; label?: string; href?: string; hidden?: boolean };
|
||||
type SelectOption = { value: number | string; label?: string; hidden?: boolean };
|
||||
|
||||
type StateSetter<T> = React.Dispatch<React.SetStateAction<T>>;
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ export * from 'officerdb/types';
|
||||
export type { Job, JobStep, JobStatus, JobStepStatus, JobProgress } from './queue';
|
||||
export type { EmailSummary, EmailMessage, EmailThread } from './email';
|
||||
|
||||
export type SelectOption = { value: number | string; label?: string; href?: string };
|
||||
export type SelectOption = { value: number | string; label?: string };
|
||||
|
||||
export type StateSetter<T> = React.Dispatch<React.SetStateAction<T>>;
|
||||
|
||||
Reference in New Issue
Block a user