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:
Charis authored and GitHub committed 2026-09-21 16:01:00 -04:00
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>
)
}