Skip to content

Commit 69467b3

Browse files
ankur-archclaude
andcommitted
Getting started: open the alternate setups in a modal instead of <details>
Expanding the native <details> block pushed the sections below it around. The new ModalRow keeps the collapsed paths as a one-line row and opens them in a dialog, so revealing them never shifts the layout. The content stays mounted inside the closed dialog, so the prompts remain in the server-rendered HTML for crawlers (verified against the raw response), and llm-markdown collapses ModalRow to a bold title plus body so the .md output stays clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 78c48c3 commit 69467b3

4 files changed

Lines changed: 102 additions & 5 deletions

File tree

apps/docs/content/docs/(index)/index.mdx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,15 @@ If you're using Express or another Node.js server, follow the [existing-project
9090

9191
<SectionRow title="Other setups" description="Already have an app or a database, or need a single Prisma product on its own? Each path has a guide to follow and a prompt to hand to your agent.">
9292

93-
{/* Native <details> keeps the collapsed paths in the static HTML for crawlers
94-
and agents, and stays keyboard-accessible without client JS. */}
93+
{/* ModalRow keeps the collapsed paths mounted in the served HTML for crawlers
94+
and agents; opening them in a modal never shifts the layout below. */}
9595

96-
<details className="group rounded-xl border bg-fd-card px-4 py-1 open:pb-3">
97-
<summary className="cursor-pointer select-none py-2.5 text-sm font-medium text-fd-foreground marker:text-fd-muted-foreground hover:text-fd-primary">Existing project, your own database, or a single product (ORM, Postgres, or Compute) on its own</summary>
96+
<ModalRow
97+
title="Existing project, your own database, or a single product on its own"
98+
description="Five paths: add to an existing project, bring your own Postgres, or use the ORM, Postgres, or Compute alone."
99+
modalTitle="Other setup paths"
100+
icon={<FolderPlus />}
101+
>
98102

99103
<AgentPrompt title="Add Prisma to an existing project" guideHref="/next/add-to-existing-project/postgresql" guideTitle="PostgreSQL guide" icon={<FolderPlus />}>
100104

@@ -177,7 +181,7 @@ Current docs: https://www.prisma.io/docs/prisma-compute/deploy.md.
177181

178182
If you're using MongoDB, follow the [MongoDB quickstart](/next/quickstart/mongodb) or [add Prisma Next to an existing MongoDB app](/next/add-to-existing-project/mongodb). If you work with [Kysely](/prisma-postgres/quickstart/kysely), [Drizzle](/prisma-postgres/quickstart/drizzle-orm), or [TypeORM](/prisma-postgres/quickstart/typeorm), the Prisma Postgres quickstarts cover those tools.
179183

180-
</details>
184+
</ModalRow>
181185

182186
</SectionRow>
183187

apps/docs/src/components/getting-started.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,85 @@ export function AgentPrompt({
188188
);
189189
}
190190

