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.

132 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({ className, sideOffset = 6, collisionPadding = 8, ...props }: ComponentProps<typeof Primitive.Content>) {
14 return (
15 <Primitive.Portal>
16 <Primitive.Content
17 sideOffset={sideOffset}
18 // Never off the edge of a phone: kept 8px inside it, and as tall as there is room for.
19 collisionPadding={collisionPadding}
20 className={cn(
21 "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",
22 "data-[state=open]:animate-pop-in data-[state=closed]:animate-pop-out motion-reduce:animate-none",
23 className,
24 )}
25 {...props}
26 />
27 </Primitive.Portal>
28 );
29}
30
31export function DropdownMenuItem({
32 className,
33 variant = "default",
34 ...props
35}: ComponentProps<typeof Primitive.Item> & {
36 /** `destructive`: an item that deletes something, in the danger color (shadcn's variant). */
37 variant?: "default" | "destructive";
38}) {
39 return (
40 <Primitive.Item
41 data-variant={variant}
42 className={cn(
43 // Taller rows under a finger, so each is a fair target.
44 "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",
45 "data-highlighted:bg-line data-highlighted:text-fg data-disabled:opacity-50",
46 "[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-faint",
47 "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",
48 className,
49 )}
50 {...props}
51 />
52 );
53}
54
55export function DropdownMenuLabel({ className, ...props }: ComponentProps<typeof Primitive.Label>) {
56 return <Primitive.Label className={cn("px-2 py-1.5 text-xs text-faint", className)} {...props} />;
57}
58
59export function DropdownMenuSeparator({ className, ...props }: ComponentProps<typeof Primitive.Separator>) {
60 return <Primitive.Separator className={cn("-mx-1 my-1 h-px bg-line", className)} {...props} />;
61}
62
63export const DropdownMenuSub = Primitive.Sub;
64
65/** A row that opens a submenu beside it, with a chevron at its end. */
66export function DropdownMenuSubTrigger({ className, children, ...props }: ComponentProps<typeof Primitive.SubTrigger>) {
67 return (
68 <Primitive.SubTrigger
69 className={cn(
70 // Taller rows under a finger, so each is a fair target.
71 "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",
72 "data-highlighted:bg-line data-highlighted:text-fg data-[state=open]:bg-line data-[state=open]:text-fg data-disabled:opacity-50",
73 "[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-faint",
74 className,
75 )}
76 {...props}
77 >
78 {children}
79 <ChevronRight className="ml-auto" />
80 </Primitive.SubTrigger>
81 );
82}
83
84export function DropdownMenuSubContent({ className, sideOffset = 6, collisionPadding = 8, ...props }: ComponentProps<typeof Primitive.SubContent>) {
85 return (
86 <Primitive.Portal>
87 <Primitive.SubContent
88 sideOffset={sideOffset}
89 collisionPadding={collisionPadding}
90 className={cn(
91 "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",
92 "data-[state=open]:animate-pop-in data-[state=closed]:animate-pop-out motion-reduce:animate-none",
93 className,
94 )}
95 {...props}
96 />
97 </Primitive.Portal>
98 );
99}
100
101const CHOICE =
102 "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";
103
104/** A row that is on or off, with a check when it is on. */
105export function DropdownMenuCheckboxItem({ className, children, ...props }: ComponentProps<typeof Primitive.CheckboxItem>) {
106 return (
107 <Primitive.CheckboxItem className={cn(CHOICE, className)} {...props}>
108 <span className="absolute left-2 flex size-4 items-center justify-center">
109 <Primitive.ItemIndicator>
110 <Check size={14} />
111 </Primitive.ItemIndicator>
112 </span>
113 {children}
114 </Primitive.CheckboxItem>
115 );
116}
117
118export const DropdownMenuRadioGroup = Primitive.RadioGroup;
119
120/** One of a group's choices, with a dot on the chosen one. */
121export function DropdownMenuRadioItem({ className, children, ...props }: ComponentProps<typeof Primitive.RadioItem>) {
122 return (
123 <Primitive.RadioItem className={cn(CHOICE, className)} {...props}>
124 <span className="absolute left-2 flex size-4 items-center justify-center">
125 <Primitive.ItemIndicator>
126 <Circle size={8} className="fill-current" />
127 </Primitive.ItemIndicator>
128 </span>
129 {children}
130 </Primitive.RadioItem>
131 );
132}