Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
01ea518
style: enhance code block appearance and add title support
LadyBluenotes Jan 7, 2026
cd9da91
style: improve code block layout and enhance copy notification
LadyBluenotes Jan 8, 2026
1bb454e
style: update CodeBlock title display and add code metadata extraction
LadyBluenotes Jan 8, 2026
0f5a428
style: enhance CodeBlock styling and improve background colors
LadyBluenotes Jan 8, 2026
d806fd3
style: refactor imports in processor.ts for improved readability
LadyBluenotes Jan 8, 2026
5e23f1e
Better llms.txt
tannerlinsley Jan 7, 2026
6b104d1
Add powersync to partners
tannerlinsley Jan 8, 2026
b510288
fix ai landing
tannerlinsley Jan 8, 2026
30a3145
fix auto theme, add single showcase vieo
tannerlinsley Jan 8, 2026
c71a061
fix feedback context
tannerlinsley Jan 8, 2026
720cb3c
Squashed commit of the following:
tannerlinsley Jan 8, 2026
132bb09
style: improve CodeBlock title extraction and enhance Tabs component …
LadyBluenotes Jan 8, 2026
b388bce
style: add FileTabs component and enhance code block styling for file…
LadyBluenotes Jan 8, 2026
9260701
style: enhance package manager tabs with improved code block styling
LadyBluenotes Jan 8, 2026
91c0ce1
style: add FrameworkCodeBlock component and enhance code block extrac…
LadyBluenotes Jan 8, 2026
754133f
checkpoint
tannerlinsley Jan 8, 2026
e3115a8
checkpoint
tannerlinsley Jan 8, 2026
f5ef3a2
style: improve code structure and enhance rendering of tab panel chil…
LadyBluenotes Jan 8, 2026
2759488
Merge branch 'main' into tabs-code-styling
LadyBluenotes Jan 8, 2026
92268cd
ci: apply automated fixes
autofix-ci[bot] Jan 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 31 additions & 27 deletions src/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type { Mermaid } from 'mermaid'
import { transformerNotationDiff } from '@shikijs/transformers'
import { createHighlighter, type HighlighterGeneric } from 'shiki'
import { Button } from './Button'
import { ButtonGroup } from './ButtonGroup'

