mirror of
https://github.com/supabase/supabase.git
synced 2026-06-13 01:39:53 +08:00
## Problem The `_Shadcn_` suffix isn't needed anymore on `Command` components ## Solution - Remove the `_Shadcn_` suffix - Simplify UI package exports - Apply prettier <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Simplified command component imports and exports across the UI library by removing internal naming aliases and adopting direct component references. Updated the public UI package barrel export to use wildcard re-exports for cleaner API surface. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46153?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
206 lines
6.6 KiB
TypeScript
206 lines
6.6 KiB
TypeScript
import { useParams } from 'common'
|
|
import { Box, Plus } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import type { ComponentProps } from 'react'
|
|
import { useState } from 'react'
|
|
import { Button, CommandGroup, CommandItem } from 'ui'
|
|
import { ShimmeringLoader } from 'ui-patterns'
|
|
|
|
import { AppLayoutDropdownTriggerButton } from './AppLayoutDropdown'
|
|
import { sanitizeRoute } from './ProjectDropdown.utils'
|
|
import { ProjectRowLink } from './ProjectRowLink'
|
|
import { useEmbeddedCloseHandler } from './useEmbeddedCloseHandler'
|
|
import { OrganizationProjectSelector } from '@/components/ui/OrganizationProjectSelector'
|
|
import PartnerIcon from '@/components/ui/PartnerIcon'
|
|
import { getManagedByFromOrganizationPartner } from '@/data/organizations/managed-by-utils'
|
|
import type { OrgProject } from '@/data/projects/org-projects-infinite-query'
|
|
import { useProjectDetailQuery } from '@/data/projects/project-detail-query'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { IS_PLATFORM } from '@/lib/constants'
|
|
import type { ManagedBy } from '@/lib/constants/infrastructure'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
|
|
// --- Sub-components ---
|
|
|
|
interface ProjectDropdownNewProjectActionsProps {
|
|
organizationSlug: string | undefined
|
|
embedded: boolean
|
|
onClose: () => void
|
|
onNavigate: (href: string) => void
|
|
}
|
|
|
|
function ProjectDropdownNewProjectActions({
|
|
organizationSlug,
|
|
embedded,
|
|
onClose,
|
|
onNavigate,
|
|
}: ProjectDropdownNewProjectActionsProps) {
|
|
const href = `/new/${organizationSlug}`
|
|
|
|
if (embedded) {
|
|
return (
|
|
<Button type="default" block size="small" asChild icon={<Plus size={14} strokeWidth={1.5} />}>
|
|
<Link
|
|
href={href}
|
|
onClick={onClose}
|
|
className="text-xs text-foreground-light hover:text-foreground"
|
|
>
|
|
New project
|
|
</Link>
|
|
</Button>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<CommandGroup>
|
|
<CommandItem
|
|
className="cursor-pointer w-full"
|
|
onSelect={() => {
|
|
onClose()
|
|
onNavigate(href)
|
|
}}
|
|
onClick={onClose}
|
|
>
|
|
<Link href={href} onClick={onClose} className="w-full flex items-center gap-2">
|
|
<Plus size={14} strokeWidth={1.5} />
|
|
<p>New project</p>
|
|
</Link>
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
)
|
|
}
|
|
|
|
const ProjectDropdownNonPlatformView = ({ projectName }: { projectName: string }) => {
|
|
return <div className="text-sm px-3 py-1">{projectName}</div>
|
|
}
|
|
|
|
interface ProjectDropdownPlatformViewProps {
|
|
projectRef: string | undefined
|
|
projectName: string
|
|
projectManagedBy?: ManagedBy
|
|
selectorProps: Omit<
|
|
ComponentProps<typeof OrganizationProjectSelector>,
|
|
'renderTrigger' | 'embedded'
|
|
>
|
|
}
|
|
|
|
function ProjectDropdownPlatformView({
|
|
projectRef,
|
|
projectName,
|
|
projectManagedBy,
|
|
selectorProps,
|
|
}: ProjectDropdownPlatformViewProps) {
|
|
return (
|
|
<div className="flex items-center shrink-0">
|
|
<Link href={`/project/${projectRef}`} className="flex items-center gap-2 shrink-0 text-sm">
|
|
<Box size={14} strokeWidth={1.5} className="text-foreground-lighter" />
|
|
<span title={projectName} className="text-foreground max-w-32 lg:max-w-64 truncate">
|
|
{projectName}
|
|
</span>
|
|
{projectManagedBy && <PartnerIcon organization={{ managed_by: projectManagedBy }} />}
|
|
</Link>
|
|
|
|
<OrganizationProjectSelector
|
|
{...selectorProps}
|
|
renderTrigger={() => <AppLayoutDropdownTriggerButton className="shrink-0" />}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// --- Main component ---
|
|
|
|
interface ProjectDropdownProps {
|
|
embedded?: boolean
|
|
className?: string
|
|
onClose?: () => void
|
|
}
|
|
|
|
export const ProjectDropdown = ({
|
|
embedded = false,
|
|
className,
|
|
onClose,
|
|
}: ProjectDropdownProps = {}) => {
|
|
const router = useRouter()
|
|
const { ref } = useParams()
|
|
const { data: project, isPending: isLoadingProject } = useSelectedProjectQuery()
|
|
const { data: selectedOrganization } = useSelectedOrganizationQuery()
|
|
|
|
const isBranch = project?.parentRef !== project?.ref
|
|
const { data: parentProject, isPending: isLoadingParentProject } = useProjectDetailQuery(
|
|
{ ref: project?.parent_project_ref },
|
|
{ enabled: isBranch }
|
|
)
|
|
const selectedProject = parentProject ?? project
|
|
|
|
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
|
|
const track = useTrack()
|
|
|
|
const [open, setOpen] = useState(false)
|
|
const close = useEmbeddedCloseHandler(embedded, onClose, setOpen)
|
|
const selectedProjectManagedBy = selectedProject?.integration_source
|
|
? getManagedByFromOrganizationPartner(undefined, selectedProject.integration_source)
|
|
: selectedOrganization?.billing_partner
|
|
? selectedOrganization.managed_by
|
|
: undefined
|
|
|
|
if (isLoadingProject || (isBranch && isLoadingParentProject) || !selectedProject) {
|
|
if (!embedded) return <ShimmeringLoader className="p-2 md:mr-2 md:w-[90px]" />
|
|
}
|
|
|
|
const handleSetOpen = embedded
|
|
? (_value: boolean) => onClose?.()
|
|
: (next: boolean) => {
|
|
if (next) track('header_project_dropdown_opened')
|
|
setOpen(next)
|
|
}
|
|
|
|
const selectorProps = {
|
|
open,
|
|
setOpen: handleSetOpen,
|
|
selectedRef: ref,
|
|
onSelect: (project: { ref: string }) => {
|
|
const sanitizedRoute = sanitizeRoute(router.route, router.query)
|
|
const href = sanitizedRoute?.replace('[ref]', project.ref) ?? `/project/${project.ref}`
|
|
close()
|
|
router.push(href)
|
|
},
|
|
renderRow: (project: Pick<OrgProject, 'ref' | 'name' | 'status' | 'integration_source'>) => (
|
|
<ProjectRowLink
|
|
project={project}
|
|
selectedRef={ref}
|
|
route={router.route}
|
|
routerQueries={router.query}
|
|
/>
|
|
),
|
|
renderActions: (_setOpen: (value: boolean) => void, options?: { embedded?: boolean }) =>
|
|
projectCreationEnabled ? (
|
|
<ProjectDropdownNewProjectActions
|
|
organizationSlug={selectedOrganization?.slug}
|
|
embedded={options?.embedded ?? false}
|
|
onClose={close}
|
|
onNavigate={(href) => router.push(href)}
|
|
/>
|
|
) : null,
|
|
}
|
|
|
|
if (embedded)
|
|
return (
|
|
<OrganizationProjectSelector {...selectorProps} embedded className={className} fetchOnMount />
|
|
)
|
|
|
|
return IS_PLATFORM ? (
|
|
<ProjectDropdownPlatformView
|
|
projectRef={project?.ref}
|
|
projectName={selectedProject?.name ?? ''}
|
|
projectManagedBy={selectedProjectManagedBy}
|
|
selectorProps={selectorProps}
|
|
/>
|
|
) : (
|
|
<ProjectDropdownNonPlatformView projectName={selectedProject?.name ?? ''} />
|
|
)
|
|
}
|