// ~/src/siteplane/components/select/select.tsx "use client"; import { Select as SelectPrimitive } from "@base-ui/react/select"; import { useRender } from "@base-ui/react/use-render"; import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import * as React from "react"; import { useFormReset } from "../../system/form/use-form-reset"; import { SiteplaneUIPortalScope, useResolvedControlSize, type SiteplaneControlSize, } from "../../system/provider/siteplane-ui-provider"; import { ChoiceMenuItemCheck } from "../../system/styles/choice-menu-item"; import { surfaceClassNames } from "../../system/styles/surface"; import { cn, type WithStringClassName } from "../../system/utils/cn"; import { dropSlotClass, slotClass } from "../../system/utils/slot-class"; import { KitButton } from "../button/button-internal"; import { useFieldLabelPlacement } from "../field"; import { Separator } from "../separator"; import { Input } from "../input"; function SelectCaretIcon( props: React.ComponentProps<"svg">, ): React.ReactElement { return ( ); } export function Select( props: SelectPrimitive.Root.Props, ): React.ReactElement { const [resetKey, setResetKey] = React.useState(0); const markerRef = useFormReset(props.form, () => { if (props.value === undefined) setResetKey((current) => current + 1); }); return ( <> ); } export type SelectTriggerSize = SiteplaneControlSize; /** The trigger surface, the same four as the other form controls. */ export type SelectTriggerMode = "default" | "elevated" | "ghost" | "inset"; export interface SelectButtonProps extends useRender.ComponentProps<"button"> { size?: SelectTriggerSize; variant?: SelectTriggerMode; } export function SelectButton({ className, size, variant, render, children, ...props }: SelectButtonProps): React.ReactElement { const resolvedSize = useResolvedControlSize(size); const resolvedMode = variant ?? "default"; return ( <> {children} ); } export function SelectTrigger({ className, size, variant, children, render, ...props }: WithStringClassName & { size?: SelectTriggerSize | undefined; variant?: SelectTriggerMode | undefined; }): React.ReactElement { const resolvedSize = useResolvedControlSize(size); const resolvedMode = variant ?? "default"; // A floating Field lays out the trigger it holds (design-rework.css, // "Floating label fields"); the ghost mode draws no field to hold a label. const floating = useFieldLabelPlacement() === "floating" && resolvedMode !== "ghost"; return ( ) } > {children} ); } export function SelectValue({ className, ...props }: WithStringClassName): React.ReactElement { return ( ); } export function SelectPopup({ className, children, side = "bottom", // 8 px, measured across the reference's selects; action and filter menus // keep 4 px. sideOffset = 8, align = "start", alignOffset = 0, alignItemWithTrigger = false, anchor, portalProps, ...props }: WithStringClassName & { portalProps?: WithStringClassName; side?: SelectPrimitive.Positioner.Props["side"]; sideOffset?: SelectPrimitive.Positioner.Props["sideOffset"]; align?: SelectPrimitive.Positioner.Props["align"]; alignOffset?: SelectPrimitive.Positioner.Props["alignOffset"]; alignItemWithTrigger?: SelectPrimitive.Positioner.Props["alignItemWithTrigger"]; anchor?: SelectPrimitive.Positioner.Props["anchor"]; }): React.ReactElement { return ( {/* The scroll arrows are strips inside the panel, with the fade into the list on their ::before (design-rework.css). */} {/* The panel: its surface rule names this element alone, so the arrows beside it keep their own look (design-rework.css). */}
{children}
); } export function SelectItem({ className, children, ...props }: WithStringClassName): React.ReactElement { return ( {/* The check leads the row, in a slot every option keeps, so every label starts at the same x, as in Combobox and the menus (I8). */} {children} ); } export function SelectSeparator({ className, ...props }: WithStringClassName): React.ReactElement { return ( } {...props} /> ); } export function SelectGroup( props: WithStringClassName, ): React.ReactElement { return ( ); } /** * The label of a Select. Like FieldLabel and Label it carries no outer * margin: the stack around it spaces it, as a Field's gap does (S10). Its * colour and disabled state are the labels' shared rules (design-rework.css). */ export function SelectLabel({ className, ...props }: WithStringClassName): React.ReactElement { return ( ); } export function SelectGroupLabel({ className, ...props }: WithStringClassName): React.ReactElement { return ( ); } // SelectContent is the shadcn name of SelectPopup. export { SelectPrimitive, SelectPopup as SelectContent }; // ~/src/siteplane/components/select/index.ts export * from "./select";