ProjectBranchSelectorSheet.tsx143 lines · main
| 1 | import { Box, Boxes, GitBranch } from 'lucide-react' |
| 2 | import { |
| 3 | cn, |
| 4 | SheetHeader, |
| 5 | SheetTitle, |
| 6 | Tabs_Shadcn_, |
| 7 | TabsContent_Shadcn_, |
| 8 | TabsList_Shadcn_, |
| 9 | } from 'ui' |
| 10 | import MobileSheetNav from 'ui-patterns/MobileSheetNav/MobileSheetNav' |
| 11 | |
| 12 | import { ProjectBranchSelectorSheetTabTrigger } from './ProjectBranchSelectorSheetTabTrigger' |
| 13 | import { BranchDropdown } from '@/components/layouts/AppLayout/BranchDropdown' |
| 14 | import { OrganizationDropdown } from '@/components/layouts/AppLayout/OrganizationDropdown' |
| 15 | import { ProjectDropdown } from '@/components/layouts/AppLayout/ProjectDropdown' |
| 16 | import type { Branch } from '@/data/branches/branches-query' |
| 17 | import type { ProjectDetail } from '@/data/projects/project-detail-query' |
| 18 | import type { Organization } from '@/types' |
| 19 | |
| 20 | const EMBEDDED_CLASSNAME = |
| 21 | 'bg-transparent border-0 shadow-none min-h-0 flex-1 flex flex-col overflow-hidden rounded-none' |
| 22 | |
| 23 | export 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 | |
| 37 | export 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 | } |