"use client"; import React, { ChangeEvent, Children, forwardRef, useCallback, useEffect, useMemo, useState } from "react"; import * as SelectPrimitive from "@radix-ui/react-select"; import { defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled } from "../styles"; import { CheckIcon, KeyboardArrowDownIcon } from "../icons"; import { cls } from "../util"; import { SelectInputLabel } from "./common/SelectInputLabel"; import { usePortalContainer } from "../hooks/PortalContainerContext"; export type SelectValue = string | number | boolean; export type SelectProps = { open?: boolean, name?: string, fullWidth?: boolean, id?: string, onOpenChange?: (open: boolean) => void, value?: T, className?: string, inputClassName?: string, viewportClassName?: string, onChange?: React.EventHandler>, onValueChange?: (updatedValue: T) => void, placeholder?: React.ReactNode, renderValue?: (value: T) => React.ReactNode, size?: "smallest" | "small" | "medium" | "large", label?: React.ReactNode | string, disabled?: boolean, error?: boolean, position?: "item-aligned" | "popper", endAdornment?: React.ReactNode, inputRef?: React.RefObject, padding?: boolean, invisible?: boolean, children?: React.ReactNode; dataType?: "string" | "number" | "boolean"; portalContainer?: HTMLElement | null; // Explicitly added to props type if missing }; export const Select = forwardRef(({ inputRef, open, name, fullWidth = false, id, onOpenChange, value, onChange, onValueChange, className, inputClassName, viewportClassName, placeholder, renderValue, label, size = "large", error, disabled, padding = true, position = "item-aligned", endAdornment, invisible, children, dataType = "string", portalContainer: manualContainer, // Rename to avoid confusion ...props }, ref) => { const [openInternal, setOpenInternal] = useState(open ?? false); useEffect(() => { setOpenInternal(open ?? false); }, [open]); // Get the portal container from context const contextContainer = usePortalContainer(); // Resolve final container (Manual Prop > Context Container > Undefined/Body) const finalContainer = (manualContainer ?? contextContainer ?? undefined) as HTMLElement | undefined; const onValueChangeInternal = useCallback((newValue: string) => { let typedValue: SelectValue = newValue; if (dataType === "boolean") { if (newValue === "true") typedValue = true; else if (newValue === "false") typedValue = false; } else if (dataType === "number") { if (!isNaN(Number(newValue)) && newValue.trim() !== "") typedValue = Number(newValue); } onValueChange?.(typedValue as any); if (onChange) { const event = { target: { name, value: typedValue } } as unknown as ChangeEvent; onChange(event); } }, [onChange, onValueChange, name, dataType]); const hasValue = Array.isArray(value) ? value.length > 0 : (value != null && value !== "" && value !== undefined); const stringValue = value !== undefined ? String(value) : undefined; const displayChildren = useMemo(() => { if (!hasValue || renderValue) return null; // Find the child that matches the current value to display its content let found: React.ReactNode = null; Children.forEach(children, (child) => { if (React.isValidElement(child) && String(child.props.value) === String(value)) { found = child.props.children; } }); return found; }, [children, hasValue, renderValue, value]); return ( { onOpenChange?.(open); setOpenInternal(open); }} {...props}> {typeof label === "string" ? {label} : label}
{ e.preventDefault(); e.stopPropagation(); }} placeholder={placeholder} className={"w-full"}> {hasValue && value !== undefined && renderValue ? renderValue(value) : (displayChildren || placeholder) }
{endAdornment && (
{ e.preventDefault(); e.stopPropagation(); }}> {endAdornment}
)}
{/* Pass the calculated finalContainer */} {children}
); }); Select.displayName = "Select"; // ... (SelectItem and SelectGroup remain unchanged) export type SelectItemProps = { value: T, children?: React.ReactNode, disabled?: boolean, className?: string, }; export const SelectItem = React.memo(function SelectItem({ value, children, disabled, className }: SelectItemProps) { // Convert value to string for Radix UI const stringValue = String(value); return *]:w-full", "overflow-visible", className )}> {children}
; }); export type SelectGroupProps = { label: React.ReactNode, children: React.ReactNode, className?: string }; export const SelectGroup = React.memo(function SelectGroup({ label, children, className }: SelectGroupProps) { return <> {label} {children} ; });