mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 10:55:06 +03:00
Moves Explorer’s back navigation and create actions into a reusable sidebar breadcrumb header. Reduces product-menu headings globally to `text-sm` and keeps breadcrumb links free of padding, borders, and backgrounds. ### How to test 1. Open `/project/<ref>/explorer` and confirm the header shows Explorer and the SQL Editor switch action. 2. Open Notebooks and Chats. Confirm the header shows `Explorer > Notebooks/Chats` and the corresponding create action works. 3. Return using the Explorer breadcrumb with a click or Tab + Enter. Check that the label stays aligned and has no hover background. 4. Open another product, such as Database, and confirm its sidebar heading uses the smaller font size. 5. At a mobile viewport, open the menu and repeat the notebook/chat actions and back navigation without closing the sheet. Confirm the header stays current and disappears when returning to the main menu or opening another product. Validation: 18 focused tests, typecheck, formatting, and lint ratchet passed. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a shared Explorer sidebar header with breadcrumbs and contextual actions for creating notebooks and chats. - Added keyboard-accessible navigation between the Explorer overview and notebook or chat sections. - Added support for customized product menu headers across project layouts. - **Improvements** - Centralized Explorer navigation and actions in the shared sidebar layout. - Improved mobile menu updates when navigating between Explorer resources. - Refined Explorer home layout and drag-handle behavior across screen sizes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
104 lines
3.1 KiB
TypeScript
104 lines
3.1 KiB
TypeScript
import { useDebounce } from '@uidotdev/usehooks'
|
|
import { useParams } from 'common'
|
|
import { NotebookText } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { useMemo, useState } from 'react'
|
|
import { cn } from 'ui'
|
|
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import { ExplorerNavResourceWrapper, rowClassName } from './ExplorerLayout.constants'
|
|
import {
|
|
InfiniteListDefault,
|
|
LoaderForIconMenuItems,
|
|
type RowComponentBaseProps,
|
|
} from '@/components/ui/InfiniteList'
|
|
import {
|
|
NotebookRow,
|
|
useNotebooksInfiniteQuery,
|
|
} from '@/data/content/notebooks/notebooks-infinite-query'
|
|
|
|
const NOTEBOOK_ROW_HEIGHT = 28
|
|
|
|
type NotebookListItemProps = RowComponentBaseProps<NotebookRow> & {
|
|
projectRef: string | undefined
|
|
activeNotebookId: string | undefined
|
|
}
|
|
|
|
const NotebookListItem = ({
|
|
item: notebook,
|
|
style,
|
|
projectRef,
|
|
activeNotebookId,
|
|
}: NotebookListItemProps) => {
|
|
const isActive = activeNotebookId === notebook.id
|
|
|
|
return (
|
|
<Link
|
|
href={`/project/${projectRef}/explorer/notebook/${notebook.id}`}
|
|
className={rowClassName(isActive)}
|
|
style={style}
|
|
>
|
|
<NotebookText size={14} className={cn('shrink-0', isActive && 'text-foreground')} />
|
|
<span className="truncate text-left">{notebook.name}</span>
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
export const ExplorerNavNotebooks = () => {
|
|
const router = useRouter()
|
|
const { ref, id } = useParams()
|
|
|
|
const [search, setSearch] = useState('')
|
|
const debouncedSearch = useDebounce(search, 500)
|
|
|
|
const {
|
|
data: notebooksData,
|
|
isPending,
|
|
hasNextPage,
|
|
isFetchingNextPage,
|
|
fetchNextPage,
|
|
} = useNotebooksInfiniteQuery({
|
|
projectRef: ref,
|
|
limit: 100,
|
|
name: search.length === 0 ? search : debouncedSearch,
|
|
})
|
|
|
|
const notebooks = useMemo(() => {
|
|
const items = notebooksData?.pages.flatMap((page) => page.content) ?? []
|
|
return items
|
|
}, [notebooksData?.pages])
|
|
|
|
const activeNotebookId = router.pathname.includes('/explorer/notebook/') ? id : undefined
|
|
|
|
const itemProps = useMemo(() => ({ projectRef: ref, activeNotebookId }), [ref, activeNotebookId])
|
|
|
|
return (
|
|
<ExplorerNavResourceWrapper type="notebook" search={search} setSearch={setSearch}>
|
|
<div className="flex flex-1 min-h-0 flex-col px-3 pb-3">
|
|
{isPending ? (
|
|
<GenericSkeletonLoader />
|
|
) : notebooks.length === 0 ? (
|
|
<p className="px-2 py-2 text-xs text-foreground-lighter">
|
|
{search ? 'No notebooks found' : 'No notebooks created yet'}
|
|
</p>
|
|
) : (
|
|
<InfiniteListDefault
|
|
className="h-full w-full"
|
|
items={notebooks}
|
|
itemProps={itemProps}
|
|
ItemComponent={NotebookListItem}
|
|
LoaderComponent={LoaderForIconMenuItems}
|
|
getItemKey={(index) => notebooks[index]?.id ?? `notebook-${index}`}
|
|
getItemSize={() => NOTEBOOK_ROW_HEIGHT}
|
|
gap={1}
|
|
hasNextPage={hasNextPage}
|
|
isLoadingNextPage={isFetchingNextPage}
|
|
onLoadNextPage={fetchNextPage}
|
|
/>
|
|
)}
|
|
</div>
|
|
</ExplorerNavResourceWrapper>
|
|
)
|
|
}
|