feat(docs): add visual contributing guide (#28214)

Add a contributing guide to docs, which links to resources for contributors
and includes examples of usable components when writing docs content.
This commit is contained in:
Charis authored and GitHub committed 2024-07-26 15:54:20 -04:00
1 parent 9d5d4626ef
commit 837e314088
25 files changed
+766 -214

No files matched your search

@@ -0,0 +1,82 @@
'use client'
import { Menu } from 'lucide-react'
import type { HTMLAttributes } from 'react'
import { useEffect, useState } from 'react'
import { useBreakpoint } from 'common'
import { cn, Popover_Shadcn_, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_ } from 'ui'
interface TocItem extends HTMLAttributes<HTMLElement> {
label: string
anchor: string
}
export function ContributingToc({ className }: { className?: string }) {
const mobileToc = useBreakpoint('xl')
const [tocItems, setTocItems] = useState<Array<TocItem>>([])
useEffect(() => {
const headings = [
...document.querySelectorAll('article.prose > h2,h3'),
] as Array<HTMLHeadingElement>
const tocItems = headings
.filter((heading) => !!heading.id)
.map((heading) => ({
label: heading.textContent.substring(0, heading.textContent.length - 1), // Remove ending `#`
anchor: heading.id,
}))
setTocItems(tocItems)
}, [])
return mobileToc ? (
<MobileToc
items={tocItems}
className={cn(
'[--local-top-spacing:2rem]',
'fixed top-[calc(var(--header-height)+var(--local-top-spacing))] right-8'
)}
/>
) : (
<TocBase
items={tocItems}
className={cn(
'[--local-top-spacing:5rem]',
'fixed top-[calc(var(--header-height)+var(--local-top-spacing))] right-8 w-36',
'xl:border-l border-foreground-muted'
)}
/>
)
}
function MobileToc({ items, className }: { items: Array<TocItem>; className?: string }) {
const [open, setOpen] = useState(false)
return (
<Popover_Shadcn_ open={open} onOpenChange={setOpen}>
<PopoverTrigger_Shadcn_ className={cn('border rounded p-2', className)}>
<Menu />
<span className="sr-only">
{open ? 'Close table of contents' : 'Open table of contents'}
</span>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_ align="end" className="w-48">
<TocBase items={items} />
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
)
}
function TocBase({ items, className }: { items: Array<TocItem>; className?: string }) {
return (
<nav aria-label="Table of contents" className={cn('px-4 text-foreground-light', className)}>
<ul className="flex flex-col gap-1">
{items.map((item) => (
<li key={item.anchor} className="overflow-hidden truncate">
<a href={`#${item.anchor}`}>{item.label}</a>
</li>
))}
</ul>
</nav>
)
}
+399
View File
@@ -0,0 +1,399 @@
# Contributing to Supabase Docs
Thanks for contributing to Supabase Docs! Here are a few resources to help you get started.
The code and content for our docs site are located in the main [Supabase GitHub repo](https://github.com/supabase/supabase), under the `apps/docs` directory.
In the repo, you'll also find:
- The [developers guide](https://github.com/supabase/supabase/blob/master/apps/docs/DEVELOPERS.md), which will help you set up your local machine to develop the docs site
- The [contributing guide](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md), which goes over the content organization and some general guidelines for writing docs content
## Components
Our docs content is mainly written in MDX. Aside from standard GitHub-flavored Markdown, you can use the following helper components to help you organize and display your content:
### Accordion
For content that requires progressive disclosure:
```mdx
<Accordion
type="default"
openBehaviour="multiple"
chevronAlign="right"
justified
size="medium"
className="text-foreground-light mt-8 mb-6"
>
<div className="border-b mt-3 pb-3">
<AccordionItem
header="Accordion item 1"
id="item-1"
>
Your content here.
</AccordionItem>
</div>
<div className="border-b mt-3 pb-3">
<AccordionItem
header="Accordion item 2"
id="item-2"
>
More content here.
</AccordionItem>
</div>
</Accordion>
```
<Accordion
type="default"
openBehaviour="multiple"
chevronAlign="right"
justified
size="medium"
className="text-foreground-light mt-8 mb-6"
>
<div className="border-b mt-3 pb-3">
<AccordionItem
header="Accordion item 1"
id="item-1"
>
Your content here.
</AccordionItem>
</div>
<div className="border-b mt-3 pb-3">
<AccordionItem
header="Accordion item 2"
id="item-2"
>
More content here.
</AccordionItem>
</div>
</Accordion>
### Admonition
For extra information that doesn't fit into the main flow. There are 5 supported types of admonitions:
- `danger` to warn the user about any missteps that could cause data loss or data leaks
- `deprecation` to notify the user about features that are (or will soon be) deprecated
- `caution` to warn about anything that could cause a bug or serious user inconvenience
- `tip` to point out helpful but optional actions
- `note` for anything else
Leave a blank line between the admonition tag and the contained content. This will prevent Prettier from trying to break the lines within the content.
```mdx
<Admonition type="danger">
This could lead to data loss!
</Admonition>
<Admonition type="deprecation">
This feature is deprecated.
</Admonition>
<Admonition type="caution">
You should make sure you don't set this up wrong.
</Admonition>
<Admonition type="tip">
In certain cases, you may want to do this.
</Admonition>
<Admonition type="note">
Additional helpful information.
</Admonition>
```
<Admonition type="danger">
This could lead to data loss!
</Admonition>
<Admonition type="deprecation">
This feature is deprecated.
</Admonition>
<Admonition type="caution">
You should make sure you don't set this up wrong.
</Admonition>
<Admonition type="tip">
In certain cases, you may want to do this.
</Admonition>
<Admonition type="note">
Additional helpful information.
</Admonition>
### Icons
The following icons are available. They can be styled with [Tailwind](https://tailwindcss.com/) classes:
```mdx
<IconArrowDown />
<IconCheck />
<IconX />
```
<div class="flex items-center gap-2">
<IconArrowDown />
<IconCheck />
<IconX />
</div>
### Image
You can include images with regular Markdown syntax:
```mdx
![Supabase architectural diagram](/docs/img/supabase-architecture.svg)
```
![Supabase architectural diagram](/docs/img/supabase-architecture.svg)
If your image has alternate light and dark versions, or you want to make it zoomable, you can also use the image component:
```mdx
<Image
alt="Supabase architectural diagram"
src={{
dark: '/docs/img/supabase-architecture.svg',
light: '/docs/img/supabase-architecture--light.svg',
}}
zoomable
/>
```
<Image
alt="Supabase architectural diagram"
src={{
dark: '/docs/img/supabase-architecture.svg',
light: '/docs/img/supabase-architecture--light.svg',
}}
zoomable
/>
### Project Variables
Some guides and tutorials will require that users copy their Supabase project URL and anon key. You can provide those inline if the user is signed in:
```mdx
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
```
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
### Step Hike
For tutorials, which feature step-by-step instructions, often with accompanying code, we use the `StepHike` pattern:
````mdx
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="The first step">
Explanation of what to do first.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql
select ...
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="No code in this step" fullWidth>
Explanation of what to do next. This stretches the full width of the section: Sweet tiramisu apple biscuit candy cake. Orange ipsum muffin cookie cake biscuit. Orange muffin vanilla sweet sugar candy. Sprinkles jelly sweet orange candy cream.
</StepHikeCompact.Details>
</StepHikeCompact.Step>
</StepHikeCompact>
````
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="The first step">
Explanation of what to do first.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql
select ...
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="No code in this step" fullWidth>
Explanation of what to do next. This stretches the full width of the section: Sweet tiramisu apple biscuit candy cake. Orange ipsum muffin cookie cake biscuit. Orange muffin vanilla sweet sugar candy. Sprinkles jelly sweet orange candy cream.
</StepHikeCompact.Details>
</StepHikeCompact.Step>
</StepHikeCompact>
### Tabs
Use tabs when users can select between multiple versions of the content. For example, the content might differ based on language or package manager.
If you include the `queryGroup` prop, the user's selection will sync with other tab groups. Leave out this prop to omit this behavior.
````mdx
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
const supabase = createSupabaseClient()
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() async {
Supabase.initialize();
}
```
</TabPanel>
</Tabs>
````
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
const supabase = createSupabaseClient()
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() async {
Supabase.initialize();
}
```
</TabPanel>
</Tabs>
## Partials
We incorporate content reuse in the docs to avoid duplication. If you find yourself writing the same content over and over, you can put it in a partial instead. Here are some examples of commonly used partials:
<Accordion
type="default"
openBehaviour="multiple"
chevronAlign="right"
justified
size="medium"
className="text-foreground-light mt-8 mb-6"
>
<div className="border-b mt-3 pb-3">
<AccordionItem
header="Database setup"
id="database-setup"
>
```mdx
<DatabaseSetup />
```
<DatabaseSetup />
</AccordionItem>
</div>
<div className="border-b mt-3 pb-3">
<AccordionItem
header="Create client for Auth"
id="create-client-auth"
>
```mdx
<CreateClientSnippet />
```
<CreateClientSnippet />
</AccordionItem>
</div>
</Accordion>
To make a new partial:
1. Make a new MDX file in `apps/docs/components/MDX`.
1. Write your reusable content.
1. Inside `apps/docs/components/MDX/partials.tsx`, import and re-export your partial.
1. Inside `apps/docs/features/docs/mdx.shared.tsx`, import your partial and include it in the `components` object.
1. You can now use your partial inside any other MDX file by using: `<YourPartialName />`.
+26
View File
@@ -0,0 +1,26 @@
import { readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { ContributingToc } from '~/app/contributing/ContributingToC'
import { MDXProviderGuides } from '~/features/docs/GuidesMdx.client'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
export default async function ContributingPage() {
const contentFile = join(dirname(fileURLToPath(import.meta.url)), 'content.mdx')
const content = await readFile(contentFile, 'utf-8')
return (
<SidebarSkeleton>
<div className="px-8 py-16">
<article className="prose mx-auto">
<MDXProviderGuides>
<MDXRemoteBase source={content} />
</MDXProviderGuides>
</article>
<ContributingToc />
</div>
</SidebarSkeleton>
)
}
+9 -4
View File
@@ -4,13 +4,18 @@ import Link from 'next/link'
import { Button } from 'ui'
const ErrorPage = () => (
<div className="h-full w-full flex flex-col gap-8 p-8 items-center justify-center">
<div className="h-[calc(100vh-var(--header-height))] w-full flex flex-col gap-8 p-8 items-center justify-center">
<span className="text-center text-5xl text-foreground-lighter">
Sorry, something went wrong
</span>
<Button asChild type="secondary" className="w-fit p-4 text-lg">
<Link href="/">Return to homepage</Link>
</Button>
<div className="flex flex-row items-center gap-4">
<Button asChild type="secondary" className="w-fit p-4 text-lg">
<Link href="/">Return to homepage</Link>
</Button>
<Button type="secondary" className="w-fit p-4 text-lg" onClick={() => location.reload()}>
Refresh page
</Button>
</div>
</div>
)
@@ -6,7 +6,8 @@ import rehypeSlug from 'rehype-slug'
import { Heading } from 'ui'
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate, MDXRemoteGuides, newEditLink } from '~/features/docs/GuidesMdx.template'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
import { Tabs, TabPanel } from '~/features/ui/Tabs'
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
@@ -54,7 +55,7 @@ const DatabaseAdvisorDocs = async () => {
return (
<GuideTemplate meta={meta} editLink={editLink}>
<MDXRemoteGuides source={markdownIntro} />
<MDXRemoteBase source={markdownIntro} />
<Heading tag="h2">Available checks</Heading>
<Tabs listClassNames="flex flex-wrap gap-2 [&>button]:!m-0" queryGroup="lint">
{lints.map((lint) => (
@@ -64,7 +65,7 @@ const DatabaseAdvisorDocs = async () => {
label={capitalize(getBasename(lint.path).replace(/_/g, ' '))}
>
<section id={getBasename(lint.path)}>
<MDXRemoteGuides source={lint.content} options={options} />
<MDXRemoteBase source={lint.content} options={options} />
</section>
</TabPanel>
))}
+2 -36
View File
@@ -2,7 +2,8 @@
import { usePathname } from 'next/navigation'
import { type PropsWithChildren } from 'react'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import { getMenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu.utils'
import Layout from '~/layouts/guides'
const GuidesLayout = ({ children }: PropsWithChildren) => {
@@ -12,39 +13,4 @@ const GuidesLayout = ({ children }: PropsWithChildren) => {
return <Layout menuId={menuId}>{children}</Layout>
}
export const getMenuId = (pathname: string | null) => {
pathname = (pathname ??= '').replace(/^\/guides\//, '')
switch (true) {
case pathname.startsWith('ai'):
return MenuId.Ai
case pathname.startsWith('api'):
return MenuId.Api
case pathname.startsWith('auth'):
return MenuId.Auth
case pathname.startsWith('cli'):
return MenuId.Cli
case pathname.startsWith('database'):
return MenuId.Database
case pathname.startsWith('functions'):
return MenuId.Functions
case pathname.startsWith('getting-started'):
return MenuId.GettingStarted
case pathname.startsWith('graphql'):
return MenuId.Graphql
case pathname.startsWith('platform'):
return MenuId.Platform
case pathname.startsWith('realtime'):
return MenuId.Realtime
case pathname.startsWith('resources'):
return MenuId.Resources
case pathname.startsWith('self-hosting'):
return MenuId.SelfHosting
case pathname.startsWith('storage'):
return MenuId.Storage
default:
return MenuId.GettingStarted
}
}
export default GuidesLayout
@@ -1,6 +1,7 @@
import Param from '~/components/Params'
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate, MDXRemoteGuides, newEditLink } from '~/features/docs/GuidesMdx.template'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import specAnalyticsV0 from '~/spec/analytics_v0_config.yaml' assert { type: 'yml' }
const meta = {
@@ -23,7 +24,7 @@ const AnalyticsConfigPage = async () => {
'supabase/supabase/blob/master/apps/docs/pages/guides/self-hosting/analytics/config.tsx'
)}
>
<MDXRemoteGuides source={descriptionMdx} />
<MDXRemoteBase source={descriptionMdx} />
<div>
{specAnalyticsV0.info.tags.map(
@@ -1,6 +1,7 @@
import Param from '~/components/Params'
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate, MDXRemoteGuides, newEditLink } from '~/features/docs/GuidesMdx.template'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import specAuthV1 from '~/spec/gotrue_v1_config.yaml' assert { type: 'yml' }
const meta = {
@@ -23,7 +24,7 @@ const AuthConfigPage = async () => {
'supabase/supabase/blob/master/apps/docs/pages/guides/self-hosting/auth/config.tsx'
)}
>
<MDXRemoteGuides source={descriptionMdx} />
<MDXRemoteBase source={descriptionMdx} />
<div>
{specAuthV1.info.tags.map((tag: ReturnType<typeof specAuthV1>['info']['tags']) => {
@@ -1,6 +1,7 @@
import Param from '~/components/Params'
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate, MDXRemoteGuides, newEditLink } from '~/features/docs/GuidesMdx.template'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import specRealtimeV0 from '~/spec/realtime_v0_config.yaml' assert { type: 'yml' }
const meta = {
@@ -23,7 +24,7 @@ const RealtimeConfigPage = async () => {
'supabase/supabase/blob/master/apps/docs/pages/guides/self-hosting/realtime/config.tsx'
)}
>
<MDXRemoteGuides source={descriptionMdx} />
<MDXRemoteBase source={descriptionMdx} />
<div>
{specRealtimeV0.info.tags.map((tag: ReturnType<typeof specRealtimeV0>['info']['tags']) => {
@@ -1,6 +1,7 @@
import Param from '~/components/Params'
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
import { GuideTemplate, MDXRemoteGuides, newEditLink } from '~/features/docs/GuidesMdx.template'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import specStorageV0 from '~/spec/storage_v0_config.yaml' assert { type: 'yml' }
const meta = {
@@ -23,7 +24,7 @@ const StorageConfigPage = async () => {
'supabase/supabase/blob/master/apps/docs/pages/guides/self-hosting/storage/config.tsx'
)}
>
<MDXRemoteGuides source={descriptionMdx} />
<MDXRemoteBase source={descriptionMdx} />
<div>
{specStorageV0.info.tags.map((tag: ReturnType<typeof specStorageV0>['info']['tags']) => {
+4 -1
View File
@@ -9,6 +9,7 @@ import { type Metadata, type Viewport } from 'next'
import { BASE_PATH } from '~/lib/constants'
import { GlobalProviders } from '~/features/app.providers'
import { TopNavSkeleton } from '~/layouts/MainSkeleton'
const metadata: Metadata = {
applicationName: 'Supabase Docs',
@@ -45,7 +46,9 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => {
return (
<html lang="en">
<body>
<GlobalProviders>{children}</GlobalProviders>
<GlobalProviders>
<TopNavSkeleton>{children}</TopNavSkeleton>
</GlobalProviders>
</body>
</html>
)
+3 -3
View File
@@ -1,7 +1,7 @@
import { type Metadata } from 'next'
import { type PropsWithChildren } from 'react'
import { LayoutMainContent } from '~/layouts/DefaultLayout'
import { MainSkeleton } from '~/layouts/MainSkeleton'
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
const metadata: Metadata = {
title: 'Not found',
@@ -11,9 +11,9 @@ const metadata: Metadata = {
}
const NotFoundLayout = ({ children }: PropsWithChildren) => (
<MainSkeleton>
<SidebarSkeleton>
<LayoutMainContent>{children}</LayoutMainContent>
</MainSkeleton>
</SidebarSkeleton>
)
export default NotFoundLayout
+1 -1
View File
@@ -23,7 +23,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from 'ui'
import { getMenuId } from '../app/guides/layout'
import { getMenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu.utils'
import { useBreakpoint } from 'common'
import * as NavItems from './Navigation/NavigationMenu/NavigationMenu.constants'
@@ -28,7 +28,6 @@ import {
IconMenuDevCli,
IconGitHub,
IconSupport,
IconTerraform,
IconTroubleshooting,
IconBranching,
} from './MenuIcons'
@@ -89,6 +88,8 @@ function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, c
return <IconGitHub width={width} height={height} className={className} />
case 'support':
return <IconSupport width={width} height={height} className={className} />
case 'contributing':
return <IconTroubleshooting width={width} height={height} className={className} />
default:
return <IconMenuPlatform width={width} height={height} className={className} />
}
@@ -1,3 +1,4 @@
import { IS_DEV } from '~/lib/constants'
import type { GlobalMenuItems, NavMenuConstant, References } from '../Navigation.types'
export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
@@ -191,6 +192,11 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
icon: 'status',
href: 'https://status.supabase.com/',
},
{
label: 'Contributing',
icon: 'contributing',
href: '/contributing' as `/${string}`,
},
],
],
},
@@ -2,6 +2,7 @@
import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import type { ICommonItem } from '~/components/reference/Reference.types'
import type { Json } from '~/types'
import { menuState } from '../../../hooks/useMenuState'
@@ -105,3 +106,38 @@ export const useCloseMenuOnRouteChange = () => {
menuState.setMenuMobileOpen(false)
}, [pathname])
}
export const getMenuId = (pathname: string | null) => {
pathname = (pathname ??= '').replace(/^\/guides\//, '')
switch (true) {
case pathname.startsWith('ai'):
return MenuId.Ai
case pathname.startsWith('api'):
return MenuId.Api
case pathname.startsWith('auth'):
return MenuId.Auth
case pathname.startsWith('cli'):
return MenuId.Cli
case pathname.startsWith('database'):
return MenuId.Database
case pathname.startsWith('functions'):
return MenuId.Functions
case pathname.startsWith('getting-started'):
return MenuId.GettingStarted
case pathname.startsWith('graphql'):
return MenuId.Graphql
case pathname.startsWith('platform'):
return MenuId.Platform
case pathname.startsWith('realtime'):
return MenuId.Realtime
case pathname.startsWith('resources'):
return MenuId.Resources
case pathname.startsWith('self-hosting'):
return MenuId.SelfHosting
case pathname.startsWith('storage'):
return MenuId.Storage
default:
return MenuId.GettingStarted
}
}
@@ -1,6 +1,6 @@
'use client'
import React from 'react'
import React, { Fragment } from 'react'
import Link from 'next/link'
import { useTheme } from 'next-themes'
import { Menu } from 'lucide-react'
@@ -72,7 +72,7 @@ const TopNavDropdown = () => {
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" className="w-64">
{menu.map((menuSection, sectionIdx) => (
<>
<Fragment key={`topnav--${sectionIdx}`}>
{sectionIdx !== 0 && <DropdownMenuSeparator key={`topnav--${sectionIdx}`} />}
{menuSection.map((sectionItem, itemIdx) => (
<Link
@@ -88,7 +88,7 @@ const TopNavDropdown = () => {
</DropdownMenuItem>
</Link>
))}
</>
</Fragment>
))}
<DropdownMenuSeparator />
<DropdownMenuGroup>
@@ -11,7 +11,7 @@ import ApiOperationSection from './ApiOperationSection'
import CliCommandSection from './CLICommandSection'
import OldVersionAlert from './OldVersionAlert'
import type { IAPISpec, ICommonSection, IRefStaticDoc, ISpec, TypeSpec } from './Reference.types'
import { MainSkeleton } from '~/layouts/MainSkeleton'
import { SidebarSkeleton, TopNavSkeleton } from '~/layouts/MainSkeleton'
import MgmtApiOperationSection from '~/components/reference/MgmtApiOperationSection'
interface RefSectionHandlerProps {
@@ -80,66 +80,68 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => {
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="canonical" href={`https://supabase.com${router.basePath}${path}`} />
</Head>
<MainSkeleton menuId={props.menuId}>
{props.isOldVersion && <OldVersionAlert sections={props.sections} />}
<RefSubLayout>
{props.sections.map((section, i) => {
const sectionType = section.type
switch (sectionType) {
case 'markdown':
const markdownData = props.pageProps.docs.find((doc) => doc.id === section.id)
<TopNavSkeleton>
<SidebarSkeleton menuId={props.menuId}>
{props.isOldVersion && <OldVersionAlert sections={props.sections} />}
<RefSubLayout>
{props.sections.map((section, i) => {
const sectionType = section.type
switch (sectionType) {
case 'markdown':
const markdownData = props.pageProps.docs.find((doc) => doc.id === section.id)
return (
<RefEducationSection
key={section.id + i}
item={section}
markdownContent={markdownData}
/>
)
case 'function':
return (
<RefFunctionSection
key={section.id + i}
funcData={section}
commonFuncData={section}
spec={props.spec}
typeSpec={props.typeSpec}
/>
)
case 'cli-command':
return (
<CliCommandSection
key={section.id + i}
funcData={section}
commonFuncData={section}
/>
)
case 'operation':
if (props.type === 'mgmt-api') {
return (
<MgmtApiOperationSection
<RefEducationSection
key={section.id + i}
item={section}
markdownContent={markdownData}
/>
)
case 'function':
return (
<RefFunctionSection
key={section.id + i}
funcData={section}
commonFuncData={section}
spec={props.spec}
typeSpec={props.typeSpec}
/>
)
} else {
case 'cli-command':
return (
<ApiOperationSection
<CliCommandSection
key={section.id + i}
funcData={section}
commonFuncData={section}
spec={props.spec}
/>
)
}
default:
throw new Error(`Unknown common section type '${sectionType}'`)
}
})}
</RefSubLayout>
</MainSkeleton>
case 'operation':
if (props.type === 'mgmt-api') {
return (
<MgmtApiOperationSection
key={section.id + i}
funcData={section}
commonFuncData={section}
spec={props.spec}
/>
)
} else {
return (
<ApiOperationSection
key={section.id + i}
funcData={section}
commonFuncData={section}
spec={props.spec}
/>
)
}
default:
throw new Error(`Unknown common section type '${sectionType}'`)
}
})}
</RefSubLayout>
</SidebarSkeleton>
</TopNavSkeleton>
</>
)
}
+1 -1
View File
@@ -7,7 +7,7 @@
import { MDXProvider } from '@mdx-js/react'
import { type PropsWithChildren } from 'react'
import { components } from '~/features/docs/mdx.shared'
import { components } from '~/features/docs/MdxBase.shared'
const MDXProviderGuides = ({ children }: PropsWithChildren) => (
<MDXProvider components={components}>{children}</MDXProvider>
+8 -57
View File
@@ -1,64 +1,15 @@
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
import { remarkCodeHike, type CodeHikeConfig } from '@code-hike/mdx'
import { ExternalLink } from 'lucide-react'
import { type SerializeOptions } from 'next-mdx-remote/dist/types'
import { MDXRemote } from 'next-mdx-remote/rsc'
import { type ComponentProps, type ReactNode } from 'react'
import remarkGfm from 'remark-gfm'
import rehypeKatex from 'rehype-katex'
import remarkMath from 'remark-math'
import { type ReactNode } from 'react'
import { cn } from 'ui'
import Breadcrumbs from '~/components/Breadcrumbs'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import { components } from '~/features/docs/mdx.shared'
import { MDXProviderGuides } from '~/features/docs/GuidesMdx.client'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import type { WithRequired } from '~/features/helpers.types'
import { type GuideFrontmatter } from '~/lib/docs'
import { MDXProviderGuides } from './GuidesMdx.client'
import Breadcrumbs from '~/components/Breadcrumbs'
const codeHikeOptions: CodeHikeConfig = {
theme: codeHikeTheme,
lineNumbers: true,
showCopyButton: true,
skipLanguages: [],
autoImport: false,
}
const mdxOptions: SerializeOptions = {
mdxOptions: {
useDynamicImport: true,
remarkPlugins: [
[remarkMath, { singleDollarTextMath: false }],
remarkGfm,
[remarkCodeHike, codeHikeOptions],
],
rehypePlugins: [rehypeKatex as any],
},
}
const MDXRemoteGuides = ({ options = {}, ...props }: ComponentProps<typeof MDXRemote>) => {
const { mdxOptions: { remarkPlugins, rehypePlugins, ...otherMdxOptions } = {}, ...otherOptions } =
options
const {
mdxOptions: {
remarkPlugins: originalRemarkPlugins,
rehypePlugins: originalRehypePlugins,
...originalMdxOptions
} = {},
} = mdxOptions
const finalOptions = {
...mdxOptions,
...otherOptions,
mdxOptions: {
...originalMdxOptions,
...otherMdxOptions,
remarkPlugins: [...(originalRemarkPlugins ?? []), ...(remarkPlugins ?? [])],
rehypePlugins: [...(originalRehypePlugins ?? []), ...(rehypePlugins ?? [])],
},
} as SerializeOptions
return <MDXRemote components={components} options={finalOptions} {...props} />
}
const EDIT_LINK_SYMBOL = Symbol('edit link')
interface EditLink {
@@ -133,7 +84,7 @@ const GuideTemplate = ({ meta, content, children, editLink, mdxOptions }: GuideT
)}
<hr className="not-prose border-t-0 border-b my-8" />
<MDXProviderGuides>
{content && <MDXRemoteGuides source={content} options={mdxOptions} />}
{content && <MDXRemoteBase source={content} options={mdxOptions} />}
</MDXProviderGuides>
{children}
<footer className="mt-16 not-prose">
@@ -177,4 +128,4 @@ const GuideTemplate = ({ meta, content, children, editLink, mdxOptions }: GuideT
)
}
export { GuideTemplate, MDXRemoteGuides, newEditLink }
export { GuideTemplate, newEditLink }
+57
View File
@@ -0,0 +1,57 @@
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
import { remarkCodeHike, type CodeHikeConfig } from '@code-hike/mdx'
import { type SerializeOptions } from 'next-mdx-remote/dist/types'
import { MDXRemote } from 'next-mdx-remote/rsc'
import { type ComponentProps } from 'react'
import remarkGfm from 'remark-gfm'
import rehypeKatex from 'rehype-katex'
import remarkMath from 'remark-math'
import { components } from '~/features/docs/MdxBase.shared'
const codeHikeOptions: CodeHikeConfig = {
theme: codeHikeTheme,
lineNumbers: true,
showCopyButton: true,
skipLanguages: [],
autoImport: false,
}
const mdxOptions: SerializeOptions = {
mdxOptions: {
useDynamicImport: true,
remarkPlugins: [
[remarkMath, { singleDollarTextMath: false }],
remarkGfm,
[remarkCodeHike, codeHikeOptions],
],
rehypePlugins: [rehypeKatex as any],
},
}
const MDXRemoteBase = ({ options = {}, ...props }: ComponentProps<typeof MDXRemote>) => {
const { mdxOptions: { remarkPlugins, rehypePlugins, ...otherMdxOptions } = {}, ...otherOptions } =
options
const {
mdxOptions: {
remarkPlugins: originalRemarkPlugins,
rehypePlugins: originalRehypePlugins,
...originalMdxOptions
} = {},
} = mdxOptions
const finalOptions = {
...mdxOptions,
...otherOptions,
mdxOptions: {
...originalMdxOptions,
...otherMdxOptions,
remarkPlugins: [...(originalRemarkPlugins ?? []), ...(remarkPlugins ?? [])],
rehypePlugins: [...(originalRehypePlugins ?? []), ...(rehypePlugins ?? [])],
},
} as SerializeOptions
return <MDXRemote components={components} options={finalOptions} {...props} />
}
export { MDXRemoteBase }
+3 -3
View File
@@ -2,11 +2,11 @@ import { type PropsWithChildren } from 'react'
import HomePageCover from '~/components/HomePageCover'
import { LayoutMainContent } from './DefaultLayout'
import { MainSkeleton } from './MainSkeleton'
import { SidebarSkeleton } from './MainSkeleton'
const HomeLayout = ({ children }: PropsWithChildren) => {
return (
<MainSkeleton>
<SidebarSkeleton>
<article>
<HomePageCover title="Supabase Documentation" />
<LayoutMainContent>
@@ -15,7 +15,7 @@ const HomeLayout = ({ children }: PropsWithChildren) => {
</div>
</LayoutMainContent>
</article>
</MainSkeleton>
</SidebarSkeleton>
)
}
+55 -40
View File
@@ -1,14 +1,17 @@
'use client'
import { type PropsWithChildren, memo, useEffect, useRef } from 'react'
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'
import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TopNavBar from '~/components/Navigation/NavigationMenu/TopNavBar'
const Footer = dynamic(() => import('~/components/Navigation/Footer'))
const NavigationMenu = dynamic(
() => import('~/components/Navigation/NavigationMenu/NavigationMenu')
@@ -264,7 +267,7 @@ const Container = memo(function Container({
)
})
const NavContainer = memo(function NavContainer({ menuId }: { menuId: MenuId }) {
const NavContainer = memo(function NavContainer({ children }: PropsWithChildren) {
const mobileMenuOpen = useMenuMobileOpen()
return (
@@ -313,53 +316,65 @@ const NavContainer = memo(function NavContainer({ menuId }: { menuId: MenuId })
'lg:opacity-100 lg:visible'
)}
>
<NavigationMenu menuId={menuId} />
{children}
</div>
</div>
</nav>
)
})
function MainSkeleton({ children, menuId }: PropsWithChildren<{ menuId?: MenuId }>) {
const ref = useRef(null)
const mobileMenuOpen = useMenuMobileOpen()
const hideSideNav = !menuId
interface SkeletonProps extends PropsWithChildren {
menuId?: MenuId
NavigationMenu?: ReactNode
}
function TopNavSkeleton({ children }) {
return (
<div className="flex flex-col h-full w-full">
<div ref={ref} className="hidden lg:sticky w-full lg:flex top-0 left-0 right-0 z-50">
<div className="hidden lg:sticky w-full lg:flex top-0 left-0 right-0 z-50">
<TopNavBar />
</div>
<div className="flex flex-row h-full relative">
{!hideSideNav && <NavContainer menuId={menuId} />}
<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 && <MobileHeader menuId={menuId} />}
</div>
<div className="grow">
{children}
<Footer />
</div>
<MobileMenuBackdrop />
</Container>
</div>
{children}
</div>
)
}
export { MainSkeleton }
function SidebarSkeleton({ children, menuId, NavigationMenu }: SkeletonProps) {
const mobileMenuOpen = useMenuMobileOpen()
const hideSideNav = !menuId
return (
<div className="flex flex-row h-full relative">
{!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 && <MobileHeader menuId={menuId} />}
</div>
<div className="grow">
{children}
<Footer />
</div>
<MobileMenuBackdrop />
</Container>
</div>
)
}
export { TopNavSkeleton, SidebarSkeleton }
+4 -6
View File
@@ -7,7 +7,7 @@ import { type FC } from 'react'
import { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import { LayoutMainContent } from '~/layouts/DefaultLayout'
import { MainSkeleton } from '~/layouts/MainSkeleton'
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
interface Props {
meta?: {
@@ -33,11 +33,9 @@ const Layout: FC<Props> = (props) => {
const menuId = props.menuId
return (
<>
<MainSkeleton menuId={menuId}>
<LayoutMainContent className="pb-0">{props.children}</LayoutMainContent>
</MainSkeleton>
</>
<SidebarSkeleton menuId={menuId}>
<LayoutMainContent className="pb-0">{props.children}</LayoutMainContent>
</SidebarSkeleton>
)
}