Files
buzz-sheet/components/admin/ui/option-select.tsx
T
gunshiz f311d3c417
CI / Build immutable images and deploy (push) Canceled after 0s
CI / Verify (push) Canceled after 53s
fix(images) : retry failed CDN images through Cloudflare resizing
2026-10-06 15:15:39 +07:00

140 lines
3.5 KiB
TypeScript

"use client";
import Image from "@/components/ui/resilient-image";
import type { ComponentProps, ReactNode } from "react";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { elementIconFilter } from "@/lib/catalog/element";
export type AdminSelectOption = {
value: string;
label: ReactNode;
};
type AdminDropdownProps = {
options: readonly AdminSelectOption[];
value?: string;
onValueChange: (value: string) => void;
placeholder?: string;
disabled?: boolean;
"aria-label"?: string;
triggerProps?: Omit<ComponentProps<typeof SelectTrigger>, "children">;
};
export function AdminDropdown({
options,
value,
onValueChange,
placeholder,
disabled,
"aria-label": ariaLabel,
triggerProps,
}: AdminDropdownProps) {
const selectedOption = options.find((option) => option.value === value);
return (
<Select
value={value}
disabled={disabled}
onValueChange={(next) => {
if (next !== null) onValueChange(next);
}}
>
<SelectTrigger aria-label={ariaLabel} {...triggerProps}>
<SelectValue placeholder={placeholder}>{selectedOption?.label}</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
);
}
type ConstellationSelectProps = Omit<AdminDropdownProps, "options"> & {
includeNone?: boolean;
includeRange?: boolean;
minimum?: 0 | 1;
};
export function ConstellationSelect({
includeNone = false,
includeRange = false,
minimum = 0,
...props
}: ConstellationSelectProps) {
const options: AdminSelectOption[] = [
...(includeNone ? [{ value: "none", label: "None" }] : []),
...Array.from({ length: 7 - minimum }, (_, index) => {
const level = index + minimum;
return { value: String(level), label: `C${level}` };
}),
...(includeRange ? [{ value: "0-6", label: "C0-6" }] : []),
];
return <AdminDropdown {...props} options={options} />;
}
type RefinementSelectProps = Omit<AdminDropdownProps, "options"> & {
includeNone?: boolean;
includeRange?: boolean;
};
export function RefinementSelect({
includeNone = false,
includeRange = false,
...props
}: RefinementSelectProps) {
const options: AdminSelectOption[] = [
...(includeNone ? [{ value: "none", label: "None" }] : []),
...Array.from({ length: 5 }, (_, index) => ({
value: String(index + 1),
label: `R${index + 1}`,
})),
...(includeRange ? [{ value: "1-5", label: "R1-5" }] : []),
];
return <AdminDropdown {...props} options={options} />;
}
type ElementSelectProps = Omit<AdminDropdownProps, "options"> & {
elements: readonly string[];
iconUrls?: Record<string, string | null | undefined>;
};
export function ElementSelect({ elements, iconUrls = {}, ...props }: ElementSelectProps) {
return (
<AdminDropdown
{...props}
options={elements.map((element) => ({
value: element,
label: (
<>
{iconUrls[element] ? (
<Image
src={iconUrls[element]}
alt=""
width={20}
height={20}
className="size-5 object-contain"
style={{ filter: elementIconFilter(element) }}
/>
) : null}
{element}
</>
),
}))}
/>
);
}