flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/apps/web/app/components/ui/select.tsx

159 lines5,866 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Workspace names and icons, and a component kit for every control1import { Check, ChevronDown, ChevronUp } from "lucide-react";
2import { Select as Primitive } from "radix-ui";
3import type { ComponentProps, ReactNode } from "react";
4
5import { cn } from "../../lib/cn";
6
7// shadcn/ui's select, styled with g1t's tokens.
8//
9// Inside a <Form>, give <Select> a `name` and Radix renders a hidden native
10// <select> beside the trigger, so the value posts like any other field.
11// An item's value can never be "": use a word such as "none" and map it.
12
13export const Select = Primitive.Root;
14export const SelectGroup = Primitive.Group;
15export const SelectValue = Primitive.Value;
16
17const TRIGGER_SIZES = { sm: "h-8 px-2.5 text-[0.8125rem]", default: "h-9", lg: "h-11" };
18
19/** The control people see: the chosen value, or the placeholder, and a chevron. */
20export function SelectTrigger({
21 className,
22 size = "default",
23 children,
24 ...props
25}: ComponentProps<typeof Primitive.Trigger> & { size?: "sm" | "default" | "lg" }) {
26 return (
27 <Primitive.Trigger
28 data-size={size}
29 className={cn(
30 "group/select flex w-full min-w-0 items-center justify-between gap-2 rounded-md border border-line bg-bg px-3 text-left text-sm whitespace-nowrap text-fg outline-none transition-colors",
31 TRIGGER_SIZES[size],
32 "hover:border-line-strong focus-visible:border-accent-dim focus-visible:ring-2 focus-visible:ring-accent/25 focus-visible:outline-none",
33 "data-[state=open]:border-accent-dim data-[state=open]:ring-2 data-[state=open]:ring-accent/25",
34 "data-placeholder:text-faint aria-invalid:border-danger/70",
35 "disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-line",
36 "[&>span]:flex [&>span]:min-w-0 [&>span]:items-center [&>span]:gap-2 [&>span]:truncate",
37 "[&_svg]:shrink-0",
38 className,
39 )}
40 {...props}
41 >
42 {children}
43 <Primitive.Icon asChild>
44 <ChevronDown
45 size={15}
46 className="text-faint transition-transform duration-200 group-data-[state=open]/select:rotate-180"
47 />
48 </Primitive.Icon>
49 </Primitive.Trigger>
50 );
51}
52
53export function SelectContent({
54 className,
55 children,
56 position = "popper",
57 sideOffset = 6,
58 ...props
59}: ComponentProps<typeof Primitive.Content>) {
60 return (
61 <Primitive.Portal>
62 <Primitive.Content
63 position={position}
64 sideOffset={position === "popper" ? sideOffset : undefined}
65 className={cn(
66 "relative z-50 max-h-(--radix-select-content-available-height) min-w-36 overflow-hidden rounded-lg border border-line-strong bg-raised text-sm text-fg shadow-xl shadow-black/40",
67 "origin-(--radix-select-content-transform-origin) data-[state=open]:animate-pop-in data-[state=closed]:animate-pop-out motion-reduce:animate-none",
68 position === "popper" && "min-w-(--radix-select-trigger-width) max-w-[calc(100vw-2rem)]",
69 className,
70 )}
71 {...props}
72 >
73 <SelectScrollUpButton />
74 <Primitive.Viewport
75 className={cn("p-1", position === "popper" && "max-h-80 scroll-my-1")}
76 >
77 {children}
78 </Primitive.Viewport>
79 <SelectScrollDownButton />
80 </Primitive.Content>
81 </Primitive.Portal>
82 );
83}
84
85export function SelectLabel({ className, ...props }: ComponentProps<typeof Primitive.Label>) {
86 return (
87 <Primitive.Label
88 className={cn("px-2 pt-2 pb-1 text-[0.6875rem] font-medium tracking-wide text-faint uppercase", className)}
89 {...props}
90 />
91 );
92}
93
94/**
95 * One option. `icon` (an icon or an avatar) and the label show in the
96 * trigger once chosen; `description` shows only in the list.
97 */
98export function SelectItem({
99 className,
100 children,
101 icon,
102 description,
103 ...props
104}: ComponentProps<typeof Primitive.Item> & { icon?: ReactNode; description?: ReactNode }) {
105 return (
106 <Primitive.Item
107 className={cn(
108 "relative flex w-full cursor-default items-start gap-2 rounded-md py-1.5 pr-8 pl-2 text-fg/90 outline-none select-none",
109 "data-highlighted:bg-line data-highlighted:text-fg data-disabled:pointer-events-none data-disabled:opacity-45",
110 "data-[state=checked]:text-fg",
111 className,
112 )}
113 {...props}
114 >
115 <span className="flex min-w-0 flex-col">
116 <Primitive.ItemText>
117 <span className="flex min-w-0 items-center gap-2 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted">
118 {icon}
119 <span className="truncate">{children}</span>
120 </span>
121 </Primitive.ItemText>
122 {description && (
123 <span className={cn("mt-0.5 text-xs leading-snug text-faint", icon != null && "pl-6")}>{description}</span>
124 )}
125 </span>
126 <span className="absolute top-2 right-2 flex size-4 items-center justify-center">
127 <Primitive.ItemIndicator>
128 <Check size={14} className="text-accent" />
129 </Primitive.ItemIndicator>
130 </span>
131 </Primitive.Item>
132 );
133}
134
135export function SelectSeparator({ className, ...props }: ComponentProps<typeof Primitive.Separator>) {
136 return <Primitive.Separator className={cn("-mx-1 my-1 h-px bg-line", className)} {...props} />;
137}
138
139export function SelectScrollUpButton({ className, ...props }: ComponentProps<typeof Primitive.ScrollUpButton>) {
140 return (
141 <Primitive.ScrollUpButton
142 className={cn("flex cursor-default items-center justify-center py-1 text-faint", className)}
143 {...props}
144 >
145 <ChevronUp size={14} />
146 </Primitive.ScrollUpButton>
147 );
148}
149
150export function SelectScrollDownButton({ className, ...props }: ComponentProps<typeof Primitive.ScrollDownButton>) {
151 return (
152 <Primitive.ScrollDownButton
153 className={cn("flex cursor-default items-center justify-center py-1 text-faint", className)}
154 {...props}
155 >
156 <ChevronDown size={14} />
157 </Primitive.ScrollDownButton>
158 );
159}