"use client";
import * as React from "react";
import { Select as SelectPrimitive } from "@base-ui/react/select";
import { cva, type VariantProps } from "class-variance-authority";
import { outlineControlSurfaceClassName } from "../../lib/control-outline";
import { cn } from "../../lib/utils";
import {
PortalLayerContainerProvider,
usePortalLayerContainer,
} from "./portal-layer-context";
import { PrimitiveArrowIcon } from "./primitive-arrow-icon";
import { ScrollFade } from "./scroll-fade";
import { pressedSelectedItemClassName } from "./selection-state";
import { CheckIcon } from "@phosphor-icons/react";
const Select = SelectPrimitive.Root;
const dropdownHoverBorderClassName =
"[&:not(:focus):not([aria-expanded=true]):not([data-open]):not([data-popup-open]):not([data-state=open]):hover]:!border-[color:color-mix(in_oklab,var(--border)_20%,transparent)]";
const selectTriggerVariants = cva(
"flex w-fit cursor-pointer items-center justify-between gap-1.5 border whitespace-nowrap font-medium transition-colors outline-none select-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-[color:var(--destructive)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:aria-invalid:border-[color:color-mix(in_oklab,var(--destructive)_50%,transparent)] [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
placeholderTone: {
default: "data-placeholder:text-[color:var(--foreground)]",
muted: "data-placeholder:text-[color:var(--muted-foreground)]",
},
radius: {
default: "rounded-lg",
full: "rounded-full",
},
variant: {
default: `${outlineControlSurfaceClassName} ${dropdownHoverBorderClassName} focus-visible:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)] aria-expanded:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)] data-popup-open:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)] data-open:border-[color:color-mix(in_oklab,var(--border)_30%,transparent)]`,
ghost: `border-transparent bg-transparent bg-clip-border text-[color:var(--foreground)] focus-visible:border-[color:var(--ring)] focus-visible:ring-2 focus-visible:ring-[color:color-mix(in_oklab,var(--ring)_30%,transparent)] hover:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] hover:text-[color:var(--foreground)] active:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] active:text-[color:var(--foreground)] aria-expanded:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] aria-expanded:text-[color:var(--foreground)] data-open:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-open:text-[color:var(--foreground)] data-popup-open:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-popup-open:text-[color:var(--foreground)] data-[state=open]:bg-[color:color-mix(in_oklab,var(--input)_10%,transparent)] data-[state=open]:text-[color:var(--foreground)] ${pressedSelectedItemClassName}`,
},
size: {
sm: "h-6 gap-1 px-1.5 pr-1 py-0 text-xs/relaxed *:data-[slot=select-value]:gap-1 [&_svg:not([class*='size-'])]:size-3",
default:
"h-7 px-2 py-0.5 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5",
lg: "h-8 px-2.5 py-1 text-sm/relaxed [&_svg:not([class*='size-'])]:size-4",
xl: "h-10 px-3 py-1.5 text-base/relaxed [&_svg:not([class*='size-'])]:size-4",
},
},
defaultVariants: {
placeholderTone: "muted",
radius: "default",
variant: "default",
size: "default",
},
},
);
function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
return (
);
}
function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
return (
);
}
function SelectTrigger({
className,
placeholderTone = "muted",
radius = "default",
size = "default",
variant = "default",
children,
...props
}: SelectPrimitive.Trigger.Props & VariantProps) {
return (
{children}
}
/>
);
}
const selectTriggerArrowOpenClassName =
"group-aria-expanded/select-trigger:rotate-180 group-data-popup-open/select-trigger:rotate-180 group-data-open/select-trigger:rotate-180";
const SelectTriggerButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> &
VariantProps & {
open?: boolean;
}
>(function SelectTriggerButton(
{
children,
className,
open,
placeholderTone = "muted",
radius = "default",
size = "default",
variant = "default",
...props
},
ref,
) {
return (
);
});
function SelectContent({
className,
children,
side = "bottom",
sideOffset = 4,
align = "center",
alignOffset = 0,
alignItemWithTrigger = true,
...props
}: SelectPrimitive.Popup.Props &
Pick<
SelectPrimitive.Positioner.Props,
"align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
>) {
const resolvedContainer = usePortalLayerContainer();
const portalNodeRef = React.useRef(null);
return (
{children}
);
}
function SelectLabel({
className,
...props
}: SelectPrimitive.GroupLabel.Props) {
return (
);
}
function getTextTitle(
title: unknown,
children: React.ReactNode,
): string | undefined {
if (typeof title === "string" && title.length > 0) {
return title;
}
if (typeof children === "string" || typeof children === "number") {
return String(children);
}
return undefined;
}
function useOverflowTitle(
viewportRef: React.RefObject,
textTitle: string | undefined,
): string | undefined {
const [overflowing, setOverflowing] = React.useState(false);
React.useEffect(() => {
const viewport = viewportRef.current;
if (!viewport || !textTitle) {
setOverflowing(false);
return undefined;
}
const updateOverflow = () => {
setOverflowing(viewport.scrollWidth > viewport.clientWidth + 1);
};
updateOverflow();
if (typeof ResizeObserver === "undefined") {
return undefined;
}
const observer = new ResizeObserver(updateOverflow);
observer.observe(viewport);
if (viewport.firstElementChild instanceof HTMLElement) {
observer.observe(viewport.firstElementChild);
}
return () => {
observer.disconnect();
};
}, [textTitle, viewportRef]);
return overflowing ? textTitle : undefined;
}
function SelectItemTextContent({
children,
title,
}: {
children: React.ReactNode;
title: string | undefined;
}): React.JSX.Element {
const viewportRef = React.useRef(null);
const resolvedTitle = useOverflowTitle(viewportRef, title);
if (!title) {
return (
{children}
);
}
return (
{children}
);
}
function SelectItem({
className,
children,
title,
...props
}: SelectPrimitive.Item.Props) {
const textTitle = getTextTitle(title, children);
return (
{children}
}
>
);
}
function SelectSeparator({
className,
...props
}: SelectPrimitive.Separator.Props) {
return (
);
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps) {
return (
);
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps) {
return (
);
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectTriggerButton,
SelectValue,
};