mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix(studio): surface link to Explorer start-page preference (#50670)
## Summary * Added a persistent "Preferences" link (with Settings icon) pinned to the bottom of the Explorer sidebar's root navigation * Links to `/account/me#dashboard`, the existing Account settings anchor that contains the Explorer startup preference dropdown * Restructured sidebar layout to keep the footer link pinned while content scrolls, addressing [FE-4437](https://linear.app/supabase/issue/FE-4437/give-higher-visibility-to-changing-explorer-start-page-preference) * The Home tab disappears when "SQL query" is selected as the start page, so this visible link ensures users can always find and change their preference ## Test plan - [X] Typecheck passes on `apps/studio/components/layouts/ExplorerLayout/ExplorerNavHome.tsx` - [X] ESLint passes on the changed file - [X] Manual verification of sidebar behavior and link functionality Fixes [FE-4437](https://linear.app/supabase/issue/FE-4437/give-higher-visibility-to-changing-explorer-start-page-preference): "Give higher visibility to changing Explorer start page preference" <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a fixed **Preferences** link to the Explorer navigation footer. - **UI Improvements** - Updated Explorer navigation layout so resource links and recently updated items scroll independently from the footer. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
6bec90a744
commit
5b50701c11
1 file changed
+86
-77
@@ -1,6 +1,6 @@
|
||||
import { useParams } from 'common'
|
||||
import { motion } from 'framer-motion'
|
||||
import { ChevronRight, Plus, SquareCode } from 'lucide-react'
|
||||
import { ChevronRight, Plus, Settings, SquareCode } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
|
||||
@@ -50,90 +50,99 @@ export const ExplorerNavHome = ({
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -LEVEL_OFFSET }}
|
||||
transition={LEVEL_TRANSITION}
|
||||
className="absolute inset-0 flex flex-col gap-4 overflow-y-auto p-3"
|
||||
className="absolute inset-0 flex flex-col overflow-hidden"
|
||||
>
|
||||
<nav className="flex flex-col gap-px">
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={rowClassName(false)}
|
||||
onClick={() => createQuery()}
|
||||
>
|
||||
<SquareCode size={14} className="shrink-0" />
|
||||
<span className="flex-1 text-left">Run SQL</span>
|
||||
<Plus size={14} className="shrink-0 text-foreground-muted" />
|
||||
</button>
|
||||
{EXPLORER_SECTIONS.map(({ type, label, icon: Icon }) => {
|
||||
return (
|
||||
<button
|
||||
key={type}
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={rowClassName(false)}
|
||||
onClick={() => onSelectSection(type)}
|
||||
>
|
||||
<Icon size={14} className="shrink-0" />
|
||||
<span className="flex-1 text-left">{label}</span>
|
||||
{type === 'notebook' ? (
|
||||
isPending ? (
|
||||
<ShimmeringLoader className="w-3 py-2" />
|
||||
) : (
|
||||
<span className="text-xs text-foreground-lighter">{notebookCount}</span>
|
||||
)
|
||||
) : (
|
||||
<span className="text-xs text-foreground-lighter">{chats.length}</span>
|
||||
)}
|
||||
<ChevronRight size={14} className="shrink-0 text-foreground-muted" />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<section className="flex flex-col gap-px">
|
||||
<h3 className="mb-2 px-2 font-mono text-sm font-normal uppercase text-foreground-lighter">
|
||||
Recently updated
|
||||
</h3>
|
||||
{recentItems.length === 0 ? (
|
||||
<p className="px-2 text-xs text-foreground-lighter">Nothing edited yet</p>
|
||||
) : (
|
||||
recentItems.map((item) => {
|
||||
const Icon = EXPLORER_SECTIONS.find((section) => section.type === item.type)?.icon
|
||||
const content = (
|
||||
<>
|
||||
{Icon && <Icon size={14} className="shrink-0" aria-hidden="true" />}
|
||||
<span className="flex-1 truncate text-left">{item.label}</span>
|
||||
<span className="shrink-0 text-xs text-foreground-lighter">
|
||||
{formatRelativeTimeShort(item.updatedAt)}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
|
||||
return item.type === 'chat' ? (
|
||||
<div className="flex-1 min-h-0 flex flex-col gap-4 overflow-y-auto p-3">
|
||||
<nav className="flex flex-col gap-px">
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={rowClassName(false)}
|
||||
onClick={() => createQuery()}
|
||||
>
|
||||
<SquareCode size={14} className="shrink-0" />
|
||||
<span className="flex-1 text-left">Run SQL</span>
|
||||
<Plus size={14} className="shrink-0 text-foreground-muted" />
|
||||
</button>
|
||||
{EXPLORER_SECTIONS.map(({ type, label, icon: Icon }) => {
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
key={type}
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={rowClassName(false)}
|
||||
onClick={() => openChat(item.id)}
|
||||
onDoubleClick={() => tabs.makeTabPermanent(createTabId('chat', { id: item.id }))}
|
||||
onClick={() => onSelectSection(type)}
|
||||
>
|
||||
{content}
|
||||
<Icon size={14} className="shrink-0" />
|
||||
<span className="flex-1 text-left">{label}</span>
|
||||
{type === 'notebook' ? (
|
||||
isPending ? (
|
||||
<ShimmeringLoader className="w-3 py-2" />
|
||||
) : (
|
||||
<span className="text-xs text-foreground-lighter">{notebookCount}</span>
|
||||
)
|
||||
) : (
|
||||
<span className="text-xs text-foreground-lighter">{chats.length}</span>
|
||||
)}
|
||||
<ChevronRight size={14} className="shrink-0 text-foreground-muted" />
|
||||
</button>
|
||||
) : (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/project/${ref}/explorer/notebook/${item.id}`}
|
||||
className={rowClassName(false)}
|
||||
onDoubleClick={() =>
|
||||
tabs.makeTabPermanent(createTabId('notebook', { id: item.id }))
|
||||
}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</section>
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<section className="flex flex-col gap-px">
|
||||
<h3 className="mb-2 px-2 font-mono text-sm font-normal uppercase text-foreground-lighter">
|
||||
Recently updated
|
||||
</h3>
|
||||
{recentItems.length === 0 ? (
|
||||
<p className="px-2 text-xs text-foreground-lighter">Nothing edited yet</p>
|
||||
) : (
|
||||
recentItems.map((item) => {
|
||||
const Icon = EXPLORER_SECTIONS.find((section) => section.type === item.type)?.icon
|
||||
const content = (
|
||||
<>
|
||||
{Icon && <Icon size={14} className="shrink-0" aria-hidden="true" />}
|
||||
<span className="flex-1 truncate text-left">{item.label}</span>
|
||||
<span className="shrink-0 text-xs text-foreground-lighter">
|
||||
{formatRelativeTimeShort(item.updatedAt)}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
|
||||
return item.type === 'chat' ? (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={rowClassName(false)}
|
||||
onClick={() => openChat(item.id)}
|
||||
onDoubleClick={() => tabs.makeTabPermanent(createTabId('chat', { id: item.id }))}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
) : (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/project/${ref}/explorer/notebook/${item.id}`}
|
||||
className={rowClassName(false)}
|
||||
onDoubleClick={() =>
|
||||
tabs.makeTabPermanent(createTabId('notebook', { id: item.id }))
|
||||
}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 border-t border-default p-3">
|
||||
<Link href="/account/me#dashboard" className={rowClassName(false)}>
|
||||
<Settings size={14} className="shrink-0" />
|
||||
<span className="flex-1 text-left">Preferences</span>
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user