191+
/**
192+
* A row that opens arbitrary content in a modal instead of expanding inline,
193+
* so revealing it never shifts the layout below. The content stays mounted
194+
* inside the (closed) dialog, so it remains in the served HTML for crawlers
195+
* and in the generated .md / llms output.
196+
*/
197+
export function ModalRow({
198+
title,
199+
description,
200+
modalTitle,
201+
icon,
202+
children,
203+
}: {
204+
title: string;
205+
description?: string;
206+
modalTitle?: string;
207+
icon?: ReactNode;
208+
children: ReactNode;
209+
}) {
210+
const [open, setOpen] = useState(false);
211+
const dialogRef = useRef<HTMLDialogElement>(null);
212+
useEffect(() => {
213+
const dialog = dialogRef.current;
214+
if (!dialog) return;
215+
if (open && !dialog.open) dialog.showModal();
216+
if (!open && dialog.open) dialog.close();
217+
}, [open]);
218+
219+
return (
220+
<div className="not-prose my-4 rounded-xl border bg-fd-card">
221+
<div className="flex flex-wrap items-center gap-x-3 gap-y-2 px-4 py-3">
222+
<span
223+
className="flex size-9 shrink-0 items-center justify-center rounded-lg border bg-fd-background text-fd-primary [&_svg]:size-4"
224+
aria-hidden="true"
225+
>
226+
{icon ?? <Sparkles />}
227+
</span>
228+
<span className="min-w-0 grow">
229+
<span className="block text-sm font-medium">{title}</span>
230+
{description && (
231+
<span className="block text-xs text-fd-muted-foreground">{description}</span>
232+
)}
233+
</span>
234+
<button
235+
className={cn(buttonVariants({ color: "secondary", size: "sm", className: "gap-2" }))}
236+
onClick={() => setOpen(true)}
237+
aria-haspopup="dialog"
238+
aria-expanded={open}
239+
>
240+
<ChevronDown className="size-3.5" aria-hidden="true" />
241+
View
242+
</button>
243+
</div>
244+
<dialog
245+
ref={dialogRef}
246+
onClose={() => setOpen(false)}
247+
onClick={(e) => {
248+
if (e.target === dialogRef.current) setOpen(false);
249+
}}
250+
className="m-auto w-[min(48rem,calc(100vw-2rem))] rounded-xl border bg-fd-card p-0 text-fd-foreground shadow-xl backdrop:bg-black/50"
251+
>
252+
<div className="flex items-center gap-3 border-b px-4 py-3">
253+
<span className="grow text-sm font-medium">{modalTitle ?? title}</span>
254+
<button
255+
aria-label="Close"
256+
onClick={() => setOpen(false)}
257+
className="inline-flex items-center rounded-lg p-1.5 text-fd-muted-foreground transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground"
258+
>
259+
<X className="size-4" aria-hidden="true" />
260+
</button>
261+
</div>
262+
<div className="max-h-[70vh] overflow-y-auto px-4 py-2 text-sm leading-6 text-fd-foreground/80 [&>p]:my-3 [&>p_a]:font-medium [&>p_a]:text-fd-primary [&>p_a]:underline [&>p_a]:decoration-fd-primary/40 [&>p_a]:underline-offset-4 hover:[&>p_a]:decoration-fd-primary">
263+
{children}
264+
</div>
265+
</dialog>
266+
</div>
267+
);
268+
}
269+
191270
/**
192271
* Compact launcher row for the condensed journey: a button opens a modal with
193272
* AI Prompt and CLI tabs, and a copy action grabs the prompt without opening

apps/docs/src/lib/llm-markdown.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,18 @@ export function normalizeProcessedMarkdown(
659659
(_match, attrs: string, content: string) => formatHeroPitch(attrs, content),
660660
)
661661
.replace(/<\/?HeroGrid[^>]*>/g, "")
662+
.replace(
663+
// Same brace-tolerant attrs matcher as AgentPrompt: ModalRow takes
664+
// JSX-element props like icon={<FolderPlus />}. The row's modal content
665+
// collapses to a bold title line plus the body, like <details> does.
666+
/<ModalRow\b((?:[^>"'{]|"[^"]*"|'[^']*'|\{[^{}]*\})*)>([\s\S]*?)<\/ModalRow>/g,
667+
(_match, attrs: string, content: string) => {
668+
const title = getAttribute(attrs, "title");
669+
const body = trimComponentContent(content);
670+
if (!body) return title ? `**${title}**` : "";
671+
return title ? `**${title}**\n\n${body}` : body;
672+
},
673+
)
662674
.replace(/<details\b[^>]*>([\s\S]*?)<\/details>/g, (_match, content: string) =>
663675
formatDetails(content),
664676
);

apps/docs/src/mdx-components.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
HeroPitch,
1010
IconGrid,
1111
IconLink,
12+
ModalRow,
1213
SectionRow,
1314
StackDiagram,
1415
StackLayer,
@@ -125,6 +126,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
125126
HeroPitch,
126127
IconGrid,
127128
IconLink,
129+
ModalRow,
128130
SectionRow,
129131
StackDiagram,
130132
StackLayer,

0 commit comments

Comments
 (0)