All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Fixes all 542 ESLint problems in the web package to achieve 0 errors and 0 warnings. Changes: - Fixed 144 issues: nullish coalescing, return types, unused variables - Fixed 118 issues: unnecessary conditions, type safety, template literals - Fixed 79 issues: non-null assertions, unsafe assignments, empty functions - Fixed 67 issues: explicit return types, promise handling, enum comparisons - Fixed 45 final warnings: missing return types, optional chains - Fixed 25 typecheck-related issues: async/await, type assertions, formatting - Fixed JSX.Element namespace errors across 90+ files All Quality Rails violations resolved. Lint and typecheck both pass with 0 problems. Files modified: 118 components, tests, hooks, and utilities Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.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-gray-300 bg-white px-3 py-2 text-sm ${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-gray-300 bg-white shadow-lg">
|
|
{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 hover:bg-gray-100"
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|