ProjectBranchSelectorSheet.tsx143 lines · main
1import { Box, Boxes, GitBranch } from 'lucide-react'
2import {
3 cn,
4 SheetHeader,
5 SheetTitle,
6 Tabs_Shadcn_,
7 TabsContent_Shadcn_,
8 TabsList_Shadcn_,
9} from 'ui'
10import MobileSheetNav from 'ui-patterns/MobileSheetNav/MobileSheetNav'
11
12import { ProjectBranchSelectorSheetTabTrigger } from './ProjectBranchSelectorSheetTabTrigger'
13import { BranchDropdown } from '@/components/layouts/AppLayout/BranchDropdown'
14import { OrganizationDropdown } from '@/components/layouts/AppLayout/OrganizationDropdown'
15import { ProjectDropdown } from '@/components/layouts/AppLayout/ProjectDropdown'
16import type { Branch } from '@/data/branches/branches-query'
17import type { ProjectDetail } from '@/data/projects/project-detail-query'
18import type { Organization } from '@/types'
19
20const EMBEDDED_CLASSNAME =
21 'bg-transparent border-0 shadow-none min-h-0 flex-1 flex flex-col overflow-hidden rounded-none'
22
23export interface ProjectBranchSelectorSheetProps {
24 open: boolean
25 onOpenChange: (open: boolean) => void
26 selectedRef: string | undefined
27 onClose: () => void
28 selectedOrganization?: Organization | null
29 displayProject?: ProjectDetail | null
30 selectedBranch?: Branch | null
31 selectedOrganizationName?: string | null
32 selectedProjectName?: string | null
33 selectedBranchName?: string | null
34 isMainBranch?: boolean
35}
36
37export function ProjectBranchSelectorSheet({
38 open,
39 onOpenChange,
40 selectedRef,
41 onClose,
42 selectedOrganization,
43 displayProject,
44 selectedBranch,
45 selectedOrganizationName,
46 selectedProjectName,
47 selectedBranchName,
48 isMainBranch,
49}: ProjectBranchSelectorSheetProps) {
50 const orgLabel = selectedOrganizationName ?? selectedOrganization?.name
51 const projectLabel = selectedProjectName ?? displayProject?.name
52 const branchLabel = selectedBranchName ?? selectedBranch?.name ?? 'main'
53
54 const tabs = [
55 orgLabel && {
56 value: 'organization' as const,
57 label: orgLabel,
58 icon: Boxes,
59 },
60 projectLabel && {
61 value: 'project' as const,
62 label: projectLabel,
63 icon: Box,
64 },
65 branchLabel && {
66 value: 'branch' as const,
67 label: branchLabel,
68 icon: GitBranch,
69 },
70 ].filter(Boolean) as Array<{
71 value: 'organization' | 'project' | 'branch'
72 label: string
73 icon: typeof Boxes
74 }>
75
76 const defaultTab =
77 (selectedRef && tabs.some((t) => t.value === 'project') ? 'project' : tabs[0]?.value) ??
78 'organization'
79
80 if (tabs.length === 0) {
81 return null
82 }
83
84 return (
85 <MobileSheetNav
86 open={open}
87 onOpenChange={onOpenChange}
88 className="flex flex-col overflow-hidden h-[85dvh] md:max-h-[500px]"
89 >
90 <Tabs_Shadcn_
91 defaultValue={defaultTab}
92 className="flex flex-col flex-1 min-h-0 overflow-hidden p-0"
93 >
94 <SheetHeader className="border-0 border-default p-0 shrink-0">
95 <SheetTitle className="sr-only">Switch organization, project or branch</SheetTitle>
96 <TabsList_Shadcn_
97 className={cn(
98 'w-full grid gap-0 shrink-0',
99 tabs.length === 1 && 'grid-cols-1',
100 tabs.length === 2 && 'grid-cols-2',
101 tabs.length === 3 && 'grid-cols-3'
102 )}
103 >
104 {tabs.map(({ value, label, icon }, index) => (
105 <ProjectBranchSelectorSheetTabTrigger
106 key={value}
107 value={value}
108 label={label}
109 icon={icon}
110 isMainBranch={value === 'branch' ? isMainBranch : undefined}
111 isLast={index === tabs.length - 1}
112 />
113 ))}
114 </TabsList_Shadcn_>
115 </SheetHeader>
116 {tabs.some((t) => t.value === 'organization') && (
117 <TabsContent_Shadcn_
118 value="organization"
119 className="flex-1 min-h-0 overflow-hidden flex flex-col mt-0 p-0 data-[state=inactive]:hidden"
120 >
121 <OrganizationDropdown embedded className={EMBEDDED_CLASSNAME} onClose={onClose} />
122 </TabsContent_Shadcn_>
123 )}
124 {tabs.some((t) => t.value === 'project') && (
125 <TabsContent_Shadcn_
126 value="project"
127 className="flex-1 min-h-0 overflow-hidden flex flex-col mt-0 py-0 data-[state=inactive]:hidden"
128 >
129 <ProjectDropdown embedded className={EMBEDDED_CLASSNAME} onClose={onClose} />
130 </TabsContent_Shadcn_>
131 )}
132 {tabs.some((t) => t.value === 'branch') && (
133 <TabsContent_Shadcn_
134 value="branch"
135 className="flex-1 min-h-0 overflow-hidden flex flex-col mt-0 p-0 data-[state=inactive]:hidden"
136 >
137 <BranchDropdown embedded className={EMBEDDED_CLASSNAME} onClose={onClose} />
138 </TabsContent_Shadcn_>
139 )}
140 </Tabs_Shadcn_>
141 </MobileSheetNav>
142 )
143}