Update UI to support additions on the resource

This commit is contained in:
Owen
2026-05-06 10:09:05 -07:00
parent 54c1dd3bae
commit c4b3656fad
9 changed files with 621 additions and 198 deletions

View File

@@ -23,6 +23,7 @@ export type MultiSelectTagsProps<T extends TagValue> = {
onSearch: (query: string) => void;
ref?: Ref<HTMLButtonElement>;
disabled?: boolean;
lockedIds?: Set<string>;
};
export function MultiSelectContent<T extends TagValue>({
@@ -32,7 +33,8 @@ export function MultiSelectContent<T extends TagValue>({
value,
options,
onSearch,
onChange
onChange,
lockedIds
}: MultiSelectTagsProps<T>) {
const t = useTranslations();
const selectedValues = new Set(value.map((v) => v.id));
@@ -48,33 +50,38 @@ export function MultiSelectContent<T extends TagValue>({
{emptyPlaceholder ?? t("noResults")}
</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
value={option.id}
key={option.id}
onSelect={() => {
let newValues = [];
if (selectedValues.has(option.id)) {
newValues = value.filter(
(v) => v.id !== option.id
);
} else {
newValues = [...value, option];
}
onChange(newValues);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
selectedValues.has(option.id)
? "opacity-100"
: "opacity-0"
)}
/>
{`${option.text}`}
</CommandItem>
))}
{options.map((option) => {
const isLocked = lockedIds?.has(option.id);
return (
<CommandItem
value={option.id}
key={option.id}
disabled={isLocked}
onSelect={() => {
if (isLocked) return;
let newValues = [];
if (selectedValues.has(option.id)) {
newValues = value.filter(
(v) => v.id !== option.id
);
} else {
newValues = [...value, option];
}
onChange(newValues);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
selectedValues.has(option.id)
? "opacity-100"
: "opacity-0"
)}
/>
{`${option.text}`}
</CommandItem>
);
})}
</CommandGroup>
</CommandList>
</Command>

View File

@@ -5,7 +5,7 @@ import {
PopoverTrigger
} from "@app/components/ui/popover";
import { cn } from "@app/lib/cn";
import { ChevronDownIcon, XIcon } from "lucide-react";
import { ChevronDownIcon, LockIcon, XIcon } from "lucide-react";
import {
type MultiSelectTagsProps,
type TagValue,
@@ -16,10 +16,12 @@ export interface MultiSelectInputProps<
T extends TagValue
> extends MultiSelectTagsProps<T> {
buttonText?: string;
lockedIds?: Set<string>;
}
export function MultiSelectTagInput<T extends TagValue>({
buttonText,
lockedIds,
...props
}: MultiSelectInputProps<T>) {
const selectedValues = new Set(props.value.map((v) => v.id));
@@ -52,46 +54,63 @@ export function MultiSelectTagInput<T extends TagValue>({
"overflow-x-auto"
)}
>
{props.value.map((option) => (
<span
key={option.id}
className={cn(
"bg-muted-foreground/10 font-normal text-foreground rounded-sm",
"py-1 pl-1.5 pr-0.5 text-xs inline-flex items-center gap-0.5"
)}
onClick={(e) => e.stopPropagation()}
>
{option.text}
<button
className="p-0.5 flex-none cursor-pointer"
type="button"
onClick={(e) => {
e.stopPropagation();
let newValues = [];
if (selectedValues.has(option.id)) {
newValues = props.value.filter(
(v) => v.id !== option.id
);
} else {
newValues = [
...props.value,
option
];
}
props.onChange(newValues);
}}
{props.value.map((option) => {
const isLocked = lockedIds?.has(option.id);
return (
<span
key={option.id}
className={cn(
"bg-muted-foreground/10 font-normal text-foreground rounded-sm",
"py-1 pl-1.5 pr-0.5 text-xs inline-flex items-center gap-0.5",
isLocked && "opacity-60"
)}
onClick={(e) => e.stopPropagation()}
>
<XIcon className="size-3.5" />
</button>
</span>
))}
{option.text}
{isLocked ? (
<span className="p-0.5 flex-none">
<LockIcon className="size-3" />
</span>
) : (
<button
className="p-0.5 flex-none cursor-pointer"
type="button"
onClick={(e) => {
e.stopPropagation();
let newValues = [];
if (
selectedValues.has(
option.id
)
) {
newValues =
props.value.filter(
(v) =>
v.id !==
option.id
);
} else {
newValues = [
...props.value,
option
];
}
props.onChange(newValues);
}}
>
<XIcon className="size-3.5" />
</button>
)}
</span>
);
})}
<span className="pl-1 font-normal">{buttonText}</span>
</span>
<ChevronDownIcon className="ml-2 h-4 w-4 shrink-0 text-muted-foreground" />
</div>
</PopoverTrigger>
<PopoverContent className="p-0">
<MultiSelectContent {...props} />
<MultiSelectContent {...props} lockedIds={lockedIds} />
</PopoverContent>
</Popover>
);