mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 05:09:12 +03:00
refine
This commit is contained in:
1 parent
f6730bfd4b
commit
d0767cd76e
8 files changed
+194
-138
No files matched your search
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { CircleIcon, LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
|
||||
import { LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import * as React from 'react'
|
||||
@@ -79,7 +79,7 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
Find guides, blocks, starter apps, and theme settings.
|
||||
</DialogDescription>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandList className="min-h-0 max-h-[min(400px,calc(100dvh-8rem))] overscroll-contain [&_[cmdk-group]]:p-2! [&_[cmdk-item]]:rounded-md [&_[cmdk-item]]:p-2!">
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup key="pages" heading="Pages">
|
||||
{gettingStarted.items.map((navItem) => (
|
||||
@@ -88,9 +88,6 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
value={navItem.title}
|
||||
onSelect={() => runCommand(() => router.push(navItem.href as string))}
|
||||
>
|
||||
<div className="mr-2 flex h-4 w-4 items-center justify-center">
|
||||
<CircleIcon className="h-3 w-3" strokeWidth={1} />
|
||||
</div>
|
||||
{navItem.title}
|
||||
</CommandItem>
|
||||
))}
|
||||
@@ -111,7 +108,6 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
})
|
||||
}
|
||||
>
|
||||
<CircleIcon className="mr-2 h-3 w-3" strokeWidth={1} />
|
||||
{block.title}
|
||||
</CommandItem>
|
||||
))}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function CopyDocPrompt({ title, pagePath, command }: CopyDocPromptProps)
|
||||
}
|
||||
onClick={copyPrompt}
|
||||
>
|
||||
{status === 'copied' ? 'Prompt copied' : 'Copy prompt'}
|
||||
{status === 'copied' ? 'Prompt copied' : 'Copy agent prompt'}
|
||||
</Button>
|
||||
<span
|
||||
role="status"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { CommandCopyButton } from './command-copy-button'
|
||||
import { TerminalCode } from './terminal-code'
|
||||
|
||||
export function InstallCommand({ command }: { command: string }) {
|
||||
return (
|
||||
<div className="flex items-start gap-3 rounded-lg border bg-surface-75 p-4">
|
||||
<pre className="min-w-0 flex-1 overflow-x-auto whitespace-pre-wrap break-words font-mono text-xs leading-6">
|
||||
<code>{command}</code>
|
||||
</pre>
|
||||
<TerminalCode command={command} />
|
||||
<CommandCopyButton command={command} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Check, Copy } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
|
||||
|
||||
import { TerminalCode } from './terminal-code'
|
||||
import type { PackageManager } from '@/lib/install-command'
|
||||
|
||||
interface InstallationPanelProps {
|
||||
@@ -72,9 +73,7 @@ export function InstallationPanel({ title, pagePath, commands }: InstallationPan
|
||||
</div>
|
||||
{packageManagers.map((manager) => (
|
||||
<TabsContent key={manager} value={manager} className="m-0 p-5">
|
||||
<pre className="min-w-0 flex-1 overflow-x-auto whitespace-pre-wrap break-words font-mono text-xs leading-6 text-foreground">
|
||||
<code>{commands[manager]}</code>
|
||||
</pre>
|
||||
<TerminalCode command={commands[manager] ?? ''} />
|
||||
</TabsContent>
|
||||
))}
|
||||
<TabsContent value="prompt" className="m-0 p-5">
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import supabaseLogoIcon from 'common/assets/images/supabase-logo-icon.svg'
|
||||
import { ArrowUpRight, ChevronRight, Code2 } from 'lucide-react'
|
||||
import { ArrowUpRight, ChevronRight, Code2, Grid2X2 } from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
@@ -16,7 +15,6 @@ import {
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
@@ -28,21 +26,23 @@ import { Sidebar } from '@/components/sidebar'
|
||||
import { ThemeSwitcherDropdown } from '@/components/theme-switcher-dropdown'
|
||||
import { getLibraryBlockHref, libraryBlocks, libraryCategories } from '@/config/library'
|
||||
import { useFramework } from '@/context/framework-context'
|
||||
import { useMenuAim } from '@/hooks/use-menu-aim'
|
||||
|
||||
export function SiteHeader() {
|
||||
const pathname = usePathname()
|
||||
const { framework } = useFramework()
|
||||
const [activeCategory, setActiveCategory] = useState<string>(libraryCategories[0].name)
|
||||
|
||||
const menuAim = useMenuAim(activeCategory, setActiveCategory)
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 border-b bg-background/90 backdrop-blur-lg">
|
||||
<header className="sticky top-0 z-40 before:pointer-events-none before:absolute before:inset-x-0 before:top-0 before:h-24 before:bg-gradient-to-b before:from-background before:via-background/90 before:via-60% before:to-transparent">
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:bg-background focus:p-3"
|
||||
>
|
||||
Skip to content
|
||||
</a>
|
||||
<div className="mx-auto flex h-14 max-w-site items-center justify-between gap-4 px-4 md:px-8">
|
||||
<div className="relative mx-auto flex h-14 max-w-site items-center justify-between gap-4 px-4 md:px-8">
|
||||
<div className="flex items-center gap-4 lg:gap-8">
|
||||
<Link
|
||||
href="/"
|
||||
@@ -50,46 +50,48 @@ export function SiteHeader() {
|
||||
aria-label="Supabase Library home"
|
||||
>
|
||||
<Image src={supabaseLogoIcon} alt="" width={24} height={24} />
|
||||
<span aria-hidden="true" className="hidden text-foreground-lighter sm:inline">
|
||||
<span aria-hidden="true" className="hidden text-border sm:inline">
|
||||
/
|
||||
</span>
|
||||
<span className="hidden text-foreground-light sm:inline">Library</span>
|
||||
</Link>
|
||||
<nav aria-label="Main navigation">
|
||||
<NavigationMenu
|
||||
delayDuration={0}
|
||||
className="hidden h-14 md:flex md:flex-none"
|
||||
viewportClassName="rounded-xl bg-background data-[state=open]:slide-in-from-right-0!"
|
||||
>
|
||||
<NavigationMenuList className="flex-none gap-1">
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'text', size: 'small' }),
|
||||
'bg-transparent! hover:text-brand-link data-open:text-brand-link! focus-ring focus-visible:text-foreground px-2 h-auto',
|
||||
pathname === '/' && 'text-foreground'
|
||||
)}
|
||||
</div>
|
||||
<nav aria-label="Main navigation" className="absolute left-1/2 -translate-x-1/2">
|
||||
<NavigationMenu
|
||||
delayDuration={0}
|
||||
className="hidden h-14 md:flex md:flex-none [&>div:last-child]:left-1/2 [&>div:last-child]:-translate-x-1/2"
|
||||
viewportClassName="rounded-xl bg-background data-[state=open]:slide-in-from-right-0!"
|
||||
>
|
||||
<NavigationMenuList className="flex-none gap-1">
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'text', size: 'small' }),
|
||||
'h-8 rounded-full px-3 text-foreground-light hover:text-foreground data-[state=open]:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Grid2X2 aria-hidden="true" className="size-4 text-muted-foreground" />
|
||||
<span className="font-medium text-foreground">Explore</span>
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<Tabs
|
||||
value={activeCategory}
|
||||
onValueChange={menuAim.selectCategory}
|
||||
onPointerMove={menuAim.onPointerMove}
|
||||
onPointerLeave={menuAim.onPointerLeave}
|
||||
orientation="vertical"
|
||||
className="flex w-[640px] max-w-[calc(100vw-4rem)] max-h-[calc(100dvh-5rem)] overflow-y-auto"
|
||||
>
|
||||
Explore
|
||||
</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<Tabs
|
||||
value={activeCategory}
|
||||
onValueChange={setActiveCategory}
|
||||
orientation="vertical"
|
||||
className="flex w-[640px] max-w-[calc(100vw-4rem)] max-h-[calc(100dvh-5rem)] overflow-y-auto"
|
||||
>
|
||||
<div className="flex w-60 shrink-0 flex-col border-r bg-surface-75 py-2">
|
||||
<TabsList
|
||||
aria-label="Block categories"
|
||||
className="h-auto w-60 shrink-0 flex-col items-stretch justify-start border-b-0 border-r bg-surface-75 py-2"
|
||||
className="h-auto flex-col items-stretch justify-start border-b-0"
|
||||
>
|
||||
{libraryCategories.map((category) => (
|
||||
<TabsTrigger
|
||||
key={category.name}
|
||||
value={category.name}
|
||||
onPointerEnter={(event) => {
|
||||
if (event.pointerType === 'mouse') setActiveCategory(category.name)
|
||||
}}
|
||||
data-menu-category={category.name}
|
||||
className="justify-between gap-3 whitespace-normal border-0 px-4 py-2.5 text-left data-[state=active]:bg-surface-200 data-[state=active]:shadow-none"
|
||||
>
|
||||
{category.name}
|
||||
@@ -99,96 +101,76 @@ export function SiteHeader() {
|
||||
/>
|
||||
</TabsTrigger>
|
||||
))}
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href="/"
|
||||
className="mx-4 mt-5 border-t pt-4 text-xs text-foreground-light hover:text-foreground"
|
||||
>
|
||||
Browse all blocks
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href="/docs/getting-started/introduction"
|
||||
className="px-4 py-3 text-xs text-foreground-light hover:text-foreground"
|
||||
>
|
||||
Introduction
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</TabsList>
|
||||
{libraryCategories.map((category) => {
|
||||
const blocks = libraryBlocks.filter(
|
||||
(block) => block.category === category.name
|
||||
)
|
||||
return (
|
||||
<TabsContent
|
||||
key={category.name}
|
||||
value={category.name}
|
||||
className="m-0 min-w-0 flex-1 p-6"
|
||||
>
|
||||
{blocks.length > 0 ? (
|
||||
<ul className="flex flex-col gap-4">
|
||||
{blocks.map((block) => (
|
||||
<li key={block.slug}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={getLibraryBlockHref(block, framework)}
|
||||
className="group/menu-item flex h-fit flex-col gap-1 rounded-md text-sm text-foreground-light hover:text-foreground select-none leading-none no-underline focus-ring focus-visible:text-foreground"
|
||||
>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-sm leading-snug text-foreground">
|
||||
{block.title}
|
||||
</span>
|
||||
<ChevronRight
|
||||
strokeWidth={2}
|
||||
className="h-3 w-3 text-foreground transition-all -translate-x-1 opacity-0 group-hover/menu-item:translate-x-0 group-hover/menu-item:opacity-100"
|
||||
/>
|
||||
<ul className="mx-4 mt-5 flex flex-col border-t pt-2.5">
|
||||
{[
|
||||
{ href: '/', label: 'Browse all blocks' },
|
||||
{ href: '/docs/getting-started/quickstart', label: 'Quickstart' },
|
||||
{ href: '/docs/getting-started/faq', label: 'FAQ' },
|
||||
].map(({ href, label }) => (
|
||||
<li key={href}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={href}
|
||||
className="block py-1.5 text-xs text-foreground-light hover:text-foreground"
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{libraryCategories.map((category) => {
|
||||
const blocks = libraryBlocks.filter(
|
||||
(block) => block.category === category.name
|
||||
)
|
||||
return (
|
||||
<TabsContent
|
||||
key={category.name}
|
||||
value={category.name}
|
||||
className="m-0 min-w-0 flex-1 p-6"
|
||||
>
|
||||
{blocks.length > 0 ? (
|
||||
<ul className="flex flex-col gap-4">
|
||||
{blocks.map((block) => (
|
||||
<li key={block.slug}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={getLibraryBlockHref(block, framework)}
|
||||
className="group/menu-item flex h-fit flex-col gap-1 rounded-md text-sm text-foreground-light hover:text-foreground select-none leading-none no-underline focus-ring focus-visible:text-foreground"
|
||||
>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-sm leading-snug text-foreground">
|
||||
{block.title}
|
||||
</span>
|
||||
<span className="text-xs leading-snug text-foreground-lighter group-hover/menu-item:text-foreground-light group-focus-visible/menu-item:text-foreground-light">
|
||||
{block.description}
|
||||
</span>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-base text-foreground-light">
|
||||
No blocks in this category yet.
|
||||
</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
)
|
||||
})}
|
||||
</Tabs>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'h-9 bg-transparent px-3 text-sm text-foreground-light hover:bg-transparent hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Link href="/docs/getting-started/quickstart">Quickstart</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
'h-9 bg-transparent px-3 text-sm text-foreground-light hover:bg-transparent hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Link href="/docs/getting-started/faq">FAQ</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</nav>
|
||||
</div>
|
||||
<ChevronRight
|
||||
strokeWidth={2}
|
||||
className="h-3 w-3 text-foreground transition-all -translate-x-1 opacity-0 group-hover/menu-item:translate-x-0 group-hover/menu-item:opacity-100"
|
||||
/>
|
||||
</span>
|
||||
<span className="text-xs leading-snug text-foreground-lighter group-hover/menu-item:text-foreground-light group-focus-visible/menu-item:text-foreground-light">
|
||||
{block.description}
|
||||
</span>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-base text-foreground-light">
|
||||
No blocks in this category yet.
|
||||
</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
)
|
||||
})}
|
||||
</Tabs>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</nav>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-20 sm:w-28 lg:w-36 xl:w-44">
|
||||
<CommandMenu />
|
||||
@@ -197,7 +179,7 @@ export function SiteHeader() {
|
||||
asChild
|
||||
variant="text"
|
||||
size="tiny"
|
||||
className="hidden px-2 sm:inline-flex"
|
||||
className="hidden h-8 w-8 p-0 sm:inline-flex"
|
||||
aria-label="View source on GitHub"
|
||||
>
|
||||
<a href="https://github.com/supabase/supabase/tree/master/apps/ui-library">
|
||||
@@ -208,7 +190,7 @@ export function SiteHeader() {
|
||||
asChild
|
||||
variant="text"
|
||||
size="small"
|
||||
className="hidden px-3 text-sm sm:inline-flex"
|
||||
className="hidden h-8 px-3 text-sm sm:inline-flex"
|
||||
iconRight={<ArrowUpRight size={14} />}
|
||||
>
|
||||
<a href="https://supabase.com/docs">Docs</a>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const CodeBlock = dynamic(() => import('ui-patterns/CodeBlock').then((module) => module.CodeBlock))
|
||||
|
||||
export function TerminalCode({ command }: { command: string }) {
|
||||
return (
|
||||
<CodeBlock
|
||||
language="bash"
|
||||
hideCopy
|
||||
hideLineNumbers
|
||||
wrapLongLines
|
||||
focusable={false}
|
||||
wrapperClassName="min-w-0 max-w-none flex-1"
|
||||
className="w-full max-w-none rounded-none border-0 bg-transparent! p-0 font-mono text-xs! leading-6! text-foreground!"
|
||||
>
|
||||
{command}
|
||||
</CodeBlock>
|
||||
)
|
||||
}
|
||||
@@ -58,7 +58,7 @@ const ThemeSwitcherDropdown = () => {
|
||||
variant="text"
|
||||
size="tiny"
|
||||
aria-label="Choose a theme"
|
||||
className="px-1 group"
|
||||
className="group h-8 w-8 p-0"
|
||||
icon={
|
||||
resolvedTheme?.includes('light') ? (
|
||||
<Sun className={iconClasses} />
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, type PointerEvent } from 'react'
|
||||
|
||||
// Keep the selected category while the pointer travels toward its content panel.
|
||||
export function useMenuAim(activeCategory: string, onCategoryChange: (category: string) => void) {
|
||||
const previousPoint = useRef<{ x: number; y: number } | null>(null)
|
||||
const timeout = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
function cancelPending() {
|
||||
if (timeout.current !== null) clearTimeout(timeout.current)
|
||||
timeout.current = null
|
||||
}
|
||||
|
||||
function reset() {
|
||||
cancelPending()
|
||||
previousPoint.current = null
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeout.current !== null) clearTimeout(timeout.current)
|
||||
}
|
||||
}, [])
|
||||
|
||||
function onPointerMove(event: PointerEvent<HTMLDivElement>) {
|
||||
if (event.pointerType !== 'mouse') return
|
||||
|
||||
const previous = previousPoint.current
|
||||
const point = { x: event.clientX, y: event.clientY }
|
||||
previousPoint.current = point
|
||||
const target = event.target instanceof Element ? event.target : null
|
||||
const tab = target?.closest<HTMLElement>('[data-menu-category]')
|
||||
const category = tab?.dataset.menuCategory
|
||||
cancelPending()
|
||||
if (!category || category === activeCategory) return
|
||||
|
||||
const list = tab?.closest('[role="tablist"]')?.getBoundingClientRect()
|
||||
const panel = event.currentTarget.getBoundingClientRect()
|
||||
if (previous && list && previous.x < point.x && point.x < list.right) {
|
||||
const progress = (point.x - previous.x) / (list.right - previous.x)
|
||||
const top = previous.y + (panel.top - previous.y) * progress
|
||||
const bottom = previous.y + (panel.bottom - previous.y) * progress
|
||||
if (point.y >= top && point.y <= bottom) {
|
||||
timeout.current = setTimeout(() => onCategoryChange(category), 300)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onCategoryChange(category)
|
||||
}
|
||||
|
||||
function selectCategory(category: string) {
|
||||
reset()
|
||||
onCategoryChange(category)
|
||||
}
|
||||
|
||||
return { onPointerMove, onPointerLeave: reset, selectCategory }
|
||||
}
|
||||
Reference in new issue
Block a user