Files
prowler/ui/components/shadcn/select/select.tsx
T
Alan Buscaglia 587187419f feat(ui): add findings triage (#11704)
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
2026-07-01 17:55:33 +02:00

256 lines
8.5 KiB
TypeScript

"use client";
import * as SelectPrimitive from "@radix-ui/react-select";
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { ComponentProps, type WheelEvent } from "react";
import { cn } from "@/lib/utils";
const stopWheelPropagation = (event: WheelEvent<HTMLElement>) => {
event.stopPropagation();
};
const SELECT_TRIGGER_SIZES = {
XS: "xs",
SM: "sm",
DEFAULT: "default",
} as const;
const SELECT_TRIGGER_ICON_SIZES = {
SM: "sm",
DEFAULT: "default",
} as const;
type SelectTriggerSize =
(typeof SELECT_TRIGGER_SIZES)[keyof typeof SELECT_TRIGGER_SIZES];
type SelectTriggerIconSize =
(typeof SELECT_TRIGGER_ICON_SIZES)[keyof typeof SELECT_TRIGGER_ICON_SIZES];
function Select({
allowDeselect = false,
...props
}: ComponentProps<typeof SelectPrimitive.Root> & {
allowDeselect?: boolean;
}) {
const handleValueChange = (nextValue: string) => {
if (allowDeselect && props.value === nextValue) {
// Single-select with deselect
props.onValueChange?.("");
} else {
// Single-select
props.onValueChange?.(nextValue);
}
};
return (
<SelectPrimitive.Root
data-slot="select"
{...props}
onValueChange={handleValueChange}
/>
);
}
function SelectGroup({
...props
}: ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
function SelectValue({
...props
}: ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
function SelectTrigger({
className,
size = SELECT_TRIGGER_SIZES.DEFAULT,
iconSize = SELECT_TRIGGER_ICON_SIZES.DEFAULT,
children,
...props
}: ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: SelectTriggerSize;
iconSize?: SelectTriggerIconSize;
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"group border-border-input-primary bg-bg-input-primary text-bg-button-secondary data-[placeholder]:text-bg-button-secondary [&_svg:not([class*='text-'])]:text-bg-button-secondary aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:bg-bg-neutral-tertiary active:bg-border-neutral-tertiary dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-border-input-primary-press focus-visible:ring-border-input-primary-press flex w-full items-center justify-between gap-2 overflow-hidden rounded-lg border px-4 py-3 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-1 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 has-[>svg]:px-3 data-[size=default]:h-[52px] data-[size=sm]:h-10 data-[size=xs]:h-8 data-[size=xs]:gap-1.5 data-[size=xs]:px-3 data-[size=xs]:py-0 data-[size=xs]:text-xs *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:focus-visible:ring-slate-400 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-6",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon
className={cn(
"text-bg-button-secondary shrink-0 opacity-70 transition-transform duration-200 group-data-[state=open]:rotate-180",
iconSize === SELECT_TRIGGER_ICON_SIZES.SM ? "size-4" : "size-6",
)}
aria-hidden="true"
/>
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
function SelectContent({
className,
children,
position = "popper",
align = "start",
width = "default",
style,
...props
}: ComponentProps<typeof SelectPrimitive.Content> & {
width?: "default" | "wide";
}) {
const widthClasses =
width === "wide"
? "w-[min(max(var(--radix-select-trigger-width),24rem),calc(100vw-2rem))] max-w-[32rem]"
: undefined;
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-border-input-primary bg-bg-input-primary relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-hidden rounded-lg border",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
widthClasses,
className,
)}
style={{
maxHeight: "var(--radix-select-content-available-height)",
...style,
}}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
data-slot="select-viewport"
onWheelCapture={stopWheelPropagation}
style={{
maxHeight:
"min(300px, var(--radix-select-content-available-height, 300px))",
}}
className={cn(
"minimal-scrollbar flex flex-col gap-1 overflow-x-hidden overflow-y-auto overscroll-contain p-3",
position === "popper" &&
(width === "wide"
? "w-full scroll-my-1"
: "w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"),
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
}
function SelectLabel({
className,
...props
}: ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-bg-button-secondary px-2 py-1.5 text-xs", className)}
{...props}
/>
);
}
function SelectItem({
className,
children,
...props
}: ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-bg-button-secondary text-bg-button-secondary data-[state=checked]:bg-button-tertiary/10 data-[state=checked]:text-text-neutral-primary data-[state=checked]:hover:bg-button-tertiary/15 data-[state=checked]:focus:bg-button-tertiary/15 relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-3 pr-4 pl-4 text-sm outline-hidden select-none hover:bg-slate-200 data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 dark:hover:bg-slate-700/50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5",
className,
)}
{...props}
>
<SelectPrimitive.ItemText asChild>
<span className="flex min-w-0 flex-1 items-center gap-2">
{children}
</span>
</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);
}
function SelectSeparator({
className,
...props
}: ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function SelectScrollUpButton({
className,
...props
}: ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronUpIcon className="text-bg-button-secondary size-4" />
</SelectPrimitive.ScrollUpButton>
);
}
function SelectScrollDownButton({
className,
...props
}: ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronDownIcon className="text-bg-button-secondary size-4" />
</SelectPrimitive.ScrollDownButton>
);
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
};