Skip to content

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.sh
162 linesCodeBlameRaw
1import { Check, ChevronRight, Circle } from "lucide-react";
2import { DropdownMenu as Primitive } from "radix-ui";
3import type { ComponentProps } from "react";
4
5import { cn } from "../../lib/cn";
6
7// shadcn/ui's dropdown menu, styled with g1t's tokens.
8
9export const DropdownMenu = Primitive.Root;
10export const DropdownMenuTrigger = Primitive.Trigger;
11export const DropdownMenuGroup = Primitive.Group;
12
13export function DropdownMenuContent({
14 className,
15 sideOffset = 6,
16 collisionPadding = 8,
17 ...props
18}: ComponentProps<typeof Primitive.Content>) {
19 return (
20 <Primitive.Portal>
21 <Primitive.Content
22 sideOffset={sideOffset}
23 // Never off the edge of a phone: kept 8px inside it, and as tall as there is room for.
24 collisionPadding={collisionPadding}
25 className={cn(
26 "z-50 max-h-(--radix-dropdown-menu-content-available-height) max-w-[calc(100vw-1rem)] min-w-48 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg border border-line-strong bg-raised p-1 text-sm shadow-xl shadow-black/40",
27 "data-[state=open]:animate-pop-in data-[state=closed]:animate-pop-out motion-reduce:animate-none",
28 className,
29 )}
30 {...props}
31 />
32 </Primitive.Portal>
33 );
34}
35
36export function DropdownMenuItem({
37 className,
38 variant = "default",
39 ...props
40}: ComponentProps<typeof Primitive.Item> & {
41 /** `destructive`: an item that deletes something, in the danger color (shadcn's variant). */
42 variant?: "default" | "destructive";
43}) {
44 return (
45 <Primitive.Item
46 data-variant={variant}
47 className={cn(
48 // Taller rows under a finger, so each is a fair target.
49 "flex cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-fg/90 outline-none select-none pointer-coarse:min-h-10",
50 "data-highlighted:bg-line data-highlighted:text-fg data-disabled:opacity-50",
51 "[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-faint",
52 "data-[variant=destructive]:text-danger data-[variant=destructive]:data-highlighted:bg-danger/10 data-[variant=destructive]:data-highlighted:text-danger data-[variant=destructive]:[&_svg]:text-danger",
53 className,
54 )}
55 {...props}
56 />
57 );
58}
59
60export function DropdownMenuLabel({
61 className,
62 ...props
63}: ComponentProps<typeof Primitive.Label>) {
64 return (
65 <Primitive.Label
66 className={cn("px-2 py-1.5 text-xs text-faint", className)}
67 {...props}
68 />
69 );
70}
71
72export function DropdownMenuSeparator({
73 className,
74 ...props
75}: ComponentProps<typeof Primitive.Separator>) {
76 return (
77 <Primitive.Separator
78 className={cn("-mx-1 my-1 h-px bg-line", className)}
79 {...props}
80 />
81 );
82}
83
84export const DropdownMenuSub = Primitive.Sub;
85
86/** A row that opens a submenu beside it, with a chevron at its end. */
87export function DropdownMenuSubTrigger({
88 className,
89 children,
90 ...props
91}: ComponentProps<typeof Primitive.SubTrigger>) {
92 return (
93 <Primitive.SubTrigger
94 className={cn(
95 // Taller rows under a finger, so each is a fair target.
96 "flex cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-fg/90 outline-none select-none pointer-coarse:min-h-10",
97 "data-highlighted:bg-line data-highlighted:text-fg data-[state=open]:bg-line data-[state=open]:text-fg data-disabled:opacity-50",
98 "[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-faint",
99 className,
100 )}
101 {...props}
102 >
103 {children}
104 <ChevronRight className="ml-auto" />
105 </Primitive.SubTrigger>
106 );
107}
108
109export function DropdownMenuSubContent({
110 className,
111 sideOffset = 6,
112 collisionPadding = 8,
113 ...props
114}: ComponentProps<typeof Primitive.SubContent>) {
115 return (
116 <Primitive.Portal>
117 <Primitive.SubContent
118 sideOffset={sideOffset}
119 collisionPadding={collisionPadding}
120 className={cn(
121 "z-50 max-h-(--radix-dropdown-menu-content-available-height) max-w-[calc(100vw-1rem)] min-w-44 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg border border-line-strong bg-raised p-1 text-sm shadow-xl shadow-black/40",
122 "data-[state=open]:animate-pop-in data-[state=closed]:animate-pop-out motion-reduce:animate-none",
123 className,
124 )}
125 {...props}
126 />
127 </Primitive.Portal>
128 );
129}
130
131const CHOICE =
132 "relative flex cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-fg/90 outline-none select-none pointer-coarse:min-h-10 data-highlighted:bg-line data-highlighted:text-fg data-disabled:opacity-50 [&_svg]:shrink-0";
133
134/** A row that is on or off, with a check when it is on. */
135export function DropdownMenuCheckboxItem({ className, children, ...props }: ComponentProps<typeof Primitive.CheckboxItem>) {
136 return (
137 <Primitive.CheckboxItem className={cn(CHOICE, className)} {...props}>
138 <span className="absolute left-2 flex size-4 items-center justify-center">
139 <Primitive.ItemIndicator>
140 <Check size={14} />
141 </Primitive.ItemIndicator>
142 </span>
143 {children}
144 </Primitive.CheckboxItem>
145 );
146}
147
148export const DropdownMenuRadioGroup = Primitive.RadioGroup;
149
150/** One of a group's choices, with a dot on the chosen one. */
151export function DropdownMenuRadioItem({ className, children, ...props }: ComponentProps<typeof Primitive.RadioItem>) {
152 return (
153 <Primitive.RadioItem className={cn(CHOICE, className)} {...props}>
154 <span className="absolute left-2 flex size-4 items-center justify-center">
155 <Primitive.ItemIndicator>
156 <Circle size={8} className="fill-current" />
157 </Primitive.ItemIndicator>
158 </span>
159 {children}
160 </Primitive.RadioItem>
161 );
162}