// Language aliases mapping
const LANG_ALIASES: Record<string, string> = {
Expand Down Expand Up @@ -99,6 +98,16 @@ export function CodeBlock({
isEmbedded?: boolean
showTypeCopyButton?: boolean
}) {
// Extract title from data-code-title attribute, handling both camelCase and kebab-case
const rawTitle = ((props as any)?.dataCodeTitle ||
(props as any)?.['data-code-title']) as string | undefined

// Filter out "undefined" strings, null, and empty strings
const title =
rawTitle && rawTitle !== 'undefined' && rawTitle.trim().length > 0
? rawTitle.trim()
: undefined

const childElement = props.children as
| undefined
| { props?: { className?: string; children?: string } }
Expand All @@ -123,14 +132,9 @@ export function CodeBlock({
const code = children?.props.children

const [codeElement, setCodeElement] = React.useState(
<>
<pre ref={ref} className={`shiki github-light h-full`}>
<code>{lang === 'mermaid' ? <svg /> : code}</code>
</pre>
<pre className={`shiki vitesse-dark`}>
<code>{lang === 'mermaid' ? <svg /> : code}</code>
</pre>
</>,
<pre ref={ref} className={`shiki h-full github-light dark:vitesse-dark`}>
<code>{lang === 'mermaid' ? <svg /> : code}</code>
</pre>,
)

React[
Expand Down Expand Up @@ -189,18 +193,14 @@ export function CodeBlock({
)}
style={props.style}
>
{showTypeCopyButton ? (
<ButtonGroup
className={twMerge(
'absolute z-10 text-sm',
isEmbedded ? 'top-2 right-4' : '-top-3 right-2',
)}
>
{lang ? (
<span className="px-2 py-1 text-xs font-medium">{lang}</span>
) : null}
{(title || showTypeCopyButton) && (
<div className="flex items-center justify-between px-4 py-2 border-b border-gray-500/20 bg-gray-50 dark:bg-gray-900">
<div className="text-xs text-gray-700 dark:text-gray-300">
{title || (lang?.toLowerCase() === 'bash' ? 'sh' : (lang ?? ''))}
</div>

<Button
className="border-0 rounded-none"
className={twMerge('border-0 rounded-md transition-opacity')}
onClick={() => {
let copyContent =
typeof ref.current?.innerText === 'string'
Expand All @@ -215,20 +215,24 @@ export function CodeBlock({
setCopied(true)
setTimeout(() => setCopied(false), 2000)
notify(
<div>
<div className="font-medium">Copied code</div>
<div className="text-gray-500 dark:text-gray-400 text-xs">
<div className="flex flex-col">
<span className="font-medium">Copied code</span>
<span className="text-gray-500 dark:text-gray-400 text-xs">
Code block copied to clipboard
</div>
</span>
</div>,
)
}}
aria-label="Copy code to clipboard"
>
{copied ? <span className="text-xs">Copied!</span> : <Copy />}
{copied ? (
<span className="text-xs">Copied!</span>
) : (
<Copy className="w-4 h-4" />
)}
</Button>
</ButtonGroup>
) : null}
</div>
)}
{codeElement}
</div>
)
Expand Down
58 changes: 58 additions & 0 deletions src/components/FileTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as React from 'react'

export type FileTabDefinition = {
slug: string
name: string
}

export type FileTabsProps = {
tabs: Array<FileTabDefinition>
children: Array<React.ReactNode> | React.ReactNode
id: string
}

export function FileTabs({ tabs, id, children }: FileTabsProps) {
const childrenArray = React.Children.toArray(children)
const [activeSlug, setActiveSlug] = React.useState(tabs[0]?.slug ?? '')

if (tabs.length === 0) return null

return (
<div className="not-prose my-4">
<div className="flex items-center justify-start gap-0 overflow-x-auto overflow-y-hidden bg-gray-100 dark:bg-gray-900 border border-b-0 border-gray-500/20 rounded-t-md">
{tabs.map((tab) => (
<button
key={`${id}-${tab.slug}`}
type="button"
onClick={() => setActiveSlug(tab.slug)}
aria-label={tab.name}
title={tab.name}
className={`px-3 py-1.5 text-sm font-medium transition-colors border-b-2 -mb-[1px] ${
activeSlug === tab.slug
? 'border-current text-current bg-white dark:bg-gray-950'
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-800'
}`}
>
{tab.name}
</button>
))}
</div>
<div>
{childrenArray.map((child, index) => {
const tab = tabs[index]
if (!tab) return null
return (
<div
key={`${id}-${tab.slug}-panel`}
data-tab={tab.slug}
hidden={tab.slug !== activeSlug}
className="file-tabs-panel"
>
{child}
</div>
)
})}
</div>
</div>
)
}
71 changes: 71 additions & 0 deletions src/components/FrameworkCodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react'
import { useLocalCurrentFramework } from './FrameworkSelect'
import { useCurrentUserQuery } from '~/hooks/useCurrentUser'
import { useParams } from '@tanstack/react-router'
import { FileTabs } from './FileTabs'
import type { Framework } from '~/libraries/types'

type CodeBlockMeta = {
title: string
code: string
language: string
}

type FrameworkCodeBlockProps = {
id: string
codeBlocksByFramework: Record<string, CodeBlockMeta[]>
availableFrameworks: string[]
/** Pre-rendered React children for each framework (from domToReact) */
panelsByFramework: Record<string, React.ReactNode>
}

/**
* Renders code blocks for the currently selected framework.
* - If no blocks for framework: shows nothing
* - If 1 block: shows just the code block (minimal style)
* - If multiple blocks: shows as FileTabs (file tabs with names)
*/
export function FrameworkCodeBlock({
id,
codeBlocksByFramework,
panelsByFramework,
}: FrameworkCodeBlockProps) {
const { framework: paramsFramework } = useParams({ strict: false })
const localCurrentFramework = useLocalCurrentFramework()
const userQuery = useCurrentUserQuery()
const userFramework = userQuery.data?.lastUsedFramework

const actualFramework = (paramsFramework ||
userFramework ||
localCurrentFramework.currentFramework ||
'react') as Framework

const normalizedFramework = actualFramework.toLowerCase()

// Find the framework's code blocks
const frameworkBlocks = codeBlocksByFramework[normalizedFramework]
const frameworkPanel = panelsByFramework[normalizedFramework]

if (!frameworkBlocks || frameworkBlocks.length === 0 || !frameworkPanel) {
return null
}

if (frameworkBlocks.length === 1) {
return <div className="framework-code-block">{frameworkPanel}</div>
}

const tabs = frameworkBlocks.map((block, index) => ({
slug: `file-${index}`,
name: block.title || 'Untitled',
}))

const childrenArray = React.Children.toArray(frameworkPanel)

return (
<div className="framework-code-block">
<FileTabs id={`${id}-${normalizedFramework}`} tabs={tabs}>
{childrenArray}
</FileTabs>
</div>
)
}
79 changes: 74 additions & 5 deletions src/components/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { Tabs } from '~/components/Tabs'
import { CodeBlock } from './CodeBlock'
import { PackageManagerTabs } from './PackageManagerTabs'
import type { Framework } from '~/libraries/types'
import { FileTabs } from './FileTabs'
import { FrameworkCodeBlock } from './FrameworkCodeBlock'

type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'

Expand Down Expand Up @@ -124,7 +126,6 @@ const options: HTMLReactParserOptions = {

switch (componentName?.toLowerCase()) {
case 'tabs': {
// Check if this is a package-manager tabs (has metadata)
if (pmMeta) {
try {
const { packagesByFramework, mode } = JSON.parse(pmMeta)
Expand All @@ -148,7 +149,73 @@ const options: HTMLReactParserOptions = {
}
}

// Default tabs variant
// Check if this is files variant
const filesMeta = domNode.attribs['data-files-meta']
if (filesMeta) {
try {
const tabs = attributes.tabs || []
const id =
attributes.id ||
`files-tabs-${Math.random().toString(36).slice(2, 9)}`

const panelElements = domNode.children?.filter(
(child): child is Element =>
child instanceof Element && child.name === 'md-tab-panel',
)

const children = panelElements?.map((panel) =>
domToReact(panel.children as any, options),
)

return (
<FileTabs id={id} tabs={tabs} children={children as any} />
)
} catch {
// Fall through to default tabs if parsing fails
}
}

const frameworkMeta = domNode.attribs['data-framework-meta']
if (frameworkMeta) {
try {
const { codeBlocksByFramework } = JSON.parse(frameworkMeta)
const availableFrameworks = JSON.parse(
domNode.attribs['data-available-frameworks'] || '[]',
)
const id =
attributes.id ||
`framework-${Math.random().toString(36).slice(2, 9)}`

const panelElements = domNode.children?.filter(
(child): child is Element =>
child instanceof Element && child.name === 'md-tab-panel',
)

// Build panelsByFramework map
const panelsByFramework: Record<string, React.ReactNode> = {}
panelElements?.forEach((panel) => {
const fw = panel.attribs['data-framework']
if (fw) {
panelsByFramework[fw] = domToReact(
panel.children as any,
options,
)
}
})

return (
<FrameworkCodeBlock
id={id}
codeBlocksByFramework={codeBlocksByFramework}
availableFrameworks={availableFrameworks}
panelsByFramework={panelsByFramework}
/>
)
} catch {
// Fall through to default tabs if parsing fails
}
}

const tabs = attributes.tabs
const id =
attributes.id || `tabs-${Math.random().toString(36).slice(2, 9)}`
Expand All @@ -162,9 +229,11 @@ const options: HTMLReactParserOptions = {
child instanceof Element && child.name === 'md-tab-panel',
)

const children = panelElements?.map((panel) =>
domToReact(panel.children as any, options),
)
const children = panelElements?.map((panel) => {
const result = domToReact(panel.children as any, options)
// Wrap in fragment to ensure it's a single React node
return <>{result}</>
})

return <Tabs id={id} tabs={tabs} children={children as any} />
}
Expand Down
16 changes: 9 additions & 7 deletions src/components/PackageManagerTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,14 @@ export function PackageManagerTabs({
})

return (
<Tabs
id={id}
tabs={tabs}
children={children}
activeSlug={selectedPackageManager}
onTabChange={(slug) => setPackageManager(slug as PackageManager)}
/>
<div className="package-manager-tabs">
<Tabs
id={id}
tabs={tabs}
children={children}
activeSlug={selectedPackageManager}
onTabChange={(slug) => setPackageManager(slug as PackageManager)}
/>
</div>
)
}
Loading
Loading