Files
supabase/apps/docs/features/directives/CodeSample.client.tsx
Anthony Lio 12a1fd5427 fix(docs): codetabs file tabs (#50811)
## Problem

#50734 restyled single named code blocks as file tabs but code blocks
grouped with `<$CodeTabs>` render through a separate path and kept the
old pill style

## Solution

- add a `CodeTabs` component with the same file tab styling
- sets the `<$CodeTabs>` remark directive at it instead of `Tabs`
- remove the unused `NamedCodeBlock` copy in www (nothing in www outputs
it)

| state | preview |
| -------|------|
| before | <img width="781" height="352" alt="image"
src="https://github.com/user-attachments/assets/1e6261ae-30b7-4397-b2f4-c31bad5f9839"
/> |
| after | <img width="762" height="291" alt="image"
src="https://github.com/user-attachments/assets/851b25ed-98b7-4a5c-8d3e-fece385e3af2"
/> |

<!--
## Preview links

If relevant, include links to changed pages for easy review access.

Copy the preview base URL from the Vercel bot comment on this PR. Use
the following table as an example template.

| Site | Live | Preview | Search for |
| -------------- |
-------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------
| ----------------------------- |
| WWW | [/blog/your-post](https://supabase.com/blog/your-post) |
[/blog/your-post](https://zone-www-dot-com-git-branch-name-supabase.vercel.app/blog/your-post)
| unique phrase from the change |
| Docs |
[/docs/guides/your-page](https://supabase.com/docs/guides/your-page) |
[/docs/guides/your-page](https://docs-git-branch-name-supabase.vercel.app/docs/guides/your-page)
| unique phrase from the change |
| Studio | [/dashboard](https://supabase.com/dashboard) |
[/dashboard](https://studio-git-branch-name-supabase.vercel.app/dashboard)
| unique phrase from the change |
| Design system | [/design-system](https://supabase.com/design-system) |
[/design-system](https://design-system-git-branch-name-supabase.vercel.app/design-system)
| unique phrase from the change |
| UI library | [/library](https://supabase.com/library) |
[/library](https://ui-library-git-branch-name-supabase.vercel.app/library)
| unique phrase from the change |
| Knowledge base |
[/kb/guides/your-page](https://supabase.com/kb/guides/your-page) |
[/kb/guides/your-page](https://kb-git-branch-name-supabase.vercel.app/kb/guides/your-page)
| unique phrase from the change |
-->

<!-- ## Additional context

Optionally add any other context or screenshots.

-->

## Review instructions

1. visit
[/docs/guides/getting-started/quickstarts/nextjs](https://docs-git-fix-docs-codetabs-file-tabs-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs#7-query-supabase-data-from-nextjs)


## Checklist

Check all before review:

- [x] I have read
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
- [x] If I wrote a new docs topic or edited an existing topic, I used
the `/write-the-docs` or `/edit-the-docs` skill, which references
[WORD_LIST](https://github.com/supabase/supabase/blob/master/apps/docs/WORD_LIST.md)
and the docs
[CONTRIBUTING](https://github.com/supabase/supabase/blob/master/apps/docs/CONTRIBUTING.md)
guide


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Documentation code samples support labeled tabs, with the first tab
selected by default.
* Source menus show organization and repository paths, and source links
remain labeled “View source.”
* **Style**
* Updated code sample tabs with active-tab accents and flares, plus
clearer focus outlines and depth effects.
* Refined code-block spacing and presentation, including a styled source
footer and updated menu alignment.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-25 18:12:11 +03:00

152 lines
4.1 KiB
TypeScript

'use client'
import Link from 'next/link'
import { type PropsWithChildren, type ReactNode } from 'react'
import {
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
IconGitHubSolid,
} from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
interface CodeSampleWrapperProps {
source: string | URL | (string | URL)[]
}
interface MultipleSourcesProps {
sources: (string | URL)[]
}
interface SingleSourceProps {
source: string | URL
}
interface SourceFrameProps {
footer: ReactNode
}
const SOURCE_FOOTER_CLASSES = cn(
'not-prose absolute right-0 bottom-0 z-1 flex h-8 w-29.25 items-center gap-2 px-3 whitespace-nowrap',
'text-xs text-foreground-lighter no-underline transition-colors hover:text-foreground',
'focus-inset rounded-md'
)
const SOURCE_NOTCH_OUTLINE =
'M0 39.5A7.5 7.5 0 0 0 7.5 32V16A8.5 8.5 0 0 1 16 7.5H117A7.5 7.5 0 0 0 124.5 0'
const SOURCE_NOTCH_FOCUS_RING = 'M0 41A9 9 0 0 0 9 32V16A7 7 0 0 1 16 9H117A9 9 0 0 0 126 0'
const getSourcePath = (source: string | URL): string => {
const [, org, repo, , , ...path] = new URL(source).pathname.split('/')
return `${org}/${repo}/${path.join('/')}`
}
export function CodeSampleDummy() {
return (
<Admonition type="caution" title="Local development warning">
The <code>$CodeSample</code> directive with external repos is not supported in local
development because it relies on a GitHub API key. Please check the preview site to see the
final UI.
</Admonition>
)
}
export function CodeSampleWrapper({
children,
/**
* A GitHub URL to the source code file.
*/
source: _source,
}: PropsWithChildren<CodeSampleWrapperProps>) {
const source = Array.isArray(_source) ? _source : [_source]
if (source.length === 1) {
return <SingleSource source={source[0]}>{children}</SingleSource>
}
if (source.length > 1) {
return <MultipleSources sources={source}>{children}</MultipleSources>
}
return <>{children}</>
}
function MultipleSources({ children, sources }: PropsWithChildren<MultipleSourcesProps>) {
return (
<SourceFrame
footer={
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button tabIndex={0} className={SOURCE_FOOTER_CLASSES}>
<IconGitHubSolid size={14} aria-hidden />
View source
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
{sources.map((source) => (
<DropdownMenuItem
key={source.toString()}
onSelect={() => window.open(source.toString(), '_blank', 'noopener noreferrer')}
>
{getSourcePath(source)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
}
>
{children}
</SourceFrame>
)
}
function SingleSource({ children, source }: PropsWithChildren<SingleSourceProps>) {
return (
<SourceFrame
footer={
<Link
href={source.toString()}
target="_blank"
rel="noopener noreferrer"
className={SOURCE_FOOTER_CLASSES}
>
<IconGitHubSolid size={14} aria-hidden />
View source
</Link>
}
>
{children}
</SourceFrame>
)
}
function SourceFrame({ children, footer }: PropsWithChildren<SourceFrameProps>) {
return (
<div className="group/source code-sample-source shiki-wrapper relative w-full [&_.shiki]:pb-8 [&_.shiki]:shadow-none!">
<div className="codeblock-drop-shadow relative has-[.code-scroll:focus-visible]:z-1">
{children}
</div>
<svg
aria-hidden
width="125"
height="40"
viewBox="0 0 125 40"
fill="none"
className="pointer-events-none absolute right-0 bottom-0 z-1 overflow-visible"
>
<path d={SOURCE_NOTCH_OUTLINE} className="stroke-background-200" />
<path d={SOURCE_NOTCH_OUTLINE} className="stroke-border" />
<path
d={SOURCE_NOTCH_FOCUS_RING}
strokeWidth={2}
className="hidden stroke-ring group-has-[.code-scroll:focus-visible]/source:inline"
/>
</svg>
{footer}
</div>
)
}