← full-stack-fastapi-template  /  frontend/src/components/ui/select.tsx

1
import * as React from "react"
2
import * as SelectPrimitive from "@radix-ui/react-select"
3
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
4
5
import { cn } from "@/lib/utils"
6
7
function Select({
8
  ...props
9
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
10
  return <SelectPrimitive.Root data-slot="select" {...props} />
11
}
12
13
function SelectGroup({
14
  ...props
15
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
16
  return <SelectPrimitive.Group data-slot="select-group" {...props} />
17
}
18
19
function SelectValue({
20
  ...props
21
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
22
  return <SelectPrimitive.Value data-slot="select-value" {...props} />
23
}
24
25
function SelectTrigger({
26
  className,
27
  size = "default",
28
  children,
29
  ...props
30
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
31
  size?: "sm" | "default"
32
}) {
33
  return (
34
    <SelectPrimitive.Trigger
35
      data-slot="select-trigger"
36
      data-size={size}
37
      className={cn(
38
        "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
39
        className
40
      )}
41
      {...props}
42
    >
43
      {children}
44
      <SelectPrimitive.Icon asChild>
45
        <ChevronDownIcon className="size-4 opacity-50" />
46
      </SelectPrimitive.Icon>
47
    </SelectPrimitive.Trigger>
48
  )
49
}
50
51
function SelectContent({
52
  className,
53
  children,
54
  position = "popper",
55
  align = "center",
56
  ...props
57
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
58
  return (
59
    <SelectPrimitive.Portal>
60
      <SelectPrimitive.Content
61
        data-slot="select-content"
62
        className={cn(
63
          "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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
64
          position === "popper" &&
65
            "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
66
          className
67
        )}
68
        position={position}
69
        align={align}
70
        {...props}
71
      >
72
        <SelectScrollUpButton />
73
        <SelectPrimitive.Viewport
74
          className={cn(
75
            "p-1",
76
            position === "popper" &&
77
              "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
78
          )}
79
        >
80
          {children}
81
        </SelectPrimitive.Viewport>
82
        <SelectScrollDownButton />
83
      </SelectPrimitive.Content>
84
    </SelectPrimitive.Portal>
85
  )
86
}
87
88
function SelectLabel({
89
  className,
90
  ...props
91
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
92
  return (
93
    <SelectPrimitive.Label
94
      data-slot="select-label"
95
      className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
96
      {...props}
97
    />
98
  )
99
}
100
101
function SelectItem({
102
  className,
103
  children,
104
  ...props
105
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
106
  return (
107
    <SelectPrimitive.Item
108
      data-slot="select-item"
109
      className={cn(
110
        "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
111
        className
112
      )}
113
      {...props}
114
    >
115
      <span className="absolute right-2 flex size-3.5 items-center justify-center">
116
        <SelectPrimitive.ItemIndicator>
117
          <CheckIcon className="size-4" />
118
        </SelectPrimitive.ItemIndicator>
119
      </span>
120
      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
121
    </SelectPrimitive.Item>
122
  )
123
}
124
125
function SelectSeparator({
126
  className,
127
  ...props
128
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
129
  return (
130
    <SelectPrimitive.Separator
131
      data-slot="select-separator"
132
      className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
133
      {...props}
134
    />
135
  )
136
}
137
138
function SelectScrollUpButton({
139
  className,
140
  ...props
141
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
142
  return (
143
    <SelectPrimitive.ScrollUpButton
144
      data-slot="select-scroll-up-button"
145
      className={cn(
146
        "flex cursor-default items-center justify-center py-1",
147
        className
148
      )}
149
      {...props}
150
    >
151
      <ChevronUpIcon className="size-4" />
152
    </SelectPrimitive.ScrollUpButton>
153
  )
154
}
155
156
function SelectScrollDownButton({
157
  className,
158
  ...props
159
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
160
  return (
161
    <SelectPrimitive.ScrollDownButton
162
      data-slot="select-scroll-down-button"
163
      className={cn(
164
        "flex cursor-default items-center justify-center py-1",
165
        className
166
      )}
167
      {...props}
168
    >
169
      <ChevronDownIcon className="size-4" />
170
    </SelectPrimitive.ScrollDownButton>
171
  )
172
}
173
174
export {
175
  Select,
176
  SelectContent,
177
  SelectGroup,
178
  SelectItem,
179
  SelectLabel,
180
  SelectScrollDownButton,
181
  SelectScrollUpButton,
182
  SelectSeparator,
183
  SelectTrigger,
184
  SelectValue,
185
}
186