Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion apps/dokploy/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,77 @@ import * as React from "react";

import { cn } from "@/lib/utils";

const Select = SelectPrimitive.Root;
// Traverse children to find all Select items and collect their values
function collectSelectItemValues(children: React.ReactNode): string[] {
const values: string[] = [];
React.Children.forEach(children, (child) => {
if (!React.isValidElement(child)) return;
// Identify Radix Select Item wrappers by displayName
// We compare against the underlying Radix Item display name to support both
// our wrapped SelectItem and any direct SelectPrimitive.Item usages.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const typeAny = child.type as any;
const displayName: string | undefined = typeAny?.displayName;
if (displayName === SelectPrimitive.Item.displayName) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const valueProp = (child.props as { value?: unknown }).value;
if (valueProp !== undefined && valueProp !== null && valueProp !== "") {
values.push(String(valueProp));
}
} else if (child.props && child.props.children) {
values.push(...collectSelectItemValues(child.props.children));
}
});
return values;
}

const Select = ({
children,
defaultValue,
value,
onValueChange,
...props
}: React.ComponentPropsWithoutRef<typeof SelectPrimitive.Root>) => {
// Auto-select a single option only when the Select is uncontrolled and has no defaultValue
const computedDefaultValue = React.useMemo(() => {
if (value !== undefined) return undefined; // controlled
if (
defaultValue !== undefined &&
defaultValue !== null &&
defaultValue !== ""
)
return undefined; // user provided meaningful value
const itemValues = collectSelectItemValues(children);
return itemValues.length === 1 ? itemValues[0] : undefined;
}, [children, value, defaultValue]);

// Auto-select the only option after async children load for both controlled and uncontrolled usages
React.useEffect(() => {
if (
defaultValue !== undefined &&
defaultValue !== null &&
defaultValue !== ""
)
return; // respect explicit non-empty default
const itemValues = collectSelectItemValues(children);
const hasSingleOption = itemValues.length === 1;
const hasNoValue = value === undefined || value === null || value === "";
if (hasSingleOption && hasNoValue) {
onValueChange?.(itemValues[0]!);
}
}, [children, defaultValue, value, onValueChange]);

return (
<SelectPrimitive.Root
defaultValue={defaultValue ?? computedDefaultValue}
value={value}
onValueChange={onValueChange}
{...props}
>
{children}
</SelectPrimitive.Root>
);
};

const SelectGroup = SelectPrimitive.Group;

Expand Down