Some checks failed
ci/woodpecker/push/web Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
131 lines
3.0 KiB
TypeScript
131 lines
3.0 KiB
TypeScript
import * as React from "react";
|
|
|
|
export interface SelectProps {
|
|
value?: string;
|
|
onValueChange?: (value: string) => void;
|
|
defaultValue?: string;
|
|
disabled?: boolean;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export interface SelectTriggerProps {
|
|
id?: string;
|
|
className?: string;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export interface SelectContentProps {
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export interface SelectItemProps {
|
|
value: string;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export interface SelectValueProps {
|
|
placeholder?: string;
|
|
}
|
|
|
|
const SelectContext = React.createContext<{
|
|
value?: string;
|
|
onValueChange?: (value: string) => void;
|
|
isOpen: boolean;
|
|
setIsOpen: (open: boolean) => void;
|
|
}>({
|
|
isOpen: false,
|
|
setIsOpen: () => {
|
|
// Default no-op
|
|
},
|
|
});
|
|
|
|
export function Select({
|
|
value,
|
|
onValueChange,
|
|
defaultValue,
|
|
children,
|
|
}: SelectProps): React.JSX.Element {
|
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
|
|
const currentValue = value ?? internalValue;
|
|
|
|
const handleValueChange = (newValue: string): void => {
|
|
if (value === undefined) {
|
|
setInternalValue(newValue);
|
|
}
|
|
onValueChange?.(newValue);
|
|
setIsOpen(false);
|
|
};
|
|
|
|
const contextValue: {
|
|
value?: string;
|
|
onValueChange?: (value: string) => void;
|
|
isOpen: boolean;
|
|
setIsOpen: (open: boolean) => void;
|
|
} = { isOpen, setIsOpen };
|
|
|
|
if (currentValue !== undefined) {
|
|
contextValue.value = currentValue;
|
|
}
|
|
contextValue.onValueChange = handleValueChange;
|
|
|
|
return (
|
|
<SelectContext.Provider value={contextValue}>
|
|
<div className="relative">{children}</div>
|
|
</SelectContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function SelectTrigger({
|
|
id,
|
|
className = "",
|
|
children,
|
|
}: SelectTriggerProps): React.JSX.Element {
|
|
const { isOpen, setIsOpen } = React.useContext(SelectContext);
|
|
|
|
return (
|
|
<button
|
|
id={id}
|
|
type="button"
|
|
onClick={() => {
|
|
setIsOpen(!isOpen);
|
|
}}
|
|
className={`flex h-10 w-full items-center justify-between rounded-md border border-border bg-bg px-3 py-2 text-sm text-text ${className}`}
|
|
>
|
|
{children}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export function SelectValue({ placeholder }: SelectValueProps): React.JSX.Element {
|
|
const { value } = React.useContext(SelectContext);
|
|
|
|
return <span>{value ?? placeholder}</span>;
|
|
}
|
|
|
|
export function SelectContent({ children }: SelectContentProps): React.JSX.Element | null {
|
|
const { isOpen } = React.useContext(SelectContext);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border border-border bg-surface shadow-md">
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SelectItem({ value, children }: SelectItemProps): React.JSX.Element {
|
|
const { onValueChange } = React.useContext(SelectContext);
|
|
|
|
return (
|
|
<div
|
|
onClick={() => onValueChange?.(value)}
|
|
className="cursor-pointer px-3 py-2 text-sm text-text hover:bg-surface-2"
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|