mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Initialize tabs UI for explorer (#48789)
## Context Continued ground work for Explorer - just initializes the Tab UI for Explorer as such: - Plan is to continue using the existing tabs store + EditorTabs component - Purely visual, nothing functional <img width="1389" height="556" alt="image" src="https://github.com/user-attachments/assets/d46c5ae7-01a8-4887-9452-11b98327d6bf" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an Explorer workspace with animated navigation and tab controls. * Added a home tab and a new-tab menu for creating notebooks, with chat creation shown as unavailable. * Added support for notebook tabs in the editor and Explorer navigation. * Added flexible tab layouts with custom tab content, optional new-tab actions, and configurable collapse controls. * Improved editor navigation to recognize Explorer workspaces alongside existing table and SQL editors. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
a18ee934cd
commit
4550ee18a4
4 files changed
+121
-32
No files matched your search
@@ -1,7 +1,13 @@
|
||||
import { AnimatePresence } from 'framer-motion'
|
||||
import { useParams } from 'common'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { Home, MessageCirclePlus, NotebookText, Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ComponentProps, ReactNode, useState } from 'react'
|
||||
import { cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui'
|
||||
|
||||
import { ProjectLayoutWithAuth } from '../ProjectLayout'
|
||||
import { EditorTabs } from '../Tabs/Tabs'
|
||||
import { type ExplorerResourceType } from './ExplorerLayout.constants'
|
||||
import { ExplorerNavChats } from './ExplorerNavChats'
|
||||
import { ExplorerNavHome } from './ExplorerNavHome'
|
||||
@@ -42,7 +48,70 @@ export const ExplorerLayout = ({ browserTitle, children, title }: ExplorerLayout
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
<div className="flex flex-col h-full">
|
||||
<div className={cn('h-10 md:min-h-(--header-height) flex items-center bg-surface-100')}>
|
||||
<EditorTabs
|
||||
isCollapseButtonHidden
|
||||
customTabs={<HomeTabButton />}
|
||||
newTabButton={<NewTabButton />}
|
||||
/>
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</ProjectLayoutWithAuth>
|
||||
)
|
||||
}
|
||||
|
||||
const TabClassName =
|
||||
'flex items-center justify-center min-w-(--header-height) min-h-(--header-height) hover:bg-surface-100 shrink-0 border-b'
|
||||
|
||||
const HomeTabButton = () => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
const isActive = router.pathname.endsWith('/explorer')
|
||||
|
||||
return (
|
||||
<Link href={`/project/${ref}/explorer`} className={cn(TabClassName, 'border-r')}>
|
||||
<Home
|
||||
size={14}
|
||||
strokeWidth={1.5}
|
||||
className={cn(
|
||||
isActive ? 'text-foreground' : 'text-foreground-lighter hover:text-foreground-light'
|
||||
)}
|
||||
/>
|
||||
<span className="sr-only">Open Explorer home</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
const NewTabButton = () => {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<motion.button
|
||||
className={TabClassName}
|
||||
onClick={() => {}}
|
||||
initial={{ opacity: 0, scale: 0.8, x: -10 }}
|
||||
animate={{ opacity: 1, scale: 1, x: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Plus
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="text-foreground-lighter hover:text-foreground-light"
|
||||
/>
|
||||
</motion.button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-40" align="end">
|
||||
<DropdownMenuItem className="gap-x-2">
|
||||
<NotebookText size={14} />
|
||||
<span>New notebook</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled className="gap-x-2">
|
||||
<MessageCirclePlus size={14} />
|
||||
<span>New chat</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { useParams } from 'common'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { Plus, X } from 'lucide-react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import {
|
||||
cn,
|
||||
ContextMenu,
|
||||
@@ -37,7 +37,19 @@ import {
|
||||
type TabCloseConfirmation,
|
||||
} from '@/state/tabs'
|
||||
|
||||
export const EditorTabs = () => {
|
||||
interface EditorTabsProps {
|
||||
customTabs?: ReactNode
|
||||
newTabButton?: ReactNode
|
||||
isCollapseButtonHidden?: boolean
|
||||
}
|
||||
|
||||
// [Joshen] Will be adjusting this component to support Explorer
|
||||
// Will require quite a bit of cleaning up once Explorer supercedes SQL Editor
|
||||
export const EditorTabs = ({
|
||||
customTabs,
|
||||
newTabButton,
|
||||
isCollapseButtonHidden,
|
||||
}: EditorTabsProps) => {
|
||||
const { ref, id } = useParams()
|
||||
const router = useRouter()
|
||||
const { setLastVisitedSnippet, setLastVisitedTable } = useDashboardHistory()
|
||||
@@ -175,7 +187,10 @@ export const EditorTabs = () => {
|
||||
value={hasNewTab ? 'new' : (tabs.activeTab ?? undefined)}
|
||||
onValueChange={handleTabChange}
|
||||
>
|
||||
<CollapseButton hideTabs={false} />
|
||||
{!isCollapseButtonHidden && <CollapseButton hideTabs={false} />}
|
||||
|
||||
{customTabs}
|
||||
|
||||
<TabsList
|
||||
ref={tabsListRef}
|
||||
className={cn(
|
||||
@@ -269,25 +284,26 @@ export const EditorTabs = () => {
|
||||
)}
|
||||
|
||||
<AnimatePresence initial={false}>
|
||||
{!hasNewTab && (
|
||||
<motion.button
|
||||
className="flex items-center justify-center w-10 min-h-(--header-height) hover:bg-surface-100 shrink-0 border-b"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/project/${router.query.ref}/${editor === 'table' ? 'editor' : 'sql'}/new?skip=true`
|
||||
)
|
||||
}
|
||||
initial={{ opacity: 0, scale: 0.8, x: -10 }}
|
||||
animate={{ opacity: 1, scale: 1, x: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Plus
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="text-foreground-lighter hover:text-foreground-light"
|
||||
/>
|
||||
</motion.button>
|
||||
)}
|
||||
{!hasNewTab &&
|
||||
(newTabButton ?? (
|
||||
<motion.button
|
||||
className="flex items-center justify-center w-10 min-h-(--header-height) hover:bg-surface-100 shrink-0 border-b"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/project/${router.query.ref}/${editor === 'table' ? 'editor' : 'sql'}/new?skip=true`
|
||||
)
|
||||
}
|
||||
initial={{ opacity: 0, scale: 0.8, x: -10 }}
|
||||
animate={{ opacity: 1, scale: 1, x: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Plus
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="text-foreground-lighter hover:text-foreground-light"
|
||||
/>
|
||||
</motion.button>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
<div className="grow h-full border-b pr-6" />
|
||||
</TabsList>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { useParams } from 'common'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export function useEditorType(): 'table' | 'sql' | undefined {
|
||||
export type EditorType = 'table' | 'sql' | 'explorer' | undefined
|
||||
|
||||
export function useEditorType(): EditorType {
|
||||
const pathname = usePathname()
|
||||
const { ref } = useParams()
|
||||
|
||||
return pathname?.includes(`/project/${ref}/editor`)
|
||||
? 'table'
|
||||
: pathname?.includes(`/project/${ref}/sql`)
|
||||
? 'sql'
|
||||
: undefined
|
||||
if (pathname?.includes(`/project/${ref}/editor`)) return 'table'
|
||||
if (pathname?.includes(`/project/${ref}/sql`)) return 'sql'
|
||||
if (pathname?.includes(`/project/${ref}/explorer`)) return 'explorer'
|
||||
|
||||
return undefined
|
||||
}
|
||||
@@ -13,11 +13,13 @@ import { proxy, subscribe, useSnapshot } from 'valtio'
|
||||
|
||||
import { buildTableEditorUrl } from '@/components/grid/SupabaseGrid.utils'
|
||||
import type { SqlSnippetSource } from '@/components/interfaces/SQLEditor/querySource'
|
||||
import { ENTITY_TYPE } from '@/data/entity-types/entity-type-constants'
|
||||
import type { EditorType } from '@/components/layouts/editors/EditorsLayout.hooks'
|
||||
import type { ENTITY_TYPE } from '@/data/entity-types/entity-type-constants'
|
||||
|
||||
export const editorEntityTypes = {
|
||||
table: ['r', 'v', 'm', 'f', 'p'],
|
||||
sql: ['sql'],
|
||||
explorer: ['notebook'],
|
||||
}
|
||||
|
||||
export type TabType = ENTITY_TYPE | 'sql'
|
||||
@@ -469,7 +471,7 @@ export function createTabsState(projectRef: string) {
|
||||
}: {
|
||||
id: string
|
||||
router: NextRouter
|
||||
editor?: 'sql' | 'table'
|
||||
editor?: EditorType
|
||||
onClose?: (id: string) => void
|
||||
onClearDashboardHistory: () => void
|
||||
}) => {
|
||||
|
||||
Reference in new issue
Block a user