Files
supabase/apps/design-system/components/component-preview.tsx
Saxon Fletcher f0afc5948b page layout patterns (#46126)
Adjusts layout / page patterns including design system documentation and
page components.

<img width="937" height="643" alt="image"
src="https://github.com/user-attachments/assets/5f7864a3-04ae-4ccf-b2b8-d9fe5e29554e"
/>

**To test**
- Ensure all existing page types in Studio are un-effected. They
shouldn't be since existing components aren't touched.
- Run design-system and have a read through layout and page header
documentation. Make sure you understand when and where to use the
various combinations of page components and their variants.

**Next**
Once we are happy with these patterns we can gradually start to update
Studio pages that make use of breadcrumbs and/or sub navigation.

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

* **New Features**
  * Added PageBreadcrumbs and PageNav page chrome
* Several new interactive layout examples (Auth Emails, Edge Functions,
Full‑Width, Logs) and demo pages

* **Documentation**
  * New docs for PageBreadcrumbs and PageNav
* Reworked layout guidance and patterns; updated fragment/example docs
and usage notes

* **Improvements**
  * Component preview now supports explicit padding control
  * Simplified page header demo and updated previews

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46126?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-21 09:16:14 +10:00

197 lines
6.2 KiB
TypeScript

'use client'
import { ChevronRight, Expand } from 'lucide-react'
import * as React from 'react'
import { Button, cn, Collapsible, CollapsibleContent, CollapsibleTrigger } from 'ui'
import { Index } from '@/__registry__'
import { useConfig } from '@/hooks/use-config'
import { styles } from '@/registry/styles'
interface ComponentPreviewProps extends React.HTMLAttributes<HTMLDivElement> {
name: string
extractClassname?: boolean
extractedClassNames?: string
align?: 'center' | 'start' | 'end'
peekCode?: boolean
showGrid?: boolean
showDottedGrid?: boolean
wide?: boolean
hideCode?: boolean
padded?: boolean
}
export function ComponentPreview({
name,
children,
className,
extractClassname,
extractedClassNames,
align = 'center',
peekCode = false,
showGrid = false,
showDottedGrid = true,
wide = false,
hideCode = false,
padded = true,
...props
}: ComponentPreviewProps) {
const [config] = useConfig()
const index = styles.findIndex((style) => style.name === config.style)
const Codes = React.Children.toArray(children) as React.ReactElement[]
const Code = Codes[index]
const [expand, setExpandState] = React.useState(false)
const previewClassName = className
const Preview = React.useMemo(() => {
const Component = Index[config.style][name]?.component
if (!Component) {
return (
<p className="text-sm text-muted-foreground">
Component{' '}
<code className="relative rounded-sm bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm">
{name}
</code>{' '}
not found in registry.
</p>
)
}
return <Component />
}, [name, config.style])
const ComponentPreview = React.useMemo(() => {
return (
<>
<div
className={cn(
'preview flex min-h-[256px] w-full justify-center',
padded && 'p-10',
{
'items-center': align === 'center',
'items-start': align === 'start',
'items-end': align === 'end',
},
previewClassName
)}
>
<React.Suspense
fallback={
<div className="flex items-center text-sm text-muted-foreground">Loading...</div>
}
>
{Preview}
</React.Suspense>
</div>
</>
)
}, [Preview, align, padded, previewClassName])
const wideClasses = wide ? '2xl:-ml-20 2xl:-mr-20' : ''
if (peekCode) {
return (
<div className={cn('@container mt-4 mb-12', wideClasses)}>
<div
className={cn(
'relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio'
)}
>
{showGrid && (
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,hsla(var(--foreground-default)/0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-size-[24px_24px]"></div>
)}
{showDottedGrid && (
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(hsla(var(--foreground-default)/0.02)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
)}
<div className="z-10 relative">{ComponentPreview}</div>
{/* <div className="preview-grid-background"></div> */}
</div>
<div className="flex flex-col space-y-4">
<div
className={cn(
'relative',
'w-full rounded-md [&_pre]:my-0',
expand
? '[&_pre]:overflow-auto'
: 'inset-0 [&_pre]:max-h-[196px] [&_pre]:overflow-hidden',
'[&_pre]:rounded-tr-none [&_pre]:rounded-tl-none'
)}
>
{Code}
<div className="absolute bottom-0 w-full flex justify-center mb-4">
<Button
className="rounded-full"
onClick={() => setExpandState(!expand)}
type="default"
icon={<Expand className="text-foreground-lighter" />}
>
{expand ? 'Collapse code' : 'Expand code'}
</Button>
</div>
</div>
</div>
</div>
)
}
return (
<div className={cn('mt-4 mb-12', wideClasses)}>
<div
className={cn('relative bg-studio', {
'rounded-tl-md rounded-tr-md border-t border-l border-r': !hideCode,
'rounded-md border': hideCode,
})}
>
{showGrid && (
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,hsla(var(--foreground-default)/0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-size-[24px_24px]"></div>
)}
{showDottedGrid && (
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(hsla(var(--foreground-default)/0.02)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
)}
<div className="z-10 relative">{ComponentPreview}</div>
</div>
{!hideCode && (
<Collapsible>
<CollapsibleTrigger
className={`
flex
gap-3 items-center
w-full
font-mono
text-xs
text-foreground-light
px-4 py-4
border border-r
group
data-closed:rounded-bl-md data-closed:rounded-br-md
`}
>
<ChevronRight
className="transition-all group-data-open:rotate-90 text-foreground-lighter"
size={14}
/>
View code
</CollapsibleTrigger>
<CollapsibleContent className="transition-all">
<div
className={cn(
'relative',
'w-full rounded-md [&_pre]:my-0',
'[&_pre]:overflow-auto',
'[&_pre]:max-h-[320px]',
'[&_pre]:rounded-tr-none [&_pre]:rounded-tl-none [&_pre]:border-t-transparent'
)}
>
{Code}
</div>
</CollapsibleContent>
</Collapsible>
)}
</div>
)
}