Files
supabase/apps/docs/layouts/MainSkeleton.tsx
Charis a29dc5de4b feat(docs): main troubleshooting page (#29120)
* feat(docs): main troubleshooting page

* feat(docs): global troubleshooting page layout

* feat(docs): global troubleshooting page filter logic

* enhance(docs): troubleshooting page ui & filters

Improve UX and accessibility of global troubleshooting search page.

- Implement TroubleshootingFilterStateProvider for managing filter state
- Add TroubleshootingFilterEmptyState component
- Improve TroubleshootingFilter UI with applied filters display
- Update TroubleshootingPreview component with better layout and accessibility
- Add TroubleshootingEntryAssociatedErrors component for displaying related errors
- Refactor Troubleshooting utils and schemas
- Update troubleshooting content template

* feat(docs): individual troubleshooting pages

Add individual pages for detailed information on troubleshooting
entries. Small fixes to UI for troubleshooting displays.

- Wrap GlobalTroubleshootingPage with SidebarSkeleton
- Add TroubleshootingErrorListDetailed component for detailed error display
- Implement TroubleshootingBackLink component for navigation
- Update TroubleshootingPreview to use Next.js Link and include parent page
- Refactor Footer component to accept className prop
- Add hideFooter option to SidebarSkeleton
- Minor updates to TopNavBar and other utility functions

* fix: minor styling issues

* tweak(troubleshooting docs): use breadcrumbs

* tweak(troubleshooting docs): use sidebar for filter

* enhance(troubleshooting docs): navigation, keywords

Better navigation for individual troubleshooting entries, using a
version of the sidebar + keywords handling via search params for good
linking behavior

Details:

- Add TroubleshootingSidebar to TroubleshootingPage
- Implement query state management for keywords using nuqs
- Improve accessibility and styling of troubleshooting components
- Update empty state handling and related keywords display
- Upgrade nuqs package to version 1.19.1

* fix(docs): wrap useSearchParams in Suspense

* seo(docs): add canonical link to troubleshooting

* enhance(docs,troubleshooting): auto-reveal errors

On troubleshooting search pages, if the searched term matches a hidden
error, auto-reveal it when searching. When the search term is cleared,
hide programmatically-opened errors while preserving user-opened ones.

* enhance(docs,troubleshooting): search bar

Add search bar to individual troubleshooting pages

* fix(docs): breadcrumb links

Fix breadcrumb linking so it uses next/link instead of reloading entire
app on navigation.

* fix(docs): typo

* enh(docs): new troubleshooting design

* enh(docs): use new multi-select

* enh(docs): troubleshooting responsive styles

* Update apps/docs/content/troubleshooting/monitor-supavisor-postgres-connections.mdx
2024-10-02 17:10:12 +00:00

397 lines
9.7 KiB
TypeScript

'use client'
import dynamic from 'next/dynamic'
import { memo, useEffect, type PropsWithChildren, type ReactNode } from 'react'
import { cn } from 'ui'
import DefaultNavigationMenu, {
MenuId,
} from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TopNavBar from '~/components/Navigation/NavigationMenu/TopNavBar'
import { DOCS_CONTENT_CONTAINER_ID } from '~/features/ui/helpers.constants'
import { menuState, useMenuMobileOpen } from '~/hooks/useMenuState'
const Footer = dynamic(() => import('~/components/Navigation/Footer'))
const levelsData = {
home: {
icon: 'home',
name: 'Home',
},
gettingstarted: {
icon: 'getting-started',
name: 'Getting Started',
},
database: {
icon: 'database',
name: 'Database',
},
api: {
icon: 'rest',
name: 'REST API',
},
graphql: {
icon: 'graphql',
name: 'GraphQL',
},
auth: {
icon: 'auth',
name: 'Auth',
},
functions: {
icon: 'edge-functions',
name: 'Edge Functions',
},
realtime: {
icon: 'realtime',
name: 'Realtime',
},
analytics: {
icon: 'analytics',
name: 'Analytics',
},
storage: {
icon: 'storage',
name: 'Storage',
},
ai: {
icon: 'ai',
name: 'AI & Vectors',
},
supabase_cli: {
icon: 'reference-cli',
name: 'Supabase CLI',
},
platform: {
icon: 'platform',
name: 'Platform',
},
resources: {
icon: 'resources',
name: 'Resources',
},
self_hosting: {
icon: 'self-hosting',
name: 'Self-Hosting',
},
integrations: {
icon: 'integrations',
name: 'Integrations',
},
reference_javascript_v1: {
icon: 'reference-javascript',
name: 'Javascript Reference v1.0',
},
reference_javascript_v2: {
icon: 'reference-javascript',
name: 'Javascript Reference v2.0',
},
reference_dart_v1: {
icon: 'reference-dart',
name: 'Dart Reference v1.0',
},
reference_dart_v2: {
icon: 'reference-dart',
name: 'Dart Reference v2.0',
},
reference_csharp_v0: {
icon: 'reference-csharp',
name: 'C# Reference v0.0',
},
reference_csharp_v1: {
icon: 'reference-csharp',
name: 'C# Reference v1.0',
},
reference_python_v2: {
icon: 'reference-python',
name: 'Python Reference v2.0',
},
reference_swift_v1: {
icon: 'reference-swift',
name: 'Swift Reference v1.0',
},
reference_swift_v2: {
icon: 'reference-swift',
name: 'Swift Reference v2.0',
},
reference_kotlin_v1: {
icon: 'reference-kotlin',
name: 'Kotlin Reference v1.0',
},
reference_kotlin_v2: {
icon: 'reference-kotlin',
name: 'Kotlin Reference v2.0',
},
reference_kotlin_v3: {
icon: 'reference-kotlin',
name: 'Kotlin Reference v3.0',
},
reference_cli: {
icon: 'reference-cli',
name: 'CLI Reference',
},
reference_api: {
icon: 'reference-api',
name: 'Management API Reference',
},
reference_self_hosting_auth: {
icon: 'reference-auth',
name: 'Auth Server Reference',
},
reference_self_hosting_storage: {
icon: 'reference-storage',
name: 'Storage Server Reference',
},
reference_self_hosting_realtime: {
icon: 'reference-realtime',
name: 'Realtime Server Reference',
},
reference_self_hosting_analytics: {
icon: 'reference-analytics',
name: 'Analytics Server Reference',
},
reference_self_hosting_functions: {
icon: 'reference-functions',
name: 'Functions Server Reference',
},
}
type MobileHeaderProps = { menuId: MenuId } | { menuName: string }
const MobileHeader = memo(function MobileHeader(props: MobileHeaderProps) {
const mobileMenuOpen = useMenuMobileOpen()
return (
<div
className={cn(
'lg:hidden px-3.5 border-b z-10',
'transition-all ease-out',
'top-0',
mobileMenuOpen && 'absolute',
'flex items-center',
mobileMenuOpen ? 'gap-0' : 'gap-1'
)}
>
<button
className={cn(
'h-8 w-8 flex group items-center justify-center mr-1',
mobileMenuOpen && 'mt-0.5'
)}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
>
<div
className={cn(
'space-y-1 cursor-pointer relative',
mobileMenuOpen ? 'w-4 h-4' : 'w-4 h-[8px]'
)}
>
<span
className={cn(
'transition-all ease-out block w-4 h-px bg-foreground-muted group-hover:bg-foreground',
!mobileMenuOpen ? 'w-4' : 'absolute rotate-45 top-[6px]'
)}
/>
<span
className={cn(
'transition-all ease-out block h-px bg-foreground-muted group-hover:bg-foreground',
!mobileMenuOpen ? 'w-3 group-hover:w-4' : 'absolute w-4 -rotate-45 top-[2px]'
)}
/>
</div>
</button>
<span
className={cn(
'transition-all duration-200',
'text-foreground',
mobileMenuOpen ? 'text-xs' : 'text-sm'
)}
>
{mobileMenuOpen
? 'Close'
: 'menuId' in props
? levelsData[props.menuId]?.name ?? levelsData['home'].name
: props.menuName}
</span>
</div>
)
})
const MobileMenuBackdrop = memo(function MobileMenuBackdrop() {
const mobileMenuOpen = useMenuMobileOpen()
useEffect(() => {
window.addEventListener('resize', (e: UIEvent) => {
const w = e.target as Window
if (mobileMenuOpen && w.innerWidth >= 1024) {
menuState.setMenuMobileOpen(!mobileMenuOpen)
}
})
return () => {
window.removeEventListener('resize', () => {})
}
}, [mobileMenuOpen])
return (
<div
className={cn(
'h-full',
'left-0',
'right-0',
'z-10',
'backdrop-blur-sm backdrop-filter bg-alternative/90',
mobileMenuOpen ? 'absolute h-full w-full top-0 left-0' : 'hidden h-0',
// always hide on desktop
'lg:hidden'
)}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
></div>
)
})
const Container = memo(function Container({
children,
className,
}: PropsWithChildren<{ className?: string }>) {
return (
<main
// used by layout to scroll to top
id={DOCS_CONTENT_CONTAINER_ID}
className={cn(
'w-full transition-all ease-out relative',
// desktop override any margin styles
'lg:ml-0',
className
)}
>
<div className="flex flex-col sticky top-0">{children}</div>
</main>
)
})
const NavContainer = memo(function NavContainer({ children }: PropsWithChildren) {
const mobileMenuOpen = useMenuMobileOpen()
return (
<nav
aria-labelledby="main-nav-title"
className={cn(
'fixed lg:relative z-40 lg:z-auto',
mobileMenuOpen ? 'w-[75%] sm:w-[50%] md:w-[33%] left-0' : 'w-0 -left-full',
'lg:w-[420px] !lg:left-0',
'lg:top-[var(--header-height)] lg:sticky',
'h-screen lg:h-[calc(100vh-var(--header-height))]',
// desktop override any left styles
'lg:left-0',
'transition-all',
'top-0 bottom-0',
'flex flex-col ml-0',
'border-r',
'lg:overflow-y-auto'
)}
>
<div
className={cn(
'top-0 lg:top-[var(--header-height)]',
'h-full',
'relative lg:sticky',
'w-full lg:w-auto',
'h-fit lg:h-screen overflow-y-scroll lg:overflow-auto',
'backdrop-blur backdrop-filter bg-background',
'flex flex-col flex-grow'
)}
>
<span id="main-nav-title" className="sr-only">
Main menu
</span>
<div className="top-0 sticky h-0 z-10">
<div className="bg-gradient-to-b from-background to-transparent h-4 w-full"></div>
</div>
<div
className={cn(
'transition-all ease-out duration-200',
'absolute left-0 right-0',
'px-5 pl-5 pt-6 pb-16 lg:pb-32',
'bg-background',
// desktop styles
'lg:relative lg:left-0 lg:pb-10 lg:px-10 lg:flex',
'lg:opacity-100 lg:visible'
)}
>
{children}
</div>
</div>
</nav>
)
})
interface SkeletonProps extends PropsWithChildren {
menuId?: MenuId
menuName?: string
NavigationMenu?: ReactNode
hideFooter?: boolean
className?: string
}
function TopNavSkeleton({ children }) {
return (
<div className="flex flex-col h-full w-full">
<div className="hidden lg:sticky w-full lg:flex top-0 left-0 right-0 z-50">
<TopNavBar />
</div>
{children}
</div>
)
}
function SidebarSkeleton({
children,
menuId,
menuName,
NavigationMenu,
hideFooter = false,
className,
}: SkeletonProps) {
const mobileMenuOpen = useMenuMobileOpen()
const hideSideNav = !menuId && !NavigationMenu
return (
<div className={cn('flex flex-row h-full relative', className)}>
{!hideSideNav && (
<NavContainer>{NavigationMenu ?? <DefaultNavigationMenu menuId={menuId} />}</NavContainer>
)}
<Container>
<div
className={cn(
'flex lg:hidden w-full top-0 left-0 right-0 z-50',
hideSideNav && 'sticky',
mobileMenuOpen && 'z-10'
)}
>
<TopNavBar />
</div>
<div
className={cn(
'sticky',
'transition-all top-0 z-10',
'backdrop-blur backdrop-filter bg-background'
)}
>
{hideSideNav ? null : menuName ? (
<MobileHeader menuName={menuName} />
) : (
<MobileHeader menuId={menuId} />
)}
</div>
<div className="grow">
{children}
{!hideFooter && <Footer />}
</div>
<MobileMenuBackdrop />
</Container>
</div>
)
}
export { TopNavSkeleton, SidebarSkeleton }