mdx layout components

This commit is contained in:
2026-06-19 14:48:38 +02:00
parent 0fe62fcce5
commit 81c60feed9
6 changed files with 80 additions and 28 deletions

View File

@@ -1,25 +0,0 @@
import MDEditor from "@uiw/react-md-editor";
import type { Control, FieldValues, Path } from "react-hook-form";
import { FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form";
export default function MdeFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string, dataColorMode: "dark"|"light" }) {
return (
<FormField
control={params.control}
name={params.name}
render={({ field }) => (
<FormItem>
<FormLabel>
{params.label}
</FormLabel>
<FormControl>
<MDEditor
value={field.value ? field.value : ""}
onChange={field.onChange}
data-color-mode={params.dataColorMode}
/>
</FormControl>
</FormItem>
)}
/>
)
}

View File

@@ -44,6 +44,26 @@ External resource
alt="Describe the image"
caption="Optional caption"
/>`,
},
{
name: "Layout / Row / Column",
description: "Border-less flex layout. Columns sit side by side and wrap when narrow.",
code: `<Layout>
<Row>
<Column>
- First list item
- Second list item
</Column>
<Column>
- Another list
- Side by side
</Column>
</Row>
</Layout>`,
},
{
name: "PullQuote",

View File

@@ -73,6 +73,27 @@ const mdxAutocompleteSuggestions: MdeAutocompleteSuggestion[] = [
group: 'Component',
trigger: '<',
},
{
label: 'Layout',
value: `<Layout>\n<Row>\n<Column>\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Column>\n<Column>\n\n</Column>\n</Row>\n</Layout>`,
detail: 'Flex layout wrapper with side-by-side, wrapping columns.',
group: 'Component',
trigger: '<',
},
{
label: 'Row',
value: `<Row>\n<Column>\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Column>\n<Column>\n\n</Column>\n</Row>`,
detail: 'Side-by-side columns that wrap when narrow.',
group: 'Component',
trigger: '<',
},
{
label: 'Column',
value: `<Column>\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Column>`,
detail: 'Vertically stacked column within a Row.',
group: 'Component',
trigger: '<',
},
{
label: 'PullQuote',
value: `<PullQuote>\n${AUTOCOMPLETE_CURSOR_MARKER}\n</PullQuote>`,

View File

@@ -24,7 +24,7 @@ export default async function CvPage() {
components={mdxComponents}
options={{
mdxOptions: {
format: "md",
format: "mdx",
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypeHighlight],
},

View File

@@ -23,7 +23,7 @@ export default async function ProjectsPage() {
components={mdxComponents}
options={{
mdxOptions: {
format: "md",
format: "mdx",
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypeHighlight],
},

View File

@@ -111,6 +111,31 @@ function Img({ src, alt, title }: { src: string; alt?: string; title?: string })
return <img src={src} alt={alt ?? ""} className="w-full rounded-md border object-cover" />;
}
// Composable, border-less flex layout primitives for MDX.
//
// <Layout> — section wrapper, stacks its children with spacing
// <Row> — lays children side by side, wraps when too narrow
// <Column> — stacks its own children vertically
//
// Row/Column nest freely. A Row sizes its direct children into equal,
// flex-growing tracks with a min width, so two lists/Columns sit side by
// side and drop to stacked once they can't keep ~16rem each.
function Layout({ children }: { children: ReactNode }) {
return <div className="my-6 flex flex-col gap-6 [&>*]:my-0">{children}</div>;
}
function Row({ children }: { children: ReactNode }) {
return (
<div className="flex flex-wrap gap-x-8 gap-y-4 [&>*]:mt-0 [&>*]:min-w-[16rem] [&>*]:flex-1">
{children}
</div>
);
}
function Column({ children }: { children: ReactNode }) {
return <div className="flex min-w-0 flex-col gap-2 [&>*]:my-0">{children}</div>;
}
function PullQuote({ children }: { children: ReactNode }) {
return (
<blockquote className="border-primary my-8 border-l-4 pl-5 text-xl leading-8 font-medium">
@@ -135,7 +160,15 @@ function ExternalLink(props: ComponentPropsWithoutRef<"a">) {
);
}
const blockComponents = new Set<unknown>([Callout, Figure, PullQuote, TagList]);
const blockComponents = new Set<unknown>([
Callout,
Column,
Figure,
Layout,
PullQuote,
Row,
TagList,
]);
function Paragraph({ children }: { children: ReactNode }) {
const containsBlockComponent = Children.toArray(children).some(
@@ -153,9 +186,12 @@ export const mdxComponents = {
Badge,
ButtonLink,
Callout,
Column,
Figure,
img: Img,
Layout,
Lead,
PullQuote,
Row,
TagList,
};