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/mdsx.config.js b/docs/mdsx.config.js index d8e85f021..dd76b8cea 100644 --- a/docs/mdsx.config.js +++ b/docs/mdsx.config.js @@ -7,10 +7,9 @@ import rehypePrettyCode from 'rehype-pretty-code'; import { prettyCodeOptions, - rehypeCodeBlockTitle, - rehypeHandleCodeBlocks, + rehypeCodeBlocks, remarkLiveCode, - remarkDirectives + remarkComponents } from './src/lib/markdown/config/index.js'; export const mdsxConfig = defineConfig({ @@ -18,15 +17,14 @@ 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: [ rehypeSlug, // rehypeComponentExample, [rehypePrettyCode, prettyCodeOptions], - rehypeCodeBlockTitle, - rehypeHandleCodeBlocks + rehypeCodeBlocks ], blueprints: { default: { diff --git a/docs/src/app.css b/docs/src/app.css index 77b0ff813..ddd9d4408 100644 --- a/docs/src/app.css +++ b/docs/src/app.css @@ -78,11 +78,9 @@ 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 */ + /* 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 */ @@ -110,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; @@ -140,18 +150,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/Button.svelte b/docs/src/lib/markdown/components/Button.svelte new file mode 100644 index 000000000..c3252fc49 --- /dev/null +++ b/docs/src/lib/markdown/components/Button.svelte @@ -0,0 +1,38 @@ + + + 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/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/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/Tab.svelte b/docs/src/lib/markdown/components/Tab.svelte index 64d6a8e88..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,16 +15,20 @@ 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 // 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..44838dd1b 100644 --- a/docs/src/lib/markdown/components/Tabs.svelte +++ b/docs/src/lib/markdown/components/Tabs.svelte @@ -1,3 +1,19 @@ + + - - - {#if hasIconifyIcons} - - {/if} - - -
+
{#each tabs as tab, index} @@ -67,17 +84,18 @@ ? 'bg-surface-100 text-surface-content border-b-surface-100' : 'bg-surface-200 text-surface-content/50 hover:text-surface-content' )} - onclick={() => (activeTab = index)} + onclick={() => { + if (syncedState) { + syncedState.activeLabel = tab.label ?? null; + } else { + localActiveIndex = index; + } + }} > {#if tab.icon} - {#if typeof tab.icon === 'string'} - - - {:else} - - {@const IconComponent = tab.icon} - - {/if} + + {@const IconComponent = tab.icon} + {/if} {tab.label || `Tab ${index + 1}`} @@ -85,7 +103,7 @@
-
+
{@render children?.()}
diff --git a/docs/src/lib/markdown/components/p.svelte b/docs/src/lib/markdown/components/p.svelte index b11adebc4..0717f0bad 100644 --- a/docs/src/lib/markdown/components/p.svelte +++ b/docs/src/lib/markdown/components/p.svelte @@ -5,12 +5,6 @@ let { class: className, children, ...restProps }: HTMLAttributes = $props(); -

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

{@render children?.()}

diff --git a/docs/src/lib/markdown/components/pre.svelte b/docs/src/lib/markdown/components/pre.svelte index d3c38eb23..068e6f6b5 100644 --- a/docs/src/lib/markdown/components/pre.svelte +++ b/docs/src/lib/markdown/components/pre.svelte @@ -1,20 +1,84 @@ -
-	{@render children?.()}
-
+
+ {#if dataTitle} +
+ + {dataTitle} +
+ {/if} + +
{@render children?.()}
+ +
+ +
+
diff --git a/docs/src/lib/markdown/config/index.js b/docs/src/lib/markdown/config/index.js index 2365458f1..418a8327a 100644 --- a/docs/src/lib/markdown/config/index.js +++ b/docs/src/lib/markdown/config/index.js @@ -1,10 +1,9 @@ // 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'; -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') { diff --git a/docs/src/lib/markdown/remark/components.js b/docs/src/lib/markdown/remark/components.js new file mode 100644 index 000000000..022afa9f2 --- /dev/null +++ b/docs/src/lib/markdown/remark/components.js @@ -0,0 +1,228 @@ +import { cls } from '@layerstack/tailwind'; +import { visit, EXIT } from 'unist-util-visit'; + +/** + * Remark plugin to transform MDC components (::tip, ::note, ::steps, etc.) into custom Svelte components + * Works with remark-mdc to convert MDC components into Svelte components + * + * Supported components: + * - ::tip / :::tip - renders as Note component with variant="tip" + * - ::note / :::note - renders as Note component with variant="note" + * - ::warning / :::warning - renders as Note component with variant="warning" + * - ::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 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) => { + // Handle both leafComponent (::component) and containerComponent (::component...::) + if (node.type === 'leafComponent' || node.type === 'containerComponent') { + const componentName = node.name; + + // Alert variants all use the Note component + const alertVariants = ['tip', 'note', 'warning', 'caution']; + + // Map component names to Svelte component names and variants + let svelteComponent; + let variant; + + if (alertVariants.includes(componentName)) { + svelteComponent = 'Note'; + variant = componentName; + } else if (componentName === 'steps') { + svelteComponent = 'Steps'; + } else if (componentName === 'tabs') { + svelteComponent = 'Tabs'; + } else if (componentName === 'tab') { + svelteComponent = 'Tab'; + } else { + // Unknown component, skip transformation + return; + } + + // Track which components we need to import + componentsToImport.add(svelteComponent); + + // 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 = { + ...processedAttributes, + // Pass variant for alert components + ...(variant && { variant }) + }; + } + + // Handle inline text components (:component) + if (node.type === 'textComponent') { + const componentName = node.name; + + // Support :icon{name="logos:tailwindcss-icon"} or :icon{name="i-lucide-code"} syntax + if (componentName === 'icon') { + 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, + class: cls('inline-block', className) + }; + } + } 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 = processedAttributes; + } else if (componentName === 'example') { + componentsToImport.add('Example'); + + const data = node.data || (node.data = {}); + data.hName = 'Example'; + data.hProperties = { + ...(node.attributes || {}) + }; + } + } + }); + + // Inject component imports at the beginning of the file + if (componentsToImport.size > 0 || iconImports.size > 0) { + // Generate regular component imports + const componentArray = Array.from(componentsToImport); + const componentImportStatements = componentArray + .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'); + + // 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) => { + if (node.value.startsWith(']*>/, + /** @param {string} match */ + (match) => `${match}\n${importStatements}` + ); + return EXIT; + } + }); + + if (!hasScript) { + // Create new script tag at the beginning + tree.children.unshift({ + type: 'html', + value: `` + }); + } + } + }; +} diff --git a/docs/src/lib/markdown/remark/directives.js b/docs/src/lib/markdown/remark/directives.js deleted file mode 100644 index 2a0045742..000000000 --- a/docs/src/lib/markdown/remark/directives.js +++ /dev/null @@ -1,114 +0,0 @@ -import { visit, EXIT } from 'unist-util-visit'; - -/** - * Remark plugin to transform MDC components (::tip, ::note, ::steps, etc.) into custom Svelte components - * Works with remark-mdc to convert MDC components into Svelte components - * - * Supported components: - * - ::tip / :::tip - renders as Note component with variant="tip" - * - ::note / :::note - renders as Note component with variant="note" - * - ::warning / :::warning - renders as Note component with variant="warning" - * - ::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) - * - * @returns {(tree: any) => void} A remark transformer function - */ -export function remarkDirectives() { - return (tree) => { - const componentsToImport = new Set(); - - // Process MDC components (remark-mdc creates leafComponent and containerComponent nodes) - visit(tree, (node) => { - // Handle both leafComponent (::component) and containerComponent (::component...::) - if (node.type === 'leafComponent' || node.type === 'containerComponent') { - const componentName = node.name; - - // Alert variants all use the Note component - const alertVariants = ['tip', 'note', 'warning', 'caution']; - - // Map component names to Svelte component names and variants - let svelteComponent; - let variant; - - if (alertVariants.includes(componentName)) { - svelteComponent = 'Note'; - variant = componentName; - } else if (componentName === 'steps') { - svelteComponent = 'Steps'; - } else if (componentName === 'tabs') { - svelteComponent = 'Tabs'; - } else if (componentName === 'tab') { - svelteComponent = 'Tab'; - } else { - // Unknown component, skip transformation - return; - } - - // Track which components we need to import - componentsToImport.add(svelteComponent); - - // Get component attributes from MDC - const attributes = node.attributes || {}; - - // 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, - // Pass variant for alert components - ...(variant && { variant }) - }; - } - - // Handle inline text components (:component) - if (node.type === 'textComponent') { - const componentName = node.name; - - // Support :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 || {}) - }; - } - } - }); - - // Inject component imports at the beginning of the file - if (componentsToImport.size > 0) { - const componentArray = Array.from(componentsToImport); - const importStatements = componentArray - .map((comp) => `import ${comp} from '$lib/markdown/components/${comp}.svelte';`) - .join('\n'); - - // Check if there's already a script tag - let hasScript = false; - visit(tree, 'html', (node) => { - if (node.value.startsWith(']*>/, - /** @param {string} match */ - (match) => `${match}\n${importStatements}` - ); - return EXIT; - } - }); - - if (!hasScript) { - // Create new script tag at the beginning - tree.children.unshift({ - type: 'html', - value: `` - }); - } - } - }; -} 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. ) diff --git a/docs/src/routes/docs/getting-started/+page.md b/docs/src/routes/docs/getting-started/+page.md index 4bd3b2de6..4439a7784 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" icon="custom-brands:skeleton"} + ```css title="app.css" + /* v3 */ + @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} - -
-
+ /* v4 */ + @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" 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/) + + :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"} + :: + +::: 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 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 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