From 3f38ce96a12138bf0dd9c1dfe7a5c2593c07aa3c Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 11:52:49 -0500 Subject: [PATCH 01/19] Improve Tabs/Tab component to sync state if `::tabs{key="foo"}` is used --- docs/src/lib/markdown/components/Tab.svelte | 8 ++- docs/src/lib/markdown/components/Tabs.svelte | 55 ++++++++++++++++++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/docs/src/lib/markdown/components/Tab.svelte b/docs/src/lib/markdown/components/Tab.svelte index 64d6a8e88..54710b948 100644 --- a/docs/src/lib/markdown/components/Tab.svelte +++ b/docs/src/lib/markdown/components/Tab.svelte @@ -20,11 +20,15 @@ // Register this tab and get its index // Use untrack to capture the initial values without creating a reactive dependency - const tabIndex = tabsContext?.registerTab(untrack(() => label), untrack(() => icon)) ?? 0; + const tabIndex = + tabsContext?.registerTab( + untrack(() => label), + untrack(() => icon) + ) ?? 0; const isActive = $derived(tabsContext?.activeTab === tabIndex); -
+
{@render children?.()}
diff --git a/docs/src/lib/markdown/components/Tabs.svelte b/docs/src/lib/markdown/components/Tabs.svelte index 504f0ae22..33114c61a 100644 --- a/docs/src/lib/markdown/components/Tabs.svelte +++ b/docs/src/lib/markdown/components/Tabs.svelte @@ -1,3 +1,19 @@ + + + + diff --git a/docs/src/lib/markdown/remark/directives.js b/docs/src/lib/markdown/remark/directives.js index 2a0045742..91c5e2aed 100644 --- a/docs/src/lib/markdown/remark/directives.js +++ b/docs/src/lib/markdown/remark/directives.js @@ -13,6 +13,7 @@ import { visit, EXIT } from 'unist-util-visit'; * - ::tabs / :::tabs - renders as Tabs component (supports nested ::tab) * - ::tab / :::tab - renders as Tab component (used inside tabs, supports icon attribute) * - :icon - renders as Icon component (inline icon with name attribute) + * - :button - renders as Button component (inline button) * * @returns {(tree: any) => void} A remark transformer function */ @@ -77,6 +78,14 @@ export function remarkDirectives() { data.hProperties = { ...(node.attributes || {}) }; + } else if (componentName === 'button') { + componentsToImport.add('Button'); + + const data = node.data || (node.data = {}); + data.hName = 'Button'; + data.hProperties = { + ...(node.attributes || {}) + }; } } }); From 249d8bf36d8c5971605c5bd5e3e8fc4c5268c3d7 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 11:55:39 -0500 Subject: [PATCH 03/19] Rename remarkDirectives to remarkComponents --- docs/mdsx.config.js | 4 ++-- docs/src/lib/markdown/config/index.js | 2 +- docs/src/lib/markdown/remark/{directives.js => components.js} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename docs/src/lib/markdown/remark/{directives.js => components.js} (99%) diff --git a/docs/mdsx.config.js b/docs/mdsx.config.js index d8e85f021..6acdb207c 100644 --- a/docs/mdsx.config.js +++ b/docs/mdsx.config.js @@ -10,7 +10,7 @@ import { rehypeCodeBlockTitle, rehypeHandleCodeBlocks, remarkLiveCode, - remarkDirectives + remarkComponents } from './src/lib/markdown/config/index.js'; export const mdsxConfig = defineConfig({ @@ -18,7 +18,7 @@ export const mdsxConfig = defineConfig({ remarkPlugins: [ remarkGfm, remarkMDC, // Parse MDC syntax (::component, :::component) - remarkDirectives, // Transform MDC components to Svelte components + remarkComponents, // Transform MDC components to Svelte components remarkLiveCode ], rehypePlugins: [ diff --git a/docs/src/lib/markdown/config/index.js b/docs/src/lib/markdown/config/index.js index 2365458f1..e0c657024 100644 --- a/docs/src/lib/markdown/config/index.js +++ b/docs/src/lib/markdown/config/index.js @@ -1,6 +1,6 @@ // Remark plugins export { remarkLiveCode } from '../rehype/live-code.js'; -export { remarkDirectives } from '../remark/directives.js'; +export { remarkComponents } from '../remark/components.js'; // Rehype plugins export { rehypeCodeBlockTitle } from '../rehype/code-block-title.js'; diff --git a/docs/src/lib/markdown/remark/directives.js b/docs/src/lib/markdown/remark/components.js similarity index 99% rename from docs/src/lib/markdown/remark/directives.js rename to docs/src/lib/markdown/remark/components.js index 91c5e2aed..2e546d487 100644 --- a/docs/src/lib/markdown/remark/directives.js +++ b/docs/src/lib/markdown/remark/components.js @@ -17,7 +17,7 @@ import { visit, EXIT } from 'unist-util-visit'; * * @returns {(tree: any) => void} A remark transformer function */ -export function remarkDirectives() { +export function remarkComponents() { return (tree) => { const componentsToImport = new Set(); From 8fc963898810d45259f61e3e0c71432bdeca586d Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 12:11:11 -0500 Subject: [PATCH 04/19] Add :example markdown component --- docs/content-collections.ts | 10 ++++++++-- docs/src/lib/markdown/remark/components.js | 15 ++++++++++++++- docs/src/lib/markdown/utils.ts | 8 ++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/docs/content-collections.ts b/docs/content-collections.ts index e529c3e44..b31ea1f57 100644 --- a/docs/content-collections.ts +++ b/docs/content-collections.ts @@ -42,7 +42,10 @@ const components = defineCollection({ } // Extract the first Example component's name from the markdown content - const usageExample = doc.content.match(/]*name=["']([^"']+)["'][^>]*>/)?.[1]; + // Support both and :example{name="..."} syntax + const usageExample = + doc.content.match(/]*name=["']([^"']+)["'][^>]*>/)?.[1] || + doc.content.match(/:example\{[^}]*name=["']([^"']+)["'][^}]*\}/)?.[1]; return { ...doc, @@ -133,7 +136,10 @@ const utils = defineCollection({ } // Extract the first Example component's name from the markdown content - const usageExample = doc.content.match(/]*name=["']([^"']+)["'][^>]*>/)?.[1]; + // Support both and :example{name="..."} syntax + const usageExample = + doc.content.match(/]*name=["']([^"']+)["'][^>]*>/)?.[1] || + doc.content.match(/:example\{[^}]*name=["']([^"']+)["'][^}]*\}/)?.[1]; return { ...doc, diff --git a/docs/src/lib/markdown/remark/components.js b/docs/src/lib/markdown/remark/components.js index 2e546d487..856b09c6e 100644 --- a/docs/src/lib/markdown/remark/components.js +++ b/docs/src/lib/markdown/remark/components.js @@ -14,6 +14,7 @@ import { visit, EXIT } from 'unist-util-visit'; * - ::tab / :::tab - renders as Tab component (used inside tabs, supports icon attribute) * - :icon - renders as Icon component (inline icon with name attribute) * - :button - renders as Button component (inline button) + * - :example - renders as Example component (inline example) * * @returns {(tree: any) => void} A remark transformer function */ @@ -86,6 +87,14 @@ export function remarkComponents() { data.hProperties = { ...(node.attributes || {}) }; + } else if (componentName === 'example') { + componentsToImport.add('Example'); + + const data = node.data || (node.data = {}); + data.hName = 'Example'; + data.hProperties = { + ...(node.attributes || {}) + }; } } }); @@ -94,7 +103,11 @@ export function remarkComponents() { if (componentsToImport.size > 0) { const componentArray = Array.from(componentsToImport); const importStatements = componentArray - .map((comp) => `import ${comp} from '$lib/markdown/components/${comp}.svelte';`) + .map((comp) => { + // Example component lives in $lib/components, not $lib/markdown/components + const path = comp === 'Example' ? '$lib/components' : '$lib/markdown/components'; + return `import ${comp} from '${path}/${comp}.svelte';`; + }) .join('\n'); // Check if there's already a script tag diff --git a/docs/src/lib/markdown/utils.ts b/docs/src/lib/markdown/utils.ts index 05451c97b..c9c7a3127 100644 --- a/docs/src/lib/markdown/utils.ts +++ b/docs/src/lib/markdown/utils.ts @@ -95,8 +95,12 @@ export async function loadExamplesFromMarkdown( currentPath?: string ): Promise { // Extract all and from markdown content - const regex = /]*?)\/>/g; - const matches = [...markdownContent.matchAll(regex)]; + // Also support :example{component="..." name="..."} and :example{path="..."} syntax + const componentRegex = /]*?)\/>/g; + const mdcRegex = /:example\{([^}]*?)\}/g; + const componentMatches = [...markdownContent.matchAll(componentRegex)]; + const mdcMatches = [...markdownContent.matchAll(mdcRegex)]; + const matches = [...componentMatches, ...mdcMatches]; const pageExamples = matches.map((match) => { const attrs = match[1]; const component = attrs.match(/component="([^"]*?)"/)?.[1] || defaultComponent; // use default component if not explicit (ex. ) From 52971afd79f3fff271655e58c5e5e7ef18838602 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 12:11:47 -0500 Subject: [PATCH 05/19] Migrate getting started to new markdown components --- docs/src/routes/docs/getting-started/+page.md | 412 ++++++++++-------- 1 file changed, 230 insertions(+), 182 deletions(-) diff --git a/docs/src/routes/docs/getting-started/+page.md b/docs/src/routes/docs/getting-started/+page.md index 4bd3b2de6..8b3fec582 100644 --- a/docs/src/routes/docs/getting-started/+page.md +++ b/docs/src/routes/docs/getting-started/+page.md @@ -1,29 +1,101 @@ - +or with a single `.css` import, Layerchart [provides](https://github.com/techniq/layerchart/tree/next/packages/layerchart/src/lib/styles) theming conventions for many popular UI frameworks. -# Getting Started +:::tabs{key="framework"} -LayerChart can be used standlone, or integrates nicely with other frameworks and design systems. + ::tab{label="shadcn-svelte" icon="custom-brands:shadcnsvelte"} + ```css title="app.css" + @import 'layerchart/shadcn-svelte.css'; + ``` + :: + + ::tab{label="Skeleton 3" icon="custom-brands:skeleton"} + ```css title="app.css" + @import 'layerchart/skeleton-3.css'; + ``` + :: -Provides built-in first class support for tailwindcss 4, but is completely optional. The library also works seamlessly with vanilla CSS, inline styles, and unoCSS. - - - git a project`} > -

- Use the Svelte CLI to generate a new SvelteKit project, or continue with an existing project. -

- - {#snippet content(value)} - {#if value === 0} - - {:else if value === 1} - - {:else if value === 2} - - {:else if value === 3} - - {:else if value === 4} - - {/if} - {/snippet} - -
To add tailwind to an existing project you can npv sv add tailwindcss
-
- layerchart with your package manager of choice.`}> - - {#snippet content(value)} - {#if value === 0} - - {:else if value === 1} - - {:else if value === 2} - - {:else if value === 3} - - {:else if value === 4} - - {/if} - {/snippet} - - - -

- Out of the box LayerChart will use currentColor as the default color, but you can customize the CSS globally with a few CSS variables. -

- -

- or with a single .css import, Layerchart provides theming conventions for many popular UI frameworks. -

- - {#snippet content(value)} - {#if value === 0} - - {:else if value === 1} - - {:else if value === 2} - - {:else if value === 3} - - {:else if value === 4} - - {:else if value === 5} - - {/if} - {/snippet} - -
- -

- Import the charting components you need from layerchart. Don't forget to take a look at the large collection of examples for some additonal inspiration. -

-
- -
-
- -

- All set! Now just fire up the dev server and start iterating. Have fun! -

- - {#snippet content(value)} - {#if value === 0} - - {:else if value === 1} - - {:else if value === 2} - - {:else if value === 3} - - {:else if value === 4} - - {/if} - {/snippet} - -
-
+ ::tab{label="Skeleton 4" icon="custom-brands:skeleton"} + ```css title="app.css" + @import 'layerchart/skeleton-4.css'; + ``` + :: + + ::tab{label="Svelte UX" icon="custom-brands:svelteux"} + ```css title="app.css" + /* Works out of the box! */ + ``` + :: + + ::tab{label="DaisyUI 5" icon="custom-brands:daisyui"} + ```css title="app.css" + @import 'layerchart/daisyui-5.css'; + ``` + :: + +::: + +## Create you first chart + +Import the charting components you need from `layerchart`. Don't forget to take a look at the large collection of [examples](/docs/examples) for some additonal inspiration. + +:example{component="LineChart" name="basic" showCode=true} + +## Done! + +All set! Now just fire up the dev server and start iterating. Have fun! + +:::tabs{key="bundler"} + + ::tab{label="pnpm" icon="vscode-icons:file-type-pnpm"} + ```sh + pnpm dev + ``` + :: + + ::tab{label="npm" icon="vscode-icons:file-type-npm"} + ```sh + npm run dev + ``` + :: + + ::tab{label="bun" icon="vscode-icons:file-type-bun"} + ```sh + bun run dev + ``` + :: + + ::tab{label="deno" icon="vscode-icons:file-type-deno"} + ```sh + deno task dev + ``` + :: + + ::tab{label="yarn" icon="vscode-icons:file-type-yarn"} + ```sh + yarn dev + ``` + :: + +::: + +:: ### Git up and running even quicker! Starter [project repos](https://github.com/techniq/layerchart/tree/next/examples) are available for popular UI frameworks. - -{#snippet content(value)} -{#if value === 0} - - -
v1: -{@render githubButton('shadcn-svelte-1')} -{@render stackBlitzButton('shadcn-svelte-1')}
-{:else if value === 1} - -
v3: -{@render githubButton('skeleton-3')} -{@render stackBlitzButton('skeleton-3')}
-
v4: -{@render githubButton('skeleton-4')} -{@render stackBlitzButton('skeleton-4')}
-{:else if value === 2} - -
v2: -{@render githubButton('svelte-ux-2')} -{@render stackBlitzButton('svelte-ux-2')} -
-{:else if value === 3} - -
v5: -{@render githubButton('daisyui-5')} -{@render stackBlitzButton('daisyui-5')}
-{:else if value === 4} - -
-v1: {@render githubButton('unoCSS')}{@render stackBlitzButton('unocss-1')}
-{:else if value === 5} -
Vanilla CSS: {@render githubButton('standalone')} -{@render stackBlitzButton('standalone')}
-{/if} -{/snippet} -
- -{#snippet githubButton(path, text = 'Source')} - -{/snippet} - -{#snippet stackBlitzButton(path, text = 'Open in StackBlitz')} - -{/snippet} +:::tabs{key="framework"} + + ::tab{label="shadcn-svelte" icon="custom-brands:shadcnsvelte"} + [shadcn-svelte](https://www.shadcn-svelte.com/) + + v1: + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/shadcn-svelte-1" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/shadcn-svelte-1" size="sm" icon="simple-icons:stackblitz"} + :: + + ::tab{label="Skeleton" icon="custom-brands:skeleton"} + [Skeleton](https://www.skeleton.dev/) + + v3: + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/skeleton-3" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/skeleton-3" size="sm" icon="simple-icons:stackblitz"} + + v4: + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/skeleton-4" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/skeleton-4" size="sm" icon="simple-icons:stackblitz"} + :: + + ::tab{label="Svelte UX" icon="custom-brands:svelteux"} + [Svelte UX](https://svelte-ux.techniq.dev/) + + v2: + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/svelte-ux-2" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/svelte-ux-2" size="sm" icon="simple-icons:stackblitz"} + :: + + ::tab{label="daisyUI" icon="custom-brands:daisyui"} + [daisyUI](https://daisyui.com/) + + v5: + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/daisyui-5" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/daisyui-5" size="sm" icon="simple-icons:stackblitz"} + :: + + ::tab{label="UnoCSS" icon="logos:unocss"} + [UnoCSS](https://unocss.dev/) + + 1: + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/unocss" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/unocss-1" size="sm" icon="simple-icons:stackblitz"} + :: + + ::tab{label="Vanilla CSS" icon="vscode-icons:file-type-css"} + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/standalone" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/standalone" size="sm" icon="simple-icons:stackblitz"} + :: + +::: From 78cb9f6a43c57c18de723cac75648bd5c4a2cb6c Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 12:14:24 -0500 Subject: [PATCH 06/19] Update lockfile --- pnpm-lock.yaml | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1504204fd..bbd6e88b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -264,9 +264,6 @@ importers: rehype-slug: specifier: ^6.0.0 version: 6.0.0 - remark-directive: - specifier: ^3.0.0 - version: 3.0.1 remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -3870,9 +3867,6 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - mdast-util-directive@3.1.0: - resolution: {integrity: sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==} - mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -3936,9 +3930,6 @@ packages: micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - micromark-extension-directive@3.0.2: - resolution: {integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==} - micromark-extension-gfm-autolink-literal@2.1.0: resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} @@ -4472,9 +4463,6 @@ packages: rehype-stringify@10.0.1: resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} - remark-directive@3.0.1: - resolution: {integrity: sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==} - remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -8750,20 +8738,6 @@ snapshots: markdown-table@3.0.4: {} - mdast-util-directive@3.1.0: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-visit-parents: 6.0.1 - transitivePeerDependencies: - - supports-color - mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -8933,16 +8907,6 @@ snapshots: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - micromark-extension-directive@3.0.2: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - parse-entities: 4.0.2 - micromark-extension-gfm-autolink-literal@2.1.0: dependencies: micromark-util-character: 2.1.1 @@ -9503,15 +9467,6 @@ snapshots: hast-util-to-html: 9.0.5 unified: 11.0.5 - remark-directive@3.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-directive: 3.1.0 - micromark-extension-directive: 3.0.2 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 From c1ef0c8557dfd858884466322ba777e3e3818074 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 12:27:53 -0500 Subject: [PATCH 07/19] Fix scale page (array variabe access broke in markdown) --- docs/src/routes/docs/guides/scales/+page.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/src/routes/docs/guides/scales/+page.md b/docs/src/routes/docs/guides/scales/+page.md index d16b1f8d7..4ed81aa27 100644 --- a/docs/src/routes/docs/guides/scales/+page.md +++ b/docs/src/routes/docs/guides/scales/+page.md @@ -16,6 +16,10 @@ xScale(${domain[1]}) => ${scale(domain[1])}; xScale(${value}) => ${format(scale(value), 'decimal')}; `); + + // array access (`domain[0]`) doesn't work in markdown + let [minDomain, maxDomain] = $derived(domain); + let [minRange, maxRange] = $derived(range); # Scales @@ -28,7 +32,7 @@ LayerChart uses [d3-scale](https://d3js.org/d3-scale) under the hood, which prov -In this interactive example, the **domain** (top bar) represents your data values ranging from {domain[0]} to {domain[1]}, while the **range** (bottom bar) represents the pixel values from {range[0]} to {range[1]}. The animated line shows how a domain value maps to its corresponding range value. Try dragging the edges to resize the domain/range, or mouse over to see how different values map between them. +In this interactive example, the **domain** (top bar) represents your data values ranging from {minDomain} to {maxDomain}, while the **range** (bottom bar) represents the pixel values from {minRange} to {maxRange}. The animated line shows how a domain value maps to its corresponding range value. Try dragging the edges to resize the domain/range, or mouse over to see how different values map between them. ### Creating a scale From 8c89d493f1217c1b6c5c1af3ddf0454baa149c4d Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 22:30:58 -0500 Subject: [PATCH 08/19] Update markdown icon loading to utilize unplugin-icons import/components instead of Icon component with runtime iconify loading --- docs/src/lib/markdown/components/Icon.svelte | 42 ------ docs/src/lib/markdown/components/Tab.svelte | 4 +- docs/src/lib/markdown/components/Tabs.svelte | 38 +----- docs/src/lib/markdown/remark/components.js | 126 +++++++++++++++--- docs/src/routes/docs/getting-started/+page.md | 4 +- 5 files changed, 118 insertions(+), 96 deletions(-) delete mode 100644 docs/src/lib/markdown/components/Icon.svelte diff --git a/docs/src/lib/markdown/components/Icon.svelte b/docs/src/lib/markdown/components/Icon.svelte deleted file mode 100644 index 1d0a2ac7e..000000000 --- a/docs/src/lib/markdown/components/Icon.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - -{#if component} - - {@const IconComponent = component} - -{:else if iconifyName} - - -{/if} diff --git a/docs/src/lib/markdown/components/Tab.svelte b/docs/src/lib/markdown/components/Tab.svelte index 54710b948..8eec7cf23 100644 --- a/docs/src/lib/markdown/components/Tab.svelte +++ b/docs/src/lib/markdown/components/Tab.svelte @@ -7,7 +7,7 @@ interface Props extends HTMLAttributes { children: Snippet; label?: string; - icon?: string | Component; + icon?: Component; } const { children, label, icon, class: className, ...restProps }: Props = $props(); @@ -15,7 +15,7 @@ const tabsContext = getContext<{ activeTab: number; setActiveTab: (index: number) => void; - registerTab: (label: string | undefined, icon: string | Component | undefined) => number; + registerTab: (label: string | undefined, icon: Component | undefined) => number; }>('tabs'); // Register this tab and get its index diff --git a/docs/src/lib/markdown/components/Tabs.svelte b/docs/src/lib/markdown/components/Tabs.svelte index 33114c61a..ce2d099ea 100644 --- a/docs/src/lib/markdown/components/Tabs.svelte +++ b/docs/src/lib/markdown/components/Tabs.svelte @@ -31,7 +31,7 @@ let localActiveIndex = $state(0); let syncedState = $derived(key ? getSyncedState(key) : null); - let tabs = $state>([]); + let tabs = $state>([]); let tabCounter = 0; // Compute active tab index based on synced label or local index @@ -63,38 +63,15 @@ localActiveIndex = index; } }, - registerTab: (label: string | undefined, icon: string | Component | undefined) => { + registerTab: (label: string | undefined, icon: Component | undefined) => { const index = tabCounter++; tabs = [...tabs, { label, icon }]; return index; } }); - // Convert i-collection-name format to collection:name format for Iconify - function getIconifyName(name: string): string { - // If already in collection:name format, use as-is - if (name.includes(':')) return name; - // Convert i-collection-name to collection:name - const match = name.match(/^i-([^-]+)-(.+)$/); - if (match) { - const [, collection, iconName] = match; - return `${collection}:${iconName.replace(/-/g, '-')}`; - } - return name; - } - - // Check if any tabs have string icons (need Iconify) - const hasIconifyIcons = $derived(tabs.some((tab) => typeof tab.icon === 'string')); - - - {#if hasIconifyIcons} - - {/if} - -
@@ -117,14 +94,9 @@ }} > {#if tab.icon} - {#if typeof tab.icon === 'string'} - - - {:else} - - {@const IconComponent = tab.icon} - - {/if} + + {@const IconComponent = tab.icon} + {/if} {tab.label || `Tab ${index + 1}`} diff --git a/docs/src/lib/markdown/remark/components.js b/docs/src/lib/markdown/remark/components.js index 856b09c6e..a28c3a65f 100644 --- a/docs/src/lib/markdown/remark/components.js +++ b/docs/src/lib/markdown/remark/components.js @@ -11,16 +11,56 @@ import { visit, EXIT } from 'unist-util-visit'; * - ::caution / :::caution - renders as Note component with variant="caution" * - ::steps / :::steps - renders as Steps component * - ::tabs / :::tabs - renders as Tabs component (supports nested ::tab) - * - ::tab / :::tab - renders as Tab component (used inside tabs, supports icon attribute) - * - :icon - renders as Icon component (inline icon with name attribute) - * - :button - renders as Button component (inline button) + * - ::tab / :::tab - renders as Tab component (used inside tabs, supports icon attribute via unplugin-icons) + * - :icon - renders as unplugin-icons component (inline icon with name attribute) + * - :button - renders as Button component (inline button, supports icon attribute via unplugin-icons) * - :example - renders as Example component (inline example) * * @returns {(tree: any) => void} A remark transformer function */ +/** + * Convert icon name from various formats to unplugin-icons import format + * @param {string} name - Icon name (e.g., "logos:tailwindcss-icon", "i-logos-tailwindcss-icon", "lucide:code") + * @returns {{importPath: string, componentName: string}} - Import path and PascalCase component name + */ +function convertIconName(name) { + // Remove i- prefix if present + let iconName = name; + if (iconName.startsWith('i-')) { + iconName = iconName.slice(2); + } + + // Split by colon to get collection and icon name + let collection, icon; + if (iconName.includes(':')) { + [collection, icon] = iconName.split(':'); + } else { + // For i-collection-icon format without colon, we need to parse differently + // This is tricky because collections can have hyphens (e.g., vscode-icons) + // For now, assume everything after first hyphen is the icon name + const parts = iconName.split('-'); + collection = parts[0]; + icon = parts.slice(1).join('-'); + } + + // Create PascalCase component name by converting both collection and icon parts + /** @param {string} str */ + const toPascalCase = (str) => + str + .split('-') + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(''); + + const componentName = toPascalCase(collection) + toPascalCase(icon); + const importPath = `~icons/${collection}/${icon}`; + + return { importPath, componentName }; +} + export function remarkComponents() { return (tree) => { const componentsToImport = new Set(); + const iconImports = new Map(); // Map of componentName -> importPath // Process MDC components (remark-mdc creates leafComponent and containerComponent nodes) visit(tree, (node) => { @@ -55,12 +95,26 @@ export function remarkComponents() { // Get component attributes from MDC const attributes = node.attributes || {}; + // Handle icon attribute on tab components (e.g., ::tab{label="pnpm" icon="vscode-icons:file-type-pnpm"}) + let processedAttributes = { ...attributes }; + if (componentName === 'tab' && attributes.icon) { + const { importPath, componentName: iconCompName } = convertIconName(attributes.icon); + iconImports.set(iconCompName, importPath); + + // Replace icon string with component reference expression + // Remove quotes so it becomes {ComponentName} instead of "ComponentName" + processedAttributes = { + ...attributes, + icon: `{${iconCompName}}` + }; + } + // Convert the MDC component into a component that rehype can handle // We set data.hName to tell rehype to convert this to the component const data = node.data || (node.data = {}); data.hName = svelteComponent; data.hProperties = { - ...attributes, + ...processedAttributes, // Pass variant for alert components ...(variant && { variant }) }; @@ -70,23 +124,48 @@ export function remarkComponents() { if (node.type === 'textComponent') { const componentName = node.name; - // Support :icon{name="i-lucide-code"} syntax + // Support :icon{name="logos:tailwindcss-icon"} or :icon{name="i-lucide-code"} syntax if (componentName === 'icon') { - componentsToImport.add('Icon'); - - const data = node.data || (node.data = {}); - data.hName = 'Icon'; - data.hProperties = { - ...(node.attributes || {}) - }; + const iconName = node.attributes?.name; + if (iconName) { + const { importPath, componentName: iconComponentName } = convertIconName(iconName); + + // Track this icon import + iconImports.set(iconComponentName, importPath); + + // Get other attributes (excluding 'name') + const { name: _, class: className, ...otherAttributes } = node.attributes || {}; + + // Convert to the icon component + const data = node.data || (node.data = {}); + data.hName = iconComponentName; + data.hProperties = { + ...otherAttributes, + // Always add inline-block, and append any user-provided classes + class: className ? `inline-block ${className}` : 'inline-block' + }; + } } else if (componentName === 'button') { componentsToImport.add('Button'); + const attributes = node.attributes || {}; + + // Handle icon attribute on button components (e.g., :button{icon="lucide:github"}) + let processedAttributes = { ...attributes }; + if (attributes.icon) { + const { importPath, componentName: iconCompName } = convertIconName(attributes.icon); + iconImports.set(iconCompName, importPath); + + // Replace icon string with component reference expression + processedAttributes = { + ...attributes, + icon: `{${iconCompName}}` + }; + } + const data = node.data || (node.data = {}); data.hName = 'Button'; - data.hProperties = { - ...(node.attributes || {}) - }; + data.hProperties = processedAttributes; } else if (componentName === 'example') { componentsToImport.add('Example'); @@ -100,9 +179,10 @@ export function remarkComponents() { }); // Inject component imports at the beginning of the file - if (componentsToImport.size > 0) { + if (componentsToImport.size > 0 || iconImports.size > 0) { + // Generate regular component imports const componentArray = Array.from(componentsToImport); - const importStatements = componentArray + const componentImportStatements = componentArray .map((comp) => { // Example component lives in $lib/components, not $lib/markdown/components const path = comp === 'Example' ? '$lib/components' : '$lib/markdown/components'; @@ -110,6 +190,18 @@ export function remarkComponents() { }) .join('\n'); + // Generate icon imports from unplugin-icons + const iconImportStatements = Array.from(iconImports.entries()) + .map(([componentName, importPath]) => { + return `import ${componentName} from '${importPath}';`; + }) + .join('\n'); + + // Combine all imports + const importStatements = [componentImportStatements, iconImportStatements] + .filter(Boolean) + .join('\n'); + // Check if there's already a script tag let hasScript = false; visit(tree, 'html', (node) => { diff --git a/docs/src/routes/docs/getting-started/+page.md b/docs/src/routes/docs/getting-started/+page.md index 8b3fec582..56c5f1e7f 100644 --- a/docs/src/routes/docs/getting-started/+page.md +++ b/docs/src/routes/docs/getting-started/+page.md @@ -137,7 +137,7 @@ or with a single `.css` import, Layerchart [provides](https://github.com/techniq ``` :: - ::tab{label="DaisyUI 5" icon="custom-brands:daisyui"} + ::tab{label="DaisyUI 5" icon="custom-brands:daisyUI"} ```css title="app.css" @import 'layerchart/daisyui-5.css'; ``` @@ -225,7 +225,7 @@ Starter [project repos](https://github.com/techniq/layerchart/tree/next/examples :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/svelte-ux-2" size="sm" icon="simple-icons:stackblitz"} :: - ::tab{label="daisyUI" icon="custom-brands:daisyui"} + ::tab{label="daisyUI" icon="custom-brands:daisyUI"} [daisyUI](https://daisyui.com/) v5: From d48a5a506505a2c15cbf61e51b007a14f2a4a773 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 22:36:41 -0500 Subject: [PATCH 09/19] Fix typo --- docs/src/routes/docs/getting-started/+page.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/routes/docs/getting-started/+page.md b/docs/src/routes/docs/getting-started/+page.md index 56c5f1e7f..8181f2cc2 100644 --- a/docs/src/routes/docs/getting-started/+page.md +++ b/docs/src/routes/docs/getting-started/+page.md @@ -51,7 +51,7 @@ Use the Svelte CLI to generate a new SvelteKit project, or continue with an exis ::: ::note -To add tailwind to an existing project you can `npv sv add tailwindcss` +To add tailwind to an existing project you can `npm sv add tailwindcss` :: ## Import `layerchart` with your package manager of choice. From d12ff2052b3147159a15fe41b5a1c895be03f214 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 22:52:28 -0500 Subject: [PATCH 10/19] Fix Skeleton icon color --- docs/src/lib/markdown/components/Tabs.svelte | 3 +-- docs/src/lib/markdown/remark/components.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/src/lib/markdown/components/Tabs.svelte b/docs/src/lib/markdown/components/Tabs.svelte index ce2d099ea..f9848428a 100644 --- a/docs/src/lib/markdown/components/Tabs.svelte +++ b/docs/src/lib/markdown/components/Tabs.svelte @@ -69,7 +69,6 @@ return index; } }); -
@@ -96,7 +95,7 @@ {#if tab.icon} {@const IconComponent = tab.icon} - + {/if} {tab.label || `Tab ${index + 1}`} diff --git a/docs/src/lib/markdown/remark/components.js b/docs/src/lib/markdown/remark/components.js index a28c3a65f..022afa9f2 100644 --- a/docs/src/lib/markdown/remark/components.js +++ b/docs/src/lib/markdown/remark/components.js @@ -1,3 +1,4 @@ +import { cls } from '@layerstack/tailwind'; import { visit, EXIT } from 'unist-util-visit'; /** @@ -141,8 +142,7 @@ export function remarkComponents() { data.hName = iconComponentName; data.hProperties = { ...otherAttributes, - // Always add inline-block, and append any user-provided classes - class: className ? `inline-block ${className}` : 'inline-block' + class: cls('inline-block', className) }; } } else if (componentName === 'button') { From 29fbd1f04cba3b4af0aad2624fea75db8466f006 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 22:52:45 -0500 Subject: [PATCH 11/19] Improve framework tab organization --- docs/src/routes/docs/getting-started/+page.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/src/routes/docs/getting-started/+page.md b/docs/src/routes/docs/getting-started/+page.md index 8181f2cc2..0ee7d91b4 100644 --- a/docs/src/routes/docs/getting-started/+page.md +++ b/docs/src/routes/docs/getting-started/+page.md @@ -119,14 +119,13 @@ or with a single `.css` import, Layerchart [provides](https://github.com/techniq ``` :: - ::tab{label="Skeleton 3" icon="custom-brands:skeleton"} + + ::tab{label="Skeleton" icon="custom-brands:skeleton"} ```css title="app.css" + /* v3 */ @import 'layerchart/skeleton-3.css'; - ``` - :: - ::tab{label="Skeleton 4" icon="custom-brands:skeleton"} - ```css title="app.css" + /* v4 */ @import 'layerchart/skeleton-4.css'; ``` :: @@ -137,7 +136,7 @@ or with a single `.css` import, Layerchart [provides](https://github.com/techniq ``` :: - ::tab{label="DaisyUI 5" icon="custom-brands:daisyUI"} + ::tab{label="daisyUI" icon="custom-brands:daisyUI"} ```css title="app.css" @import 'layerchart/daisyui-5.css'; ``` From e360bc9e0dd78222bc72512b75d7a799512a64d7 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sat, 27 Dec 2025 23:00:17 -0500 Subject: [PATCH 12/19] Fix alignment of source/stackblitz buttons --- docs/src/routes/docs/getting-started/+page.md | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/src/routes/docs/getting-started/+page.md b/docs/src/routes/docs/getting-started/+page.md index 0ee7d91b4..4439a7784 100644 --- a/docs/src/routes/docs/getting-started/+page.md +++ b/docs/src/routes/docs/getting-started/+page.md @@ -200,42 +200,41 @@ Starter [project repos](https://github.com/techniq/layerchart/tree/next/examples [shadcn-svelte](https://www.shadcn-svelte.com/) v1: - :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/shadcn-svelte-1" size="sm" icon="lucide:github"} - :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/shadcn-svelte-1" size="sm" icon="simple-icons:stackblitz"} + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/shadcn-svelte-1" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/shadcn-svelte-1" size="sm" icon="simple-icons:stackblitz"} :: ::tab{label="Skeleton" icon="custom-brands:skeleton"} [Skeleton](https://www.skeleton.dev/) v3: - :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/skeleton-3" size="sm" icon="lucide:github"} - :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/skeleton-3" size="sm" icon="simple-icons:stackblitz"} + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/skeleton-3" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/skeleton-3" size="sm" icon="simple-icons:stackblitz"} v4: - :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/skeleton-4" size="sm" icon="lucide:github"} - :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/skeleton-4" size="sm" icon="simple-icons:stackblitz"} + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/skeleton-4" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/skeleton-4" size="sm" icon="simple-icons:stackblitz"} :: ::tab{label="Svelte UX" icon="custom-brands:svelteux"} [Svelte UX](https://svelte-ux.techniq.dev/) v2: - :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/svelte-ux-2" size="sm" icon="lucide:github"} - :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/svelte-ux-2" size="sm" icon="simple-icons:stackblitz"} + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/svelte-ux-2" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/svelte-ux-2" size="sm" icon="simple-icons:stackblitz"} :: ::tab{label="daisyUI" icon="custom-brands:daisyUI"} [daisyUI](https://daisyui.com/) v5: - :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/daisyui-5" size="sm" icon="lucide:github"} - :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/daisyui-5" size="sm" icon="simple-icons:stackblitz"} + :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/daisyui-5" size="sm" icon="lucide:github"} + :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/daisyui-5" size="sm" icon="simple-icons:stackblitz"} :: ::tab{label="UnoCSS" icon="logos:unocss"} [UnoCSS](https://unocss.dev/) - 1: :button{label="Source" href="https://github.com/techniq/layerchart/tree/docs-v2/examples/unocss" size="sm" icon="lucide:github"} :button{label="Open in StackBlitz" href="https://stackblitz.com/github/techniq/layerchart/tree/docs-v2/examples/unocss-1" size="sm" icon="simple-icons:stackblitz"} :: From 264ab01ec8f39e49c295814b7e0276b6bceb7809 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sun, 28 Dec 2025 10:00:14 -0500 Subject: [PATCH 13/19] Remove unused `rehypeCodeBlockTitle` and rename `rehypeHandleCodeBlocks` to `rehypeCodeBlocks` --- docs/mdsx.config.js | 6 ++-- docs/src/lib/markdown/config/index.js | 3 +- .../lib/markdown/rehype/code-block-title.js | 32 ------------------- .../lib/markdown/rehype/handle-code-blocks.js | 2 +- 4 files changed, 4 insertions(+), 39 deletions(-) delete mode 100644 docs/src/lib/markdown/rehype/code-block-title.js diff --git a/docs/mdsx.config.js b/docs/mdsx.config.js index 6acdb207c..dd76b8cea 100644 --- a/docs/mdsx.config.js +++ b/docs/mdsx.config.js @@ -7,8 +7,7 @@ import rehypePrettyCode from 'rehype-pretty-code'; import { prettyCodeOptions, - rehypeCodeBlockTitle, - rehypeHandleCodeBlocks, + rehypeCodeBlocks, remarkLiveCode, remarkComponents } from './src/lib/markdown/config/index.js'; @@ -25,8 +24,7 @@ export const mdsxConfig = defineConfig({ rehypeSlug, // rehypeComponentExample, [rehypePrettyCode, prettyCodeOptions], - rehypeCodeBlockTitle, - rehypeHandleCodeBlocks + rehypeCodeBlocks ], blueprints: { default: { diff --git a/docs/src/lib/markdown/config/index.js b/docs/src/lib/markdown/config/index.js index e0c657024..418a8327a 100644 --- a/docs/src/lib/markdown/config/index.js +++ b/docs/src/lib/markdown/config/index.js @@ -3,8 +3,7 @@ export { remarkLiveCode } from '../rehype/live-code.js'; export { remarkComponents } from '../remark/components.js'; // Rehype plugins -export { rehypeCodeBlockTitle } from '../rehype/code-block-title.js'; -export { rehypeHandleCodeBlocks } from '../rehype/handle-code-blocks.js'; +export { rehypeCodeBlocks } from '../rehype/handle-code-blocks.js'; export { rehypeComponentExample } from '../rehype/component-example.js'; // Transformers diff --git a/docs/src/lib/markdown/rehype/code-block-title.js b/docs/src/lib/markdown/rehype/code-block-title.js deleted file mode 100644 index 267a7f060..000000000 --- a/docs/src/lib/markdown/rehype/code-block-title.js +++ /dev/null @@ -1,32 +0,0 @@ -import { visit } from 'unist-util-visit'; - -/** - * Handles metadata attributes for code blocks with titles - * Adds data-metadata attribute when a figcaption (title) is present - * @returns {(tree: import('hast').Root) => void} - */ -export function rehypeCodeBlockTitle() { - return (tree) => { - visit(tree, 'element', (node) => { - if ( - node.tagName === 'figure' && - node.properties?.['data-rehype-pretty-code-figure'] !== undefined - ) { - const preElement = node.children?.at(-1); - const firstChild = node.children?.at(0); - - if ( - preElement && - preElement.type === 'element' && - preElement.tagName === 'pre' && - firstChild && - firstChild.type === 'element' && - firstChild.tagName === 'figcaption' - ) { - node.properties['data-metadata'] = ''; - preElement.properties['data-metadata'] = ''; - } - } - }); - }; -} diff --git a/docs/src/lib/markdown/rehype/handle-code-blocks.js b/docs/src/lib/markdown/rehype/handle-code-blocks.js index 7977d9acc..eca68d4d8 100644 --- a/docs/src/lib/markdown/rehype/handle-code-blocks.js +++ b/docs/src/lib/markdown/rehype/handle-code-blocks.js @@ -5,7 +5,7 @@ import { visit } from 'unist-util-visit'; * Supports syntax like ```js frame title="My Code" showLineNumbers * @returns {(tree: import('hast').Root) => void} */ -export function rehypeHandleCodeBlocks() { +export function rehypeCodeBlocks() { return (tree) => { visit(tree, 'element', (node) => { if (node.tagName === 'pre') { From 949f350068ff60f3d0d579465a02bb43084343b7 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sun, 28 Dec 2025 10:16:59 -0500 Subject: [PATCH 14/19] Add file type icons and improve title handling --- docs/src/app.css | 4 +- docs/src/lib/markdown/components/pre.svelte | 43 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/docs/src/app.css b/docs/src/app.css index 77b0ff813..9a365c61a 100644 --- a/docs/src/app.css +++ b/docs/src/app.css @@ -80,9 +80,9 @@ code:not(pre > code):not(.custom) { figure[data-rehype-pretty-code-figure] { @apply rounded-lg outline outline-surface-content/20 dark:outline-surface-content/10 overflow-hidden; - /* Title/filename display */ + /* Title/filename display - hidden, handled by pre.svelte component */ & figcaption[data-rehype-pretty-code-title] { - @apply text-sm font-mono font-medium leading-tight text-surface-content/50 border-b border-surface-content/20 dark:border-surface-content/10 px-4 py-2 bg-surface-100; + @apply hidden; } /* Pre element within figure */ diff --git a/docs/src/lib/markdown/components/pre.svelte b/docs/src/lib/markdown/components/pre.svelte index d3c38eb23..fc5a27214 100644 --- a/docs/src/lib/markdown/components/pre.svelte +++ b/docs/src/lib/markdown/components/pre.svelte @@ -2,17 +2,60 @@ import type { HTMLAttributes } from 'svelte/elements'; import { cls } from '@layerstack/tailwind'; + import SimpleIconsCss from '~icons/simple-icons/css'; + import SimpleIconsJavascript from '~icons/simple-icons/javascript'; + import SimpleIconsTypescript from '~icons/simple-icons/typescript'; + import SimpleIconsJson from '~icons/simple-icons/json'; + import SimpleIconsTerminal from '~icons/simple-icons/windowsterminal'; + import SimpleIconsSvelte from '~icons/simple-icons/svelte'; + import SimpleIconsHTML5 from '~icons/simple-icons/html5'; + let { class: className, children, 'data-title': dataTitle, + 'data-language': dataLanguage, ...restProps }: HTMLAttributes = $props(); + + let Icon = $derived.by(() => { + switch (dataLanguage) { + case 'css': + return SimpleIconsCss; + case 'js': + case 'javascript': + return SimpleIconsJavascript; + case 'ts': + case 'typescript': + return SimpleIconsTypescript; + case 'json': + return SimpleIconsJson; + case 'sh': + case 'bash': + return SimpleIconsTerminal; + case 'svelte': + return SimpleIconsSvelte; + case 'html': + return SimpleIconsHTML5; + default: + return null; + } + }); +{#if dataTitle} +
+ + {dataTitle} +
+{/if} +

From e67343c6f6bc7f31abe014aff82b46d065fd6bc7 Mon Sep 17 00:00:00 2001
From: Sean Lynch 
Date: Sun, 28 Dec 2025 10:23:13 -0500
Subject: [PATCH 15/19] Tweak styling of steps and paragraphs

---
 docs/src/app.css                          | 6 +++---
 docs/src/lib/markdown/components/p.svelte | 5 +----
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/docs/src/app.css b/docs/src/app.css
index 9a365c61a..24a1d654a 100644
--- a/docs/src/app.css
+++ b/docs/src/app.css
@@ -140,18 +140,18 @@ pre {
 
 /* Steps component styling - inspired by Docus */
 .steps {
-	@apply ms-4 ps-8 border-l border-surface-content/10;
+	@apply ms-4 pl-7 border-l border-surface-content/10;
 	counter-reset: step;
 
 	/* Headings (h2, h3, h4) in steps */
 	& :is(h2, h3, h4) {
 		counter-increment: step;
-		@apply relative font-semibold text-base mb-2 mt-6 first:mt-0;
+		@apply relative font-semibold text-lg mb-2 mt-6 first:mt-0;
 
 		/* Counter circle */
 		&::before {
 			content: counter(step);
-			@apply absolute size-6 -start-[45px] bg-surface-100 rounded-full;
+			@apply absolute size-6 -left-10 bg-surface-100 rounded-full;
 			@apply font-semibold text-sm tabular-nums;
 			@apply inline-flex items-center justify-center;
 			@apply ring-1 ring-surface-content/20;
diff --git a/docs/src/lib/markdown/components/p.svelte b/docs/src/lib/markdown/components/p.svelte
index b11adebc4..374654155 100644
--- a/docs/src/lib/markdown/components/p.svelte
+++ b/docs/src/lib/markdown/components/p.svelte
@@ -6,10 +6,7 @@
 
 
 

&:not(:first-child)]:mt-6 ml-2 leading-relaxed', - className - )} + class={cls('text-surface-content [main>&:not(:first-child)]:mt-6 leading-relaxed', className)} {...restProps} > {@render children?.()} From ef4ee63559924dde19a3bead76d08d7269ff9a32 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sun, 28 Dec 2025 10:47:13 -0500 Subject: [PATCH 16/19] Add copy code buttons to markdown code blocks --- docs/src/lib/markdown/components/pre.svelte | 49 ++++++++++++++------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/docs/src/lib/markdown/components/pre.svelte b/docs/src/lib/markdown/components/pre.svelte index fc5a27214..3a1ff3f51 100644 --- a/docs/src/lib/markdown/components/pre.svelte +++ b/docs/src/lib/markdown/components/pre.svelte @@ -1,6 +1,7 @@ -{#if dataTitle} +

+ {#if dataTitle} +
+ + {dataTitle} +
+ {/if} + +
{@render children?.()}
+
- - {dataTitle} +
-{/if} - -
-	{@render children?.()}
-
+
From 8618803e86b1977cb7bcee1f11b1938fac4bb178 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sun, 28 Dec 2025 11:13:11 -0500 Subject: [PATCH 17/19] Adjust some padding --- docs/src/lib/markdown/components/Tabs.svelte | 2 +- docs/src/lib/markdown/components/p.svelte | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/src/lib/markdown/components/Tabs.svelte b/docs/src/lib/markdown/components/Tabs.svelte index f9848428a..1963ebabb 100644 --- a/docs/src/lib/markdown/components/Tabs.svelte +++ b/docs/src/lib/markdown/components/Tabs.svelte @@ -71,7 +71,7 @@ }); -
+
{#each tabs as tab, index} diff --git a/docs/src/lib/markdown/components/p.svelte b/docs/src/lib/markdown/components/p.svelte index 374654155..0717f0bad 100644 --- a/docs/src/lib/markdown/components/p.svelte +++ b/docs/src/lib/markdown/components/p.svelte @@ -5,9 +5,6 @@ let { class: className, children, ...restProps }: HTMLAttributes = $props(); -

&:not(:first-child)]:mt-6 leading-relaxed', className)} - {...restProps} -> +

{@render children?.()}

From 7163ca0ee4f5671c1d8a5ec189ac2c756a4cef38 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Sun, 28 Dec 2025 11:37:31 -0500 Subject: [PATCH 18/19] Add `showLineNumbers` styling and improve code block styling (handle within pre.svelte) --- docs/src/app.css | 2 -- docs/src/lib/markdown/components/LiveCode.svelte | 2 +- docs/src/lib/markdown/components/pre.svelte | 4 +++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/app.css b/docs/src/app.css index 24a1d654a..83affb077 100644 --- a/docs/src/app.css +++ b/docs/src/app.css @@ -78,8 +78,6 @@ code:not(pre > code):not(.custom) { /* Code block figure container */ figure[data-rehype-pretty-code-figure] { - @apply rounded-lg outline outline-surface-content/20 dark:outline-surface-content/10 overflow-hidden; - /* Title/filename display - hidden, handled by pre.svelte component */ & figcaption[data-rehype-pretty-code-title] { @apply hidden; diff --git a/docs/src/lib/markdown/components/LiveCode.svelte b/docs/src/lib/markdown/components/LiveCode.svelte index 5e1310553..36eeb071e 100644 --- a/docs/src/lib/markdown/components/LiveCode.svelte +++ b/docs/src/lib/markdown/components/LiveCode.svelte @@ -10,7 +10,7 @@
-
+
{@render preview()}
{@render children()} diff --git a/docs/src/lib/markdown/components/pre.svelte b/docs/src/lib/markdown/components/pre.svelte index 3a1ff3f51..068e6f6b5 100644 --- a/docs/src/lib/markdown/components/pre.svelte +++ b/docs/src/lib/markdown/components/pre.svelte @@ -48,7 +48,9 @@ }); -
+
{#if dataTitle}
Date: Sun, 28 Dec 2025 11:37:42 -0500 Subject: [PATCH 19/19] More styling refinements --- docs/src/app.css | 12 +++++++ docs/src/lib/markdown/components/Note.svelte | 1 + docs/src/lib/markdown/components/Tabs.svelte | 2 +- docs/src/routes/docs/markdown/+page.md | 34 ++++++++++++++++---- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/docs/src/app.css b/docs/src/app.css index 83affb077..ddd9d4408 100644 --- a/docs/src/app.css +++ b/docs/src/app.css @@ -108,6 +108,18 @@ pre { } } +/* Line numbers */ +code[data-line-numbers] { + counter-reset: line; +} + +code[data-line-numbers] > [data-line]::before { + counter-increment: line; + content: counter(line); + @apply inline-block w-4 mr-6 pr-2 text-right text-surface-content/40; + @apply border-r border-surface-content/10; +} + /* Custom scrollbar styling */ * { scrollbar-width: thin; diff --git a/docs/src/lib/markdown/components/Note.svelte b/docs/src/lib/markdown/components/Note.svelte index 0682c7a88..c74d80d7d 100644 --- a/docs/src/lib/markdown/components/Note.svelte +++ b/docs/src/lib/markdown/components/Note.svelte @@ -27,6 +27,7 @@ class={cls( 'border border-l-[6px] px-4 py-2 my-4 rounded-sm flex items-center gap-2 text-sm', 'bg-(--color)/10 border-(--color)/50', + '[*&>p]:my-2', className )} style:--color={color} diff --git a/docs/src/lib/markdown/components/Tabs.svelte b/docs/src/lib/markdown/components/Tabs.svelte index 1963ebabb..44838dd1b 100644 --- a/docs/src/lib/markdown/components/Tabs.svelte +++ b/docs/src/lib/markdown/components/Tabs.svelte @@ -103,7 +103,7 @@
-
+
{@render children?.()}
diff --git a/docs/src/routes/docs/markdown/+page.md b/docs/src/routes/docs/markdown/+page.md index 3554204df..70f23d0b4 100644 --- a/docs/src/routes/docs/markdown/+page.md +++ b/docs/src/routes/docs/markdown/+page.md @@ -20,6 +20,26 @@
Test
``` +## Line numbers + +````md +```svelte showLineNumbers + + +
Test
+``` +```` + +```svelte showLineNumbers + + +
Test
+``` + ### Diff ````md @@ -158,24 +178,26 @@ This action cannot be undone. This uses `:::caution`. :::steps -## Step 1: Install dependencies +## Install dependencies First, install the required packages: ```bash -npm install remark-mdc +npm install layerchart ``` -## Step 2: Configure mdsx +## Configure + +Do something else -Add the remark-mdc plugin to your mdsx configuration. +## ??? -## Step 3: Use MDC components +## Profit! Start using `::component` and `:::component` syntax in your markdown files! ::: -### Tabs (Nested Components) +### Tabs :::tabs