mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## 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 -->
158 lines
5.1 KiB
TypeScript
158 lines
5.1 KiB
TypeScript
import { type PropsWithChildren } from 'react'
|
|
import { bundledLanguages, type BundledLanguage } from 'shiki'
|
|
import { createTwoslasher, type ExtraFiles, type NodeHover } from 'twoslash'
|
|
import { cn } from 'ui'
|
|
|
|
import { CodeBlockControls, CodeBlockTokens, type CodeToken } from './CodeBlock.client'
|
|
import { highlightCode } from './CodeBlock.highlight'
|
|
import { getCodeBlockLabel, getTokenClassName } from './CodeBlock.utils'
|
|
import denoTypes from './types/lib.deno.d.ts.include'
|
|
|
|
const extraFiles: ExtraFiles = { 'deno.d.ts': denoTypes }
|
|
|
|
const twoslasher = createTwoslasher({
|
|
extraFiles,
|
|
// todo: remove once Twoslash stops using deprecated baseUrl and node10 resolution
|
|
compilerOptions: { ignoreDeprecations: '6.0' },
|
|
})
|
|
const TWOSLASHABLE_LANGS: ReadonlyArray<string> = ['js', 'ts', 'javascript', 'typescript']
|
|
|
|
const BUNDLED_LANGUAGES = Object.keys(bundledLanguages)
|
|
|
|
export async function CodeBlock({
|
|
className,
|
|
lang: langSetting,
|
|
lineNumbers = true,
|
|
contents,
|
|
children,
|
|
skipTypeGeneration,
|
|
hideControls = false,
|
|
compact = false,
|
|
}: PropsWithChildren<{
|
|
className?: string
|
|
lang?: string
|
|
lineNumbers?: boolean
|
|
contents?: string
|
|
skipTypeGeneration?: boolean
|
|
hideControls?: boolean
|
|
compact?: boolean
|
|
}>) {
|
|
let code = (contents || extractCode(children)).trim()
|
|
const lang = tryToBundledLanguage(langSetting || '') || extractLang(children)
|
|
|
|
let twoslashed = null as null | Map<number, Map<number, Array<NodeHover>>>
|
|
if (!skipTypeGeneration && lang && TWOSLASHABLE_LANGS.includes(lang)) {
|
|
try {
|
|
const { code: editedCode, nodes } = twoslasher(code)
|
|
const hoverNodes: Array<NodeHover> = nodes.filter((node) => node.type === 'hover')
|
|
twoslashed = annotationsByLine(hoverNodes)
|
|
code = editedCode
|
|
} catch (_err) {
|
|
// Type compilation fails when imports aren't defined
|
|
}
|
|
}
|
|
|
|
const { tokens } = await highlightCode(code, lang)
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'shiki',
|
|
'group',
|
|
'relative has-[.code-scroll:focus-visible]:z-1',
|
|
'has-[.code-scroll:focus-visible]:outline-2 has-[.code-scroll:focus-visible]:outline-ring',
|
|
'not-prose',
|
|
'w-full',
|
|
compact ? 'border-0 my-0!' : 'border border-default rounded-lg shadow-codeblock',
|
|
'bg-200',
|
|
'text-sm',
|
|
className
|
|
)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
'code-scroll',
|
|
'w-full overflow-x-auto overscroll-x-none',
|
|
!compact && 'rounded-lg',
|
|
'focus-visible:outline-hidden'
|
|
)}
|
|
role="group"
|
|
aria-roledescription="code block"
|
|
aria-label={getCodeBlockLabel(lang, tokens.length)}
|
|
tabIndex={0}
|
|
>
|
|
<CodeBlockTokens
|
|
lineNumbers={lineNumbers}
|
|
lines={tokens.map((line, lineIndex) => {
|
|
let offset = 0
|
|
return line.map(({ content, color, fontStyle }): CodeToken => {
|
|
const annotations = twoslashed
|
|
?.get(lineIndex)
|
|
?.get(offset)
|
|
?.map(({ text, docs, tags }) => ({ text, docs, tags }))
|
|
offset += content.length
|
|
const className = getTokenClassName(color, fontStyle)
|
|
|
|
return annotations ? [content, className, annotations] : [content, className]
|
|
})
|
|
})}
|
|
/>
|
|
</div>
|
|
{/* After the code so the block is named before its controls, and outside the scroller so they stay pinned */}
|
|
{!hideControls && <CodeBlockControls content={code.trim()} />}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function extractCode(children: React.ReactNode): string {
|
|
if (typeof children === 'string') return children
|
|
const child = Array.isArray(children) ? children[0] : children
|
|
if (!!child && typeof child === 'object' && 'props' in child) {
|
|
const props = child.props
|
|
if (!!props && typeof props === 'object' && 'children' in props) {
|
|
const code = props.children
|
|
if (typeof code === 'string') return code
|
|
}
|
|
}
|
|
return ''
|
|
}
|
|
|
|
function extractLang(children: React.ReactNode): BundledLanguage | null {
|
|
if (typeof children === 'string') return null
|
|
const child = Array.isArray(children) ? children[0] : children
|
|
if (!!child && typeof child === 'object' && 'props' in child) {
|
|
const props = child.props
|
|
if (!!props && typeof props === 'object' && 'className' in props) {
|
|
const className = props.className
|
|
if (typeof className === 'string') {
|
|
const lang = className.split(' ').find((className) => className.startsWith('language-'))
|
|
return lang ? tryToBundledLanguage(lang.replace('language-', '')) : null
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
function annotationsByLine(nodes: Array<NodeHover>): Map<number, Map<number, Array<NodeHover>>> {
|
|
const result = new Map()
|
|
nodes.forEach((node) => {
|
|
const line = node.line
|
|
const char = node.character
|
|
if (!result.has(line)) {
|
|
result.set(line, new Map())
|
|
}
|
|
if (!result.get(line).has(char)) {
|
|
result.get(line).set(char, [])
|
|
}
|
|
result.get(line).get(char).push(node)
|
|
})
|
|
return result
|
|
}
|
|
|
|
function tryToBundledLanguage(lang: string): BundledLanguage | null {
|
|
if (BUNDLED_LANGUAGES.includes(lang)) {
|
|
return lang as BundledLanguage
|
|
}
|
|
return null
|
|
}
|