refactor(ui): migrate the layout components off Tailwind utilities
Container, Columns, Grid, Divider, Image, Link, Typography and Kbd were built from utility classes and class: conditionals. That works only where Tailwind is present, and every variant cost a dozen conditional lines -- Divider spent eleven of them saying which token to paint the rule. They now carry wire-* classes with a local style block, and variants are data attributes the style block selects on. Divider went from eleven conditionals to five rules, and Typography lost thirteen. Behaviour is preserved rather than improved on. Container keeps columns and gap even though a container is not really a grid, because applications depend on them, and its columns default stays 2: the redesign contract test caught that changing it would silently reflow every Container already published. Additive only: Grid gains minItemWidth for an auto-fit track, Divider gains dashed and dotted variants, Image gains fit, and Link gains underline. Verified in a browser rather than by eye, since the pane cannot screenshot: track counts match the declared columns at desktop and collapse correctly below each breakpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,10 +23,10 @@
|
||||
"packages/ui/components/Clipboard.wrn": "bb8651c7e44517db827603d0c0e546b6891093d7e6589c0b6e45d91b6187591b",
|
||||
"packages/ui/components/Collapse.wrn": "c537239831c6938f3c39be0ca3ae8ba928c2490e21cfe5853c10f7221a4678ca",
|
||||
"packages/ui/components/ColorPicker.wrn": "6940b7ae48f1358008b8c3ae6dc7da31f96a1aefadeef18252568b2cda0b5cbf",
|
||||
"packages/ui/components/Columns.wrn": "449a427c2f4dd846b5239b73efe5b8d15ad4f20fa28598802a5eb57c33332391",
|
||||
"packages/ui/components/Columns.wrn": "e01496aa53025444cc51671efcce40d6d24d86775b2cbb1ee0def1862bb6315d",
|
||||
"packages/ui/components/Combobox.wrn": "62a7d861b0e536ceb088cea3552c59acbafaea5184101e3651dc8f4976b99dbc",
|
||||
"packages/ui/components/Confetti.wrn": "22d687beefd5047055f65e806b4e39f81e18887532ec18d439ac364a044b230a",
|
||||
"packages/ui/components/Container.wrn": "be8fce140043eced8b78c8dd14ff85ace4ed2d91671251641ef83e0fe4da8f2a",
|
||||
"packages/ui/components/Container.wrn": "4a0acbb896f4c11714e3925d912b10ef9aeedfa904b4ad8561aa2cbd9a0fdca0",
|
||||
"packages/ui/components/ContextMenu.wrn": "2e011e9cb1c09a3a2344ed3fa29dcc74331cd39f838535d8aee4b249dceef0ce",
|
||||
"packages/ui/components/CopyMarkup.wrn": "8d57a7d72e02c126f855a181fc36e1b969edb90f683dce6b6ffe546d49d2b29b",
|
||||
"packages/ui/components/CustomScrollbar.wrn": "fedb2ab06cc2cf9499c130590945ca6971a3edfb60407b6658ef7756e4be9823",
|
||||
@@ -34,7 +34,7 @@
|
||||
"packages/ui/components/DataTable.wrn": "5a76e50e8559c724aaf01ec954958ac640c99758d44f1eef8e6eb1cb6dc4f862",
|
||||
"packages/ui/components/DatePicker.wrn": "18b07e59c27c2720bf960e9c04ad70d9e422ddcf0817fede74e71899811bb387",
|
||||
"packages/ui/components/DeviceFrame.wrn": "a881cf2cc1f859b43cc3c7a1e1ca0fb40cea7a9e530ab5bdcb985aa9592d11c0",
|
||||
"packages/ui/components/Divider.wrn": "e377f8005249cf4f6c51dd4d04ff9c6a70a3f4e3a79d2c02a8ab601754c8553a",
|
||||
"packages/ui/components/Divider.wrn": "f5cc449611022b3d3eedf982ae48f6b1a289db7855cc9b6ea0248dcda807ffb3",
|
||||
"packages/ui/components/DragAndDrop.wrn": "9a403ce9ed20d36211d8916911871875c9c7d0952a7de1a21a433eaa0b3c3324",
|
||||
"packages/ui/components/Drawer.wrn": "04a77a69edfad6793b5ab60981a719f11061984c087d0d61663566e322649fef",
|
||||
"packages/ui/components/Dropdown.wrn": "c61b46ae6e54f6aacac9f4ed422f6c659deea9a637fd944645acd37bf270b01d",
|
||||
@@ -45,17 +45,17 @@
|
||||
"packages/ui/components/FileUpload.wrn": "3346d8978e134c3a1e6bc742201a3fbb0cffb89da40da6fcd856a2adbb971393",
|
||||
"packages/ui/components/FileUploadProgress.wrn": "83bcc8558c70fbe0e0670642f65998abd222c235f1031be46bd7c65e6b5c5154",
|
||||
"packages/ui/components/Footer.wrn": "a5dfaa5f9548b7fdd4b556c63428e0ea8d22e5e776a5714d76619999438cf618",
|
||||
"packages/ui/components/Grid.wrn": "b5546b899c509c1f3e8f5edad801bc59acf9fe3008a627f0e4476cd61b5b672e",
|
||||
"packages/ui/components/Grid.wrn": "d089d8657298b4d33993ba4bf7b581f816c65db513d8e48cf1fdd18da19e05a8",
|
||||
"packages/ui/components/Hero.wrn": "e4e39dcd59c273192c7e4136f6583279e20b0956bcba4cfbc3f6e01a1fade152",
|
||||
"packages/ui/components/HeroActions.wrn": "c5720efe2ce7f6c114eb2ea4ccc3e8102a1099078ba23fd13770eddeb6aec640",
|
||||
"packages/ui/components/Image.wrn": "4294358e2eddf06ae4384217dbff7693b609c16bf6549f54d6ad27df74d7ae27",
|
||||
"packages/ui/components/Image.wrn": "8113441bf90ab0d1d8618d477125ad339021f39e853db89d656c72c492e7a20b",
|
||||
"packages/ui/components/Input.wrn": "9ad394eed60f1b05bbd2ccd50dc2de7b78245c52c87afd46055d772c38b7b0c9",
|
||||
"packages/ui/components/InputGroup.wrn": "0e23ea541a60e893c9d9e6f95113911ee459468d8d6105bf03aeb55395359123",
|
||||
"packages/ui/components/InputNumber.wrn": "a0aa4f4566bf54ffbef54c99648eab05b5f9870be6ea07639a4ecf3930afb631",
|
||||
"packages/ui/components/Kbd.wrn": "d51fa614b36f971f885410785b481c2e6900b2416e6e7c8868dfda1ebd5f3b4c",
|
||||
"packages/ui/components/Kbd.wrn": "5afaabbb15902bd8fb82cb4155cf6cbfff55600927b54d564d27e2dd8b752386",
|
||||
"packages/ui/components/LayoutSplitter.wrn": "a86dc54b74aed6b05e770cd1802960d6309bba42fe80e899e8aaa8b1b7aa88e3",
|
||||
"packages/ui/components/LegendIndicator.wrn": "6a7607b27a17196f899132eb952203a1073e4077365a94935f485e700cbac665",
|
||||
"packages/ui/components/Link.wrn": "935ac24e8ebd067860dc9709ea9121710315ad6e02ce95cd5e982ae4d5978a02",
|
||||
"packages/ui/components/Link.wrn": "b986abcc66bc0b1c6296f3ff4e560812e35d89cc680d5251d7a3883e68c1d053",
|
||||
"packages/ui/components/List.wrn": "02f960a8e82fa049aac477bac9c1d9371394c86d9c47513138f61bffc79dc7d8",
|
||||
"packages/ui/components/ListGroup.wrn": "37e2ec61ceab20ed020a834da03208c493702b41abcf586816e29f2e50d8ee11",
|
||||
"packages/ui/components/Map.wrn": "7b8a0d5412a464fd7cab8977d816542c506d8c5dab8230a3c984c2caee407c91",
|
||||
@@ -101,7 +101,7 @@
|
||||
"packages/ui/components/TogglePassword.wrn": "405a85cbfa3d0ff0185b52d2805fba88497a5f25501f01b2438ed3a28591a38d",
|
||||
"packages/ui/components/Tooltip.wrn": "e6c8c14a75062d04a2e64245df1a40e67785240573c73bae4b0b32e76ef822bc",
|
||||
"packages/ui/components/TreeView.wrn": "1791a10135595b17b5c746af9e0476e8520d516ba46d64c79dc4efe0af2b500d",
|
||||
"packages/ui/components/Typography.wrn": "a667e800e11d23da74b00047bd9c560a65a5920e5394f58f66a6e0b32572ce8e",
|
||||
"packages/ui/components/Typography.wrn": "1b4d1b91dee0ff522d27c566becc3bc49bd9ec979ae6334e6a15fca5d88eccac",
|
||||
"packages/ui/components/WysiwygEditor.wrn": "636e60b9f9be7a5807cca7ad20e0b0f370ee1ec6c1ecba5d0a577726cc86bc98",
|
||||
"packages/ui/components/alert.wrn": "a6020b3fbf02481f76cea4cfa563a7165c7ccc2920492f6f6b79e98cbf2cf0fa",
|
||||
"packages/ui/components/avatar.wrn": "571b70790aff28a5b7d6adbf0196e77ece1ce153a95b384148cb2bb5f74ab2e9",
|
||||
|
||||
@@ -6,6 +6,7 @@ page ContainerDetail {
|
||||
state playground_columns = Number(ctx.url.searchParams.get("pg_columns") ?? "2")
|
||||
state playground_gap = ctx.url.searchParams.get("pg_gap") ?? "md"
|
||||
state playground_maxWidth = ctx.url.searchParams.get("pg_maxWidth") ?? "xl"
|
||||
state playground_centered = (ctx.url.searchParams.get("pg_centered") ?? "true") === "true"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Container"
|
||||
@@ -20,7 +21,7 @@ page ContainerDetail {
|
||||
<span class="showcase-eyebrow">Layout component</span>
|
||||
<h1>Container</h1>
|
||||
<p>Constrain and align page content with responsive gutters and compact, wide, or full width options.</p>
|
||||
<div class="detail-badges"><span>6 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -29,7 +30,7 @@ page ContainerDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Container size='{playground_size}' color='{playground_color}' columns='{playground_columns}' gap='{playground_gap}' maxWidth='{playground_maxWidth}' class='{playground_class}'><div class="showcase-content-block">Centered container content</div></Container></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Container size='{playground_size}' color='{playground_color}' columns='{playground_columns}' gap='{playground_gap}' maxWidth='{playground_maxWidth}' centered='{playground_centered}' class='{playground_class}'><div class="showcase-content-block">Centered container content</div></Container></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Container>
|
||||
@@ -40,8 +41,8 @@ page ContainerDetail {
|
||||
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>6 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-container-size"><span><strong>size</strong><small>string</small></span><select id="playground-container-size" name="pg_size"><option value="default" selected>default</option><option value="compact">compact</option><option value="spacious">spacious</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-container-color"><span><strong>color</strong><small>string</small></span><select id="playground-container-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-container-columns"><span><strong>columns</strong><small>number</small></span><input id="playground-container-columns" name="pg_columns" type="number" value="2" /></label><label class="playground-field" for="playground-container-gap"><span><strong>gap</strong><small>string</small></span><select id="playground-container-gap" name="pg_gap"><option value="md" selected>md</option><option value="none">none</option><option value="xs">xs</option><option value="sm">sm</option><option value="lg">lg</option><option value="xl">xl</option><option value="2xl">2xl</option></select></label><label class="playground-field" for="playground-container-maxWidth"><span><strong>max Width</strong><small>string</small></span><select id="playground-container-maxWidth" name="pg_maxWidth"><option value="xl" selected>xl</option><option value="compact">compact</option><option value="lg">lg</option><option value="wide">wide</option><option value="2xl">2xl</option><option value="full">full</option></select></label><label class="playground-field" for="playground-container-class"><span><strong>class</strong><small>string</small></span><input id="playground-container-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-container-size"><span><strong>size</strong><small>string</small></span><select id="playground-container-size" name="pg_size"><option value="default" selected>default</option><option value="compact">compact</option><option value="spacious">spacious</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-container-color"><span><strong>color</strong><small>string</small></span><select id="playground-container-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-container-columns"><span><strong>columns</strong><small>number</small></span><input id="playground-container-columns" name="pg_columns" type="number" value="2" /></label><label class="playground-field" for="playground-container-gap"><span><strong>gap</strong><small>string</small></span><select id="playground-container-gap" name="pg_gap"><option value="md" selected>md</option><option value="none">none</option><option value="xs">xs</option><option value="sm">sm</option><option value="lg">lg</option><option value="xl">xl</option><option value="2xl">2xl</option></select></label><label class="playground-field" for="playground-container-maxWidth"><span><strong>max Width</strong><small>string</small></span><select id="playground-container-maxWidth" name="pg_maxWidth"><option value="xl" selected>xl</option><option value="compact">compact</option><option value="lg">lg</option><option value="wide">wide</option><option value="2xl">2xl</option><option value="full">full</option></select></label><label class="playground-toggle" for="playground-container-centered"><span><strong>centered</strong><small>boolean</small></span><input id="playground-container-centered" name="pg_centered" type="checkbox" value="true" checked data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-container-class"><span><strong>class</strong><small>string</small></span><input id="playground-container-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -57,7 +58,7 @@ page ContainerDetail {
|
||||
<div id="container-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Container size="default" columns="2" maxWidth="xl" class="showcase-instance showcase-instance--1"><div class="showcase-content-block">Centered container content</div></Container></div>
|
||||
<div class="demo-render"><Container size="default" columns="2" maxWidth="xl" centered="true" class="showcase-instance showcase-instance--1"><div class="showcase-content-block">Centered container content</div></Container></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Standard content container usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -76,7 +77,7 @@ page ContainerDetail {
|
||||
<div id="container-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Container size="compact" columns="2" maxWidth="compact" class="showcase-instance showcase-instance--2"><div class="showcase-content-block">Compact readable content</div></Container></div>
|
||||
<div class="demo-render"><Container size="compact" columns="2" maxWidth="compact" centered="true" class="showcase-instance showcase-instance--2"><div class="showcase-content-block">Compact readable content</div></Container></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Compact reading container usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -97,7 +98,7 @@ page ContainerDetail {
|
||||
<div id="container-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Container size="spacious" columns="2" maxWidth="full" class="showcase-instance showcase-instance--3"><div class="showcase-content-block">Full-width dashboard content</div></Container></div>
|
||||
<div class="demo-render"><Container size="spacious" columns="2" maxWidth="full" centered="true" class="showcase-instance showcase-instance--3"><div class="showcase-content-block">Full-width dashboard content</div></Container></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Full-width layout container usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -110,7 +111,7 @@ page ContainerDetail {
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>number</td><td><code>2</code></td><td>No</td></tr><tr><td><code>gap</code></td><td>string</td><td><code>"md"</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"xl"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>number</td><td><code>2</code></td><td>No</td></tr><tr><td><code>gap</code></td><td>string</td><td><code>"md"</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"xl"</code></td><td>No</td></tr><tr><td><code>centered</code></td><td>boolean</td><td><code>true</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#container-demo-1">Standard content container</a><a href="#container-demo-2">Compact reading container</a><a href="#container-demo-3">Full-width layout container</a><a href="#api">Props API</a></div>
|
||||
|
||||
@@ -5,6 +5,7 @@ page DividerDetail {
|
||||
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
|
||||
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Divider"
|
||||
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal"
|
||||
state playground_variant = ctx.url.searchParams.get("pg_variant") ?? "solid"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Divider"
|
||||
@@ -19,7 +20,7 @@ page DividerDetail {
|
||||
<span class="showcase-eyebrow">Layout component</span>
|
||||
<h1>Divider</h1>
|
||||
<p>Separate related horizontal or vertical content with optional labels, sizes, and semantic colors.</p>
|
||||
<div class="detail-badges"><span>5 props</span><span>0 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>6 props</span><span>0 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -28,7 +29,7 @@ page DividerDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Divider size='{playground_size}' color='{playground_color}' label='{playground_label}' orientation='{playground_orientation}' class='{playground_class}' /></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Divider size='{playground_size}' color='{playground_color}' label='{playground_label}' orientation='{playground_orientation}' variant='{playground_variant}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Divider
|
||||
@@ -39,8 +40,8 @@ page DividerDetail {
|
||||
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>5 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-divider-size"><span><strong>size</strong><small>string</small></span><select id="playground-divider-size" name="pg_size"><option value="default" selected>default</option><option value="sm">sm</option><option value="lg">lg</option><option value="xs">xs</option><option value="md">md</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-divider-color"><span><strong>color</strong><small>string</small></span><select id="playground-divider-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-divider-label"><span><strong>label</strong><small>string</small></span><input id="playground-divider-label" name="pg_label" type="text" value="Divider" /></label><label class="playground-field" for="playground-divider-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-divider-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-divider-class"><span><strong>class</strong><small>string</small></span><input id="playground-divider-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
<header><div><span>Component props</span><strong>6 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-divider-size"><span><strong>size</strong><small>string</small></span><select id="playground-divider-size" name="pg_size"><option value="default" selected>default</option><option value="sm">sm</option><option value="lg">lg</option><option value="xs">xs</option><option value="md">md</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-divider-color"><span><strong>color</strong><small>string</small></span><select id="playground-divider-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-divider-label"><span><strong>label</strong><small>string</small></span><input id="playground-divider-label" name="pg_label" type="text" value="Divider" /></label><label class="playground-field" for="playground-divider-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-divider-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-divider-variant"><span><strong>variant</strong><small>string</small></span><input id="playground-divider-variant" name="pg_variant" type="text" value="solid" /></label><label class="playground-field" for="playground-divider-class"><span><strong>class</strong><small>string</small></span><input id="playground-divider-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -56,7 +57,7 @@ page DividerDetail {
|
||||
<div id="divider-demo-1" class="demo-canvas demo-canvas--1">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Divider size="default" label="Related information" orientation="horizontal" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="demo-render"><Divider size="default" label="Related information" orientation="horizontal" variant="solid" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Horizontal section divider usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -75,12 +76,13 @@ page DividerDetail {
|
||||
<div id="divider-demo-2" class="demo-canvas demo-canvas--2">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Divider size="sm" label="" orientation="horizontal" class="showcase-instance showcase-instance--2" /></div>
|
||||
<div class="demo-render"><Divider size="sm" label="" orientation="horizontal" variant="secondary" class="showcase-instance showcase-instance--2" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Minimal unlabeled divider usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
<pre><code><Divider
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
@@ -94,7 +96,7 @@ page DividerDetail {
|
||||
<div id="divider-demo-3" class="demo-canvas demo-canvas--3">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
|
||||
<div class="demo-render"><Divider size="lg" color="info" label="Actions" orientation="vertical" class="showcase-instance showcase-instance--3" /></div>
|
||||
<div class="demo-render"><Divider size="lg" color="info" label="Actions" orientation="vertical" variant="success" class="showcase-instance showcase-instance--3" /></div>
|
||||
</div>
|
||||
<section class="demo-code" aria-label="Vertical toolbar divider usage">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
|
||||
@@ -103,12 +105,13 @@ page DividerDetail {
|
||||
color="info"
|
||||
label="Actions"
|
||||
orientation="vertical"
|
||||
variant="success"
|
||||
/></code></pre>
|
||||
</section>
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>variant</code></td><td>string</td><td><code>"solid"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#divider-demo-1">Horizontal section divider</a><a href="#divider-demo-2">Minimal unlabeled divider</a><a href="#divider-demo-3">Vertical toolbar divider</a><a href="#api">Props API</a></div>
|
||||
|
||||
@@ -6,6 +6,7 @@ page GridDetail {
|
||||
state playground_columns = Number(ctx.url.searchParams.get("pg_columns") ?? "2")
|
||||
state playground_gap = ctx.url.searchParams.get("pg_gap") ?? "md"
|
||||
state playground_maxWidth = ctx.url.searchParams.get("pg_maxWidth") ?? "xl"
|
||||
state playground_minItemWidth = ctx.url.searchParams.get("pg_minItemWidth") ?? ""
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
title = "Grid"
|
||||
@@ -20,7 +21,7 @@ page GridDetail {
|
||||
<span class="showcase-eyebrow">Layout component</span>
|
||||
<h1>Grid</h1>
|
||||
<p>Arrange arbitrary content in a responsive configurable CSS grid with stable columns, gaps, alignment, and width.</p>
|
||||
<div class="detail-badges"><span>6 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>7 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -29,7 +30,7 @@ page GridDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Grid size='{playground_size}' color='{playground_color}' columns='{playground_columns}' gap='{playground_gap}' maxWidth='{playground_maxWidth}' class='{playground_class}'><div class="showcase-grid-items"><span>One</span><span>Two</span><span>Three</span></div></Grid></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Grid size='{playground_size}' color='{playground_color}' columns='{playground_columns}' gap='{playground_gap}' maxWidth='{playground_maxWidth}' minItemWidth='{playground_minItemWidth}' class='{playground_class}'><div class="showcase-grid-items"><span>One</span><span>Two</span><span>Three</span></div></Grid></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Grid /></code></pre>
|
||||
@@ -38,8 +39,8 @@ page GridDetail {
|
||||
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>6 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-grid-size"><span><strong>size</strong><small>string</small></span><select id="playground-grid-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-grid-color"><span><strong>color</strong><small>string</small></span><select id="playground-grid-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-grid-columns"><span><strong>columns</strong><small>number</small></span><input id="playground-grid-columns" name="pg_columns" type="number" value="2" /></label><label class="playground-field" for="playground-grid-gap"><span><strong>gap</strong><small>string</small></span><select id="playground-grid-gap" name="pg_gap"><option value="md" selected>md</option><option value="none">none</option><option value="sm">sm</option><option value="lg">lg</option><option value="xl">xl</option><option value="xs">xs</option><option value="2xl">2xl</option></select></label><label class="playground-field" for="playground-grid-maxWidth"><span><strong>max Width</strong><small>string</small></span><select id="playground-grid-maxWidth" name="pg_maxWidth"><option value="xl" selected>xl</option><option value="compact">compact</option><option value="lg">lg</option><option value="wide">wide</option><option value="2xl">2xl</option><option value="full">full</option></select></label><label class="playground-field" for="playground-grid-class"><span><strong>class</strong><small>string</small></span><input id="playground-grid-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-grid-size"><span><strong>size</strong><small>string</small></span><select id="playground-grid-size" name="pg_size"><option value="default" selected>default</option><option value="xs">xs</option><option value="sm">sm</option><option value="md">md</option><option value="lg">lg</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-grid-color"><span><strong>color</strong><small>string</small></span><select id="playground-grid-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-grid-columns"><span><strong>columns</strong><small>number</small></span><input id="playground-grid-columns" name="pg_columns" type="number" value="2" /></label><label class="playground-field" for="playground-grid-gap"><span><strong>gap</strong><small>string</small></span><select id="playground-grid-gap" name="pg_gap"><option value="md" selected>md</option><option value="none">none</option><option value="sm">sm</option><option value="lg">lg</option><option value="xl">xl</option><option value="xs">xs</option><option value="2xl">2xl</option></select></label><label class="playground-field" for="playground-grid-maxWidth"><span><strong>max Width</strong><small>string</small></span><select id="playground-grid-maxWidth" name="pg_maxWidth"><option value="xl" selected>xl</option><option value="compact">compact</option><option value="lg">lg</option><option value="wide">wide</option><option value="2xl">2xl</option><option value="full">full</option></select></label><label class="playground-field" for="playground-grid-minItemWidth"><span><strong>min Item Width</strong><small>string</small></span><input id="playground-grid-minItemWidth" name="pg_minItemWidth" type="text" value="" /></label><label class="playground-field" for="playground-grid-class"><span><strong>class</strong><small>string</small></span><input id="playground-grid-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -112,7 +113,7 @@ page GridDetail {
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>number</td><td><code>2</code></td><td>No</td></tr><tr><td><code>gap</code></td><td>string</td><td><code>"md"</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"xl"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>columns</code></td><td>number</td><td><code>2</code></td><td>No</td></tr><tr><td><code>gap</code></td><td>string</td><td><code>"md"</code></td><td>No</td></tr><tr><td><code>maxWidth</code></td><td>string</td><td><code>"xl"</code></td><td>No</td></tr><tr><td><code>minItemWidth</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#grid-demo-1">Responsive three-column grid</a><a href="#grid-demo-2">Dense four-column grid</a><a href="#grid-demo-3">Full-width two-column grid</a><a href="#api">Props API</a></div>
|
||||
|
||||
@@ -8,6 +8,7 @@ page ImageDetail {
|
||||
state playground_width = ctx.url.searchParams.get("pg_width") ?? ""
|
||||
state playground_height = ctx.url.searchParams.get("pg_height") ?? ""
|
||||
state playground_loading = ctx.url.searchParams.get("pg_loading") ?? "lazy"
|
||||
state playground_fit = ctx.url.searchParams.get("pg_fit") ?? "cover"
|
||||
state playground_rounded = (ctx.url.searchParams.get("pg_rounded") ?? "false") === "true"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
@@ -23,7 +24,7 @@ page ImageDetail {
|
||||
<span class="showcase-eyebrow">Layout component</span>
|
||||
<h1>Image</h1>
|
||||
<p>Render a responsive image with explicit dimensions, loading behavior, alternative text, sizing, and rounded treatment.</p>
|
||||
<div class="detail-badges"><span>9 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>10 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -32,7 +33,7 @@ page ImageDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Image size='{playground_size}' color='{playground_color}' src='{playground_src}' alt='{playground_alt}' width='{playground_width}' height='{playground_height}' loading='{playground_loading}' rounded='{playground_rounded}' class='{playground_class}' /></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Image size='{playground_size}' color='{playground_color}' src='{playground_src}' alt='{playground_alt}' width='{playground_width}' height='{playground_height}' loading='{playground_loading}' fit='{playground_fit}' rounded='{playground_rounded}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Image /></code></pre>
|
||||
@@ -41,8 +42,8 @@ page ImageDetail {
|
||||
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>9 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-image-size"><span><strong>size</strong><small>string</small></span><select id="playground-image-size" name="pg_size"><option value="default" selected>default</option><option value="sm">sm</option><option value="lg">lg</option><option value="xs">xs</option><option value="md">md</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-image-color"><span><strong>color</strong><small>string</small></span><select id="playground-image-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-image-src"><span><strong>src</strong><small>string</small></span><input id="playground-image-src" name="pg_src" type="text" value="" /></label><label class="playground-field" for="playground-image-alt"><span><strong>alt</strong><small>string</small></span><input id="playground-image-alt" name="pg_alt" type="text" value="" /></label><label class="playground-field" for="playground-image-width"><span><strong>width</strong><small>string</small></span><select id="playground-image-width" name="pg_width"><option value="" selected>Auto / default</option><option value="default">default</option><option value="compact">compact</option><option value="wide">wide</option><option value="full">full</option></select></label><label class="playground-field" for="playground-image-height"><span><strong>height</strong><small>string</small></span><input id="playground-image-height" name="pg_height" type="text" value="" /></label><label class="playground-field" for="playground-image-loading"><span><strong>loading</strong><small>string</small></span><select id="playground-image-loading" name="pg_loading"><option value="lazy" selected>lazy</option><option value="eager">eager</option></select></label><label class="playground-toggle" for="playground-image-rounded"><span><strong>rounded</strong><small>boolean</small></span><input id="playground-image-rounded" name="pg_rounded" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-image-class"><span><strong>class</strong><small>string</small></span><input id="playground-image-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
<header><div><span>Component props</span><strong>10 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-image-size"><span><strong>size</strong><small>string</small></span><select id="playground-image-size" name="pg_size"><option value="default" selected>default</option><option value="sm">sm</option><option value="lg">lg</option><option value="xs">xs</option><option value="md">md</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-image-color"><span><strong>color</strong><small>string</small></span><select id="playground-image-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-image-src"><span><strong>src</strong><small>string</small></span><input id="playground-image-src" name="pg_src" type="text" value="" /></label><label class="playground-field" for="playground-image-alt"><span><strong>alt</strong><small>string</small></span><input id="playground-image-alt" name="pg_alt" type="text" value="" /></label><label class="playground-field" for="playground-image-width"><span><strong>width</strong><small>string</small></span><select id="playground-image-width" name="pg_width"><option value="" selected>Auto / default</option><option value="default">default</option><option value="compact">compact</option><option value="wide">wide</option><option value="full">full</option></select></label><label class="playground-field" for="playground-image-height"><span><strong>height</strong><small>string</small></span><input id="playground-image-height" name="pg_height" type="text" value="" /></label><label class="playground-field" for="playground-image-loading"><span><strong>loading</strong><small>string</small></span><select id="playground-image-loading" name="pg_loading"><option value="lazy" selected>lazy</option><option value="eager">eager</option></select></label><label class="playground-field" for="playground-image-fit"><span><strong>fit</strong><small>string</small></span><input id="playground-image-fit" name="pg_fit" type="text" value="cover" /></label><label class="playground-toggle" for="playground-image-rounded"><span><strong>rounded</strong><small>boolean</small></span><input id="playground-image-rounded" name="pg_rounded" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-image-class"><span><strong>class</strong><small>string</small></span><input id="playground-image-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -121,7 +122,7 @@ page ImageDetail {
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>src</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>alt</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>width</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>height</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>loading</code></td><td>string</td><td><code>"lazy"</code></td><td>No</td></tr><tr><td><code>rounded</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>src</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>alt</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>width</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>height</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>loading</code></td><td>string</td><td><code>"lazy"</code></td><td>No</td></tr><tr><td><code>fit</code></td><td>string</td><td><code>"cover"</code></td><td>No</td></tr><tr><td><code>rounded</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#image-demo-1">Responsive content image</a><a href="#image-demo-2">Rounded thumbnail</a><a href="#image-demo-3">Eager hero image</a><a href="#api">Props API</a></div>
|
||||
|
||||
@@ -106,7 +106,7 @@ page KbdDetail {
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"⌘ K"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"K"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#kbd-demo-1">Production default</a><a href="#kbd-demo-2">Compact application</a><a href="#kbd-demo-3">Rich configuration</a><a href="#api">Props API</a></div>
|
||||
|
||||
@@ -7,6 +7,7 @@ page LinkDetail {
|
||||
state playground_href = ctx.url.searchParams.get("pg_href") ?? "#link-demo"
|
||||
state playground_target = ctx.url.searchParams.get("pg_target") ?? ""
|
||||
state playground_rel = ctx.url.searchParams.get("pg_rel") ?? ""
|
||||
state playground_underline = ctx.url.searchParams.get("pg_underline") ?? "hover"
|
||||
state playground_external = (ctx.url.searchParams.get("pg_external") ?? "false") === "true"
|
||||
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
|
||||
seo {
|
||||
@@ -22,7 +23,7 @@ page LinkDetail {
|
||||
<span class="showcase-eyebrow">Layout component</span>
|
||||
<h1>Link</h1>
|
||||
<p>Render an accessible internal or external link with target, relation, size, color, and public focus or click events.</p>
|
||||
<div class="detail-badges"><span>8 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
<div class="detail-badges"><span>9 props</span><span>1 slots</span><span>0 outputs</span><span>Theme ready</span><span>Responsive</span></div>
|
||||
</div>
|
||||
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
|
||||
</header>
|
||||
@@ -31,7 +32,7 @@ page LinkDetail {
|
||||
<div class="playground-workbench">
|
||||
<div class="playground-preview">
|
||||
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Link size='{playground_size}' color='{playground_color}' label='{playground_label}' href='{playground_href}' target='{playground_target}' rel='{playground_rel}' external='{playground_external}' class='{playground_class}' /></div>
|
||||
<div class="playground-preview-source" data-playground-preview><Link size='{playground_size}' color='{playground_color}' label='{playground_label}' href='{playground_href}' target='{playground_target}' rel='{playground_rel}' underline='{playground_underline}' external='{playground_external}' class='{playground_class}' /></div>
|
||||
<section class="playground-code" aria-label="Current component code">
|
||||
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
|
||||
<pre><code data-playground-code><Link
|
||||
@@ -42,8 +43,8 @@ page LinkDetail {
|
||||
|
||||
</div>
|
||||
<form class="playground-controls" data-playground-form>
|
||||
<header><div><span>Component props</span><strong>8 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-link-size"><span><strong>size</strong><small>string</small></span><select id="playground-link-size" name="pg_size"><option value="default" selected>default</option><option value="sm">sm</option><option value="lg">lg</option><option value="xs">xs</option><option value="md">md</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-link-color"><span><strong>color</strong><small>string</small></span><select id="playground-link-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-link-label"><span><strong>label</strong><small>string</small></span><input id="playground-link-label" name="pg_label" type="text" value="Link" /></label><label class="playground-field" for="playground-link-href"><span><strong>href</strong><small>string</small></span><input id="playground-link-href" name="pg_href" type="text" value="#link-demo" /></label><label class="playground-field" for="playground-link-target"><span><strong>target</strong><small>string</small></span><select id="playground-link-target" name="pg_target"><option value="" selected>Auto / default</option><option value="_self">_self</option><option value="_blank">_blank</option><option value="_parent">_parent</option><option value="_top">_top</option></select></label><label class="playground-field" for="playground-link-rel"><span><strong>rel</strong><small>string</small></span><input id="playground-link-rel" name="pg_rel" type="text" value="" /></label><label class="playground-toggle" for="playground-link-external"><span><strong>external</strong><small>boolean</small></span><input id="playground-link-external" name="pg_external" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-link-class"><span><strong>class</strong><small>string</small></span><input id="playground-link-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
<header><div><span>Component props</span><strong>9 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
|
||||
<div class="playground-fields"><label class="playground-field" for="playground-link-size"><span><strong>size</strong><small>string</small></span><select id="playground-link-size" name="pg_size"><option value="default" selected>default</option><option value="sm">sm</option><option value="lg">lg</option><option value="xs">xs</option><option value="md">md</option><option value="xl">xl</option><option value="icon">icon</option><option value="icon-xs">icon-xs</option><option value="icon-sm">icon-sm</option><option value="icon-lg">icon-lg</option></select></label><label class="playground-field" for="playground-link-color"><span><strong>color</strong><small>string</small></span><select id="playground-link-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-link-label"><span><strong>label</strong><small>string</small></span><input id="playground-link-label" name="pg_label" type="text" value="Link" /></label><label class="playground-field" for="playground-link-href"><span><strong>href</strong><small>string</small></span><input id="playground-link-href" name="pg_href" type="text" value="#link-demo" /></label><label class="playground-field" for="playground-link-target"><span><strong>target</strong><small>string</small></span><select id="playground-link-target" name="pg_target"><option value="" selected>Auto / default</option><option value="_self">_self</option><option value="_blank">_blank</option><option value="_parent">_parent</option><option value="_top">_top</option></select></label><label class="playground-field" for="playground-link-rel"><span><strong>rel</strong><small>string</small></span><input id="playground-link-rel" name="pg_rel" type="text" value="" /></label><label class="playground-field" for="playground-link-underline"><span><strong>underline</strong><small>string</small></span><input id="playground-link-underline" name="pg_underline" type="text" value="hover" /></label><label class="playground-toggle" for="playground-link-external"><span><strong>external</strong><small>boolean</small></span><input id="playground-link-external" name="pg_external" type="checkbox" value="true" data-playground-boolean /><i aria-hidden="true"></i></label><label class="playground-field" for="playground-link-class"><span><strong>class</strong><small>string</small></span><input id="playground-link-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -116,7 +117,7 @@ page LinkDetail {
|
||||
</div>
|
||||
</article></div></section>
|
||||
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Link"</code></td><td>No</td></tr><tr><td><code>href</code></td><td>string</td><td><code>"#"</code></td><td>No</td></tr><tr><td><code>target</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>rel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>external</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>size</code></td><td>string</td><td><code>"default"</code></td><td>No</td></tr><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Link"</code></td><td>No</td></tr><tr><td><code>href</code></td><td>string</td><td><code>"#"</code></td><td>No</td></tr><tr><td><code>target</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>rel</code></td><td>string</td><td><code>""</code></td><td>No</td></tr><tr><td><code>underline</code></td><td>string</td><td><code>"hover"</code></td><td>No</td></tr><tr><td><code>external</code></td><td>boolean</td><td><code>false</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
|
||||
</main>
|
||||
<aside class="detail-aside">
|
||||
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#link-demo-1">Standard navigation link</a><a href="#link-demo-2">External resource link</a><a href="#link-demo-3">Large emphasized link</a><a href="#api">Props API</a></div>
|
||||
|
||||
@@ -26,11 +26,11 @@ page LayoutShowcase {
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><Container size="default" columns="2" maxWidth="xl" class="showcase-instance showcase-instance--1"><div class="showcase-content-block">Centered container content</div></Container></div>
|
||||
<div class="catalog-card-stage"><Container size="default" columns="2" maxWidth="xl" centered="true" class="showcase-instance showcase-instance--1"><div class="showcase-content-block">Centered container content</div></Container></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Layout</span><h2>Container</h2><p>Constrain and align page content with responsive gutters and compact, wide, or full width options.</p></div>
|
||||
<div class="catalog-card-footer"><span>6 props · 1 slots</span><a href="/components/container">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/container">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
@@ -46,11 +46,11 @@ page LayoutShowcase {
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
<div class="catalog-card-preview">
|
||||
<div class="catalog-card-glow" aria-hidden="true"></div>
|
||||
<div class="catalog-card-stage"><Divider size="default" label="Related information" orientation="horizontal" class="showcase-instance showcase-instance--1" /></div>
|
||||
<div class="catalog-card-stage"><Divider size="default" label="Related information" orientation="horizontal" variant="solid" class="showcase-instance showcase-instance--1" /></div>
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Layout</span><h2>Divider</h2><p>Separate related horizontal or vertical content with optional labels, sizes, and semantic colors.</p></div>
|
||||
<div class="catalog-card-footer"><span>5 props · 0 slots</span><a href="/components/divider">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>6 props · 0 slots</span><a href="/components/divider">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
@@ -80,7 +80,7 @@ page LayoutShowcase {
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Layout</span><h2>Grid</h2><p>Arrange arbitrary content in a responsive configurable CSS grid with stable columns, gaps, alignment, and width.</p></div>
|
||||
<div class="catalog-card-footer"><span>6 props · 1 slots</span><a href="/components/grid">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/grid">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
@@ -90,7 +90,7 @@ page LayoutShowcase {
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Layout</span><h2>Image</h2><p>Render a responsive image with explicit dimensions, loading behavior, alternative text, sizing, and rounded treatment.</p></div>
|
||||
<div class="catalog-card-footer"><span>9 props · 1 slots</span><a href="/components/image">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>10 props · 1 slots</span><a href="/components/image">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
@@ -120,7 +120,7 @@ page LayoutShowcase {
|
||||
</div>
|
||||
<div class="catalog-card-body">
|
||||
<div><span class="catalog-card-category">Layout</span><h2>Link</h2><p>Render an accessible internal or external link with target, relation, size, color, and public focus or click events.</p></div>
|
||||
<div class="catalog-card-footer"><span>8 props · 1 slots</span><a href="/components/link">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
<div class="catalog-card-footer"><span>9 props · 1 slots</span><a href="/components/link">Explore component <span aria-hidden="true">→</span></a></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="catalog-card" data-interactive-preview="false">
|
||||
|
||||
@@ -358,7 +358,7 @@
|
||||
"category": "layout",
|
||||
"purpose": "Constrain and align page content with responsive gutters and compact, wide, or full width options.",
|
||||
"demoCount": 3,
|
||||
"propCount": 6,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": [],
|
||||
"profiled": true
|
||||
@@ -463,7 +463,7 @@
|
||||
"category": "layout",
|
||||
"purpose": "Separate related horizontal or vertical content with optional labels, sizes, and semantic colors.",
|
||||
"demoCount": 3,
|
||||
"propCount": 5,
|
||||
"propCount": 6,
|
||||
"slots": [],
|
||||
"events": [],
|
||||
"profiled": true
|
||||
@@ -595,7 +595,7 @@
|
||||
"category": "layout",
|
||||
"purpose": "Arrange arbitrary content in a responsive configurable CSS grid with stable columns, gaps, alignment, and width.",
|
||||
"demoCount": 3,
|
||||
"propCount": 6,
|
||||
"propCount": 7,
|
||||
"slots": ["default"],
|
||||
"events": [],
|
||||
"profiled": true
|
||||
@@ -631,7 +631,7 @@
|
||||
"category": "layout",
|
||||
"purpose": "Render a responsive image with explicit dimensions, loading behavior, alternative text, sizing, and rounded treatment.",
|
||||
"demoCount": 3,
|
||||
"propCount": 9,
|
||||
"propCount": 10,
|
||||
"slots": ["default"],
|
||||
"events": [],
|
||||
"profiled": true
|
||||
@@ -715,7 +715,7 @@
|
||||
"category": "layout",
|
||||
"purpose": "Render an accessible internal or external link with target, relation, size, color, and public focus or click events.",
|
||||
"demoCount": 3,
|
||||
"propCount": 8,
|
||||
"propCount": 9,
|
||||
"slots": ["default"],
|
||||
"events": [],
|
||||
"profiled": true
|
||||
|
||||
@@ -624,7 +624,7 @@ Create responsive balanced content columns with configurable count, gap, density
|
||||
Constrain and align page content with responsive gutters and compact, wide, or full width options.
|
||||
|
||||
- Mount: `data-component="Container"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `class: string = ""`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `centered: boolean = true`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: None
|
||||
|
||||
@@ -642,7 +642,7 @@ Theme-aware, responsive custom scrollbar component.
|
||||
Separate related horizontal or vertical content with optional labels, sizes, and semantic colors.
|
||||
|
||||
- Mount: `data-component="Divider"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = ""`, `orientation: string = "horizontal"`, `class: string = ""`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = ""`, `orientation: string = "horizontal"`, `variant: string = "solid"`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Outputs: None
|
||||
|
||||
@@ -669,7 +669,7 @@ Render structured responsive footer navigation, pre and post content, copyright
|
||||
Arrange arbitrary content in a responsive configurable CSS grid with stable columns, gaps, alignment, and width.
|
||||
|
||||
- Mount: `data-component="Grid"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `class: string = ""`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `columns: number = 2`, `gap: string = "md"`, `maxWidth: string = "xl"`, `minItemWidth: string = ""`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: None
|
||||
|
||||
@@ -678,7 +678,7 @@ Arrange arbitrary content in a responsive configurable CSS grid with stable colu
|
||||
Render a responsive image with explicit dimensions, loading behavior, alternative text, sizing, and rounded treatment.
|
||||
|
||||
- Mount: `data-component="Image"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `src: string = ""`, `alt: string = ""`, `width: string = ""`, `height: string = ""`, `loading: string = "lazy"`, `rounded: boolean = false`, `class: string = ""`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `src: string = ""`, `alt: string = ""`, `width: string = ""`, `height: string = ""`, `loading: string = "lazy"`, `fit: string = "cover"`, `rounded: boolean = false`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: None
|
||||
|
||||
@@ -687,7 +687,7 @@ Render a responsive image with explicit dimensions, loading behavior, alternativ
|
||||
Theme-aware, responsive kbd component.
|
||||
|
||||
- Mount: `data-component="Kbd"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "⌘ K"`, `class: string = ""`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "K"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: None
|
||||
|
||||
@@ -705,7 +705,7 @@ Theme-aware, responsive layout splitter component.
|
||||
Render an accessible internal or external link with target, relation, size, color, and public focus or click events.
|
||||
|
||||
- Mount: `data-component="Link"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Link"`, `href: string = "#"`, `target: string = ""`, `rel: string = ""`, `external: boolean = false`, `class: string = ""`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Link"`, `href: string = "#"`, `target: string = ""`, `rel: string = ""`, `underline: string = "hover"`, `external: boolean = false`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: None
|
||||
|
||||
|
||||
@@ -4153,6 +4153,13 @@
|
||||
"default": "\"xl\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "centered",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
@@ -5194,6 +5201,13 @@
|
||||
"default": "\"horizontal\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "variant",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"solid\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
@@ -6550,6 +6564,13 @@
|
||||
"default": "\"xl\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "minItemWidth",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
@@ -6964,6 +6985,13 @@
|
||||
"default": "\"lazy\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "fit",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"cover\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "rounded",
|
||||
"type": "boolean",
|
||||
@@ -7752,7 +7780,7 @@
|
||||
"name": "label",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"⌘ K\"",
|
||||
"default": "\"K\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
@@ -7952,6 +7980,13 @@
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "underline",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"hover\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "external",
|
||||
"type": "boolean",
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
// Columns -- a simple multi-column split for page content.
|
||||
//
|
||||
// <Columns columns={2} gap="lg">...</Columns>
|
||||
//
|
||||
// Columns and Grid overlap deliberately: Columns is the coarse two or three
|
||||
// way split of a page, and its gaps are wider for that reason. Grid is the one
|
||||
// to reach for when the items are cards and the count matters.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
component Columns {
|
||||
props {
|
||||
size: string = "default"
|
||||
@@ -8,28 +18,92 @@ component Columns {
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
functions {
|
||||
shared function columnCount() {
|
||||
var value = Number(columns)
|
||||
if (!value || value < 1) {
|
||||
return 1
|
||||
}
|
||||
return Math.min(4, value)
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Columns"
|
||||
class='wire-columns {class}'
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-columns='{columns}'
|
||||
data-gap='{gap}'
|
||||
class='grid w-full grid-cols-1 items-start {class}'
|
||||
class:max-w-3xl='maxWidth === "md"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl"'
|
||||
class:max-w-screen-2xl='maxWidth === "2xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
class:md:grid-cols-2='columns >= 2'
|
||||
class:lg:grid-cols-3='columns === 3'
|
||||
class:lg:grid-cols-4='columns === 4'
|
||||
class:gap-3='gap === "sm"'
|
||||
class:gap-6='gap === "md"'
|
||||
class:gap-10='gap === "lg"'
|
||||
class:gap-14='gap === "xl"'
|
||||
data-max-width='{maxWidth}'
|
||||
data-columns='{columnCount()}'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-columns {
|
||||
--columns-gap: 1.5rem;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
gap: var(--columns-gap);
|
||||
grid-template-columns: 1fr;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.wire-columns[data-max-width="md"] {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.wire-columns[data-max-width="lg"] {
|
||||
max-width: 64rem;
|
||||
}
|
||||
|
||||
.wire-columns[data-max-width="xl"] {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
.wire-columns[data-max-width="2xl"] {
|
||||
max-width: 96rem;
|
||||
}
|
||||
|
||||
.wire-columns[data-max-width="full"] {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.wire-columns[data-gap="sm"] {
|
||||
--columns-gap: 0.75rem;
|
||||
}
|
||||
|
||||
.wire-columns[data-gap="lg"] {
|
||||
--columns-gap: 2.5rem;
|
||||
}
|
||||
|
||||
.wire-columns[data-gap="xl"] {
|
||||
--columns-gap: 3.5rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Splits at the tablet breakpoint rather than the phone one: a page split
|
||||
* is worth keeping single-column for longer than a card deck is.
|
||||
*/
|
||||
@media (min-width: 768px) {
|
||||
.wire-columns:not([data-columns="1"]) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.wire-columns[data-columns="3"] {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-columns[data-columns="4"] {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
// Image -- a framed image with a reserved aspect ratio.
|
||||
//
|
||||
// <Image src="/hero.jpg" alt="Dashboard" size="video" rounded={true} />
|
||||
//
|
||||
// The frame keeps its ratio whether or not the image has loaded, so the page
|
||||
// does not jump when it arrives. Without a src it renders a labelled
|
||||
// placeholder of the same shape rather than collapsing.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
component Image {
|
||||
props {
|
||||
size: string = "default"
|
||||
@@ -7,41 +17,111 @@ component Image {
|
||||
width: string = ""
|
||||
height: string = ""
|
||||
loading: string = "lazy"
|
||||
fit: string = "cover"
|
||||
rounded: boolean = false
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<figure
|
||||
{...attrs}
|
||||
data-ui-component="Image"
|
||||
class='relative m-0 overflow-hidden bg-[var(--wire-color-surface-soft)] {class}'
|
||||
class:rounded-2xl='rounded'
|
||||
class='wire-image {class}'
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-fit='{fit}'
|
||||
data-rounded='{rounded}'
|
||||
>
|
||||
{#if src}
|
||||
<img
|
||||
class="wire-image__img"
|
||||
data-show="src"
|
||||
src='{src}'
|
||||
alt='{alt}'
|
||||
width='{width}'
|
||||
height='{height}'
|
||||
loading='{loading}'
|
||||
decoding="async"
|
||||
class="block h-auto w-full object-cover transition duration-300"
|
||||
class:aspect-square='size === "square"'
|
||||
class:aspect-video='size === "video"'
|
||||
class:aspect-[4/3]='size === "landscape"'
|
||||
class:aspect-[3/4]='size === "portrait"'
|
||||
/>
|
||||
{:else}
|
||||
|
||||
<div
|
||||
class="flex min-h-48 w-full items-center justify-center text-[var(--wire-color-text-muted)]"
|
||||
class="wire-image__placeholder"
|
||||
data-show="!src"
|
||||
role="img"
|
||||
aria-label='{alt || "Image placeholder"}'
|
||||
>
|
||||
<span class="icon-[lucide--image] size-8" aria-hidden="true"></span>
|
||||
<span class="icon-[lucide--image] wire-image__placeholder-icon" aria-hidden="true"></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
</figure>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-image {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
background: var(--wire-color-surface-soft);
|
||||
}
|
||||
|
||||
.wire-image[data-rounded="true"] {
|
||||
border-radius: var(--wire-radius-lg);
|
||||
}
|
||||
|
||||
/*
|
||||
* The ratio sits on the frame rather than the image, so the space is
|
||||
* reserved before the file arrives and the page does not jump.
|
||||
*/
|
||||
.wire-image[data-size="square"] {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.wire-image[data-size="video"] {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
.wire-image[data-size="landscape"] {
|
||||
aspect-ratio: 4 / 3;
|
||||
}
|
||||
|
||||
.wire-image[data-size="portrait"] {
|
||||
aspect-ratio: 3 / 4;
|
||||
}
|
||||
|
||||
.wire-image__img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.wire-image[data-fit="contain"] .wire-image__img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.wire-image[data-fit="fill"] .wire-image__img {
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.wire-image[data-size="default"] .wire-image__img {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.wire-image__placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 12rem;
|
||||
height: 100%;
|
||||
color: var(--wire-color-text-muted);
|
||||
}
|
||||
|
||||
.wire-image__placeholder-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,55 @@
|
||||
// Kbd -- a keyboard key.
|
||||
//
|
||||
// <Kbd label="Ctrl" />
|
||||
//
|
||||
// Deliberately small. A kbd element is a kbd element; the work here is making
|
||||
// it look like a key and follow the theme.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
component Kbd {
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
label: string = "⌘ K"
|
||||
label: string = "K"
|
||||
class: string = ""
|
||||
}
|
||||
view { <kbd class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--kbd {class}"><slot>{label}</slot></kbd> }
|
||||
|
||||
view {
|
||||
<kbd {...attrs} class='wire-kbd {class}' data-size='{size}' data-color='{color}'>
|
||||
<slot>{label}</slot>
|
||||
</kbd>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-kbd {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1.6rem;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
/* The lower edge is what reads as a physical key. */
|
||||
border-bottom-width: 2px;
|
||||
border-radius: var(--wire-radius-sm);
|
||||
background: var(--wire-color-surface-soft);
|
||||
color: var(--wire-color-text);
|
||||
font-family: inherit;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.wire-kbd[data-size="sm"] {
|
||||
min-width: 1.4rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.wire-kbd[data-size="lg"] {
|
||||
min-width: 2rem;
|
||||
padding: 0.3rem 0.55rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+111
-21
@@ -1,3 +1,13 @@
|
||||
// Link -- a text link that follows the theme.
|
||||
//
|
||||
// <Link href="/docs" label="Documentation" />
|
||||
// <Link href="https://example.com" label="Spec" external={true} />
|
||||
//
|
||||
// external adds rel="noopener noreferrer" and an outbound icon, so the reader
|
||||
// is told the link leaves the site before they follow it.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
component Link {
|
||||
props {
|
||||
size: string = "default"
|
||||
@@ -6,42 +16,122 @@ component Link {
|
||||
href: string = "#"
|
||||
target: string = ""
|
||||
rel: string = ""
|
||||
underline: string = "hover"
|
||||
external: boolean = false
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
view {
|
||||
<a
|
||||
{...attrs}
|
||||
data-ui-component="Link"
|
||||
class='wire-link {class}'
|
||||
href='{href}'
|
||||
target='{target}'
|
||||
rel='{external ? (rel || "noopener noreferrer") : rel}'
|
||||
class='inline-flex min-w-0 items-center gap-1.5 rounded-md font-semibold underline-offset-4 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wire-color-background)] {class}'
|
||||
class:text-xs='size === "xs"'
|
||||
class:text-sm='size === "sm" || size === "default"'
|
||||
class:text-base='size === "md"'
|
||||
class:text-lg='size === "lg"'
|
||||
class:text-[var(--wire-color-primary)]='color === "primary"'
|
||||
class:hover:text-[var(--wire-color-primary-hover)]='color === "primary"'
|
||||
class:text-[var(--wire-color-secondary)]='color === "secondary"'
|
||||
class:hover:text-[var(--wire-color-secondary-hover)]='color === "secondary"'
|
||||
class:text-[var(--wire-color-success)]='color === "success"'
|
||||
class:text-[var(--wire-color-warning-text)]='color === "warning"'
|
||||
class:text-[var(--wire-color-danger)]='color === "danger"'
|
||||
class:text-[var(--wire-color-info)]='color === "info"'
|
||||
class:text-[var(--wire-color-text)]='color === "neutral"'
|
||||
class:hover:underline='external === false'
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-underline='{underline}'
|
||||
data-external='{external}'
|
||||
>
|
||||
<span class="truncate">{label}</span>
|
||||
|
||||
{#if external}
|
||||
<span class="wire-link__label">{label}</span>
|
||||
<span
|
||||
class="icon-[lucide--external-link] size-3.5 shrink-0"
|
||||
class="icon-[lucide--external-link] wire-link__icon"
|
||||
data-show="external"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{/if}
|
||||
|
||||
<slot></slot>
|
||||
</a>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-link {
|
||||
--link-tone: var(--wire-color-primary);
|
||||
--link-tone-hover: var(--wire-color-primary-hover);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
border-radius: var(--wire-radius-sm);
|
||||
color: var(--link-tone);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
text-underline-offset: 4px;
|
||||
transition: color 140ms ease;
|
||||
}
|
||||
|
||||
.wire-link[data-color="secondary"] {
|
||||
--link-tone: var(--wire-color-secondary);
|
||||
--link-tone-hover: var(--wire-color-secondary-hover);
|
||||
}
|
||||
|
||||
.wire-link[data-color="success"] {
|
||||
--link-tone: var(--wire-color-success);
|
||||
--link-tone-hover: var(--wire-color-success);
|
||||
}
|
||||
|
||||
.wire-link[data-color="warning"] {
|
||||
--link-tone: var(--wire-color-warning-text);
|
||||
--link-tone-hover: var(--wire-color-warning-text);
|
||||
}
|
||||
|
||||
.wire-link[data-color="danger"] {
|
||||
--link-tone: var(--wire-color-danger);
|
||||
--link-tone-hover: var(--wire-color-danger);
|
||||
}
|
||||
|
||||
.wire-link[data-color="info"] {
|
||||
--link-tone: var(--wire-color-info);
|
||||
--link-tone-hover: var(--wire-color-info);
|
||||
}
|
||||
|
||||
.wire-link[data-color="neutral"] {
|
||||
--link-tone: var(--wire-color-text);
|
||||
--link-tone-hover: var(--wire-color-text);
|
||||
}
|
||||
|
||||
.wire-link[data-size="xs"] {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.wire-link[data-size="md"] {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.wire-link[data-size="lg"] {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.wire-link[data-underline="always"] {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.wire-link[data-underline="hover"]:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.wire-link:hover {
|
||||
color: var(--link-tone-hover);
|
||||
}
|
||||
|
||||
.wire-link:focus-visible {
|
||||
outline: 2px solid var(--wire-color-focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* The label truncates; the outbound icon must not shrink with it. */
|
||||
.wire-link__label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wire-link__icon {
|
||||
flex: 0 0 auto;
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,26 +11,74 @@ component Typography {
|
||||
view {
|
||||
<div
|
||||
data-ui-component="Typography"
|
||||
class='wire-typography text-[var(--wire-color-text)] {class}'
|
||||
class:max-w-3xl='maxWidth === "md"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
class:text-sm='size === "sm"'
|
||||
class:text-base='size === "default" || size === "md"'
|
||||
class:text-lg='size === "lg"'
|
||||
class:columns-1='columns === 1'
|
||||
class:md:columns-2='columns === 2'
|
||||
class:lg:columns-3='columns === 3'
|
||||
class:gap-4='gap === "sm"'
|
||||
class:gap-8='gap === "md"'
|
||||
class:gap-12='gap === "lg"'
|
||||
class='wire-typography {class}'
|
||||
data-size='{size}'
|
||||
data-columns='{columns}'
|
||||
data-gap='{gap}'
|
||||
data-max-width='{maxWidth}'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-typography {
|
||||
color: var(--wire-color-text);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.wire-typography[data-size="sm"] {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.wire-typography[data-size="lg"] {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.wire-typography[data-max-width="md"] {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.wire-typography[data-max-width="lg"] {
|
||||
max-width: 64rem;
|
||||
}
|
||||
|
||||
.wire-typography[data-max-width="xl"] {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
.wire-typography[data-max-width="full"] {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Multi-column prose only below a comfortable reading width; a narrow
|
||||
* screen split into columns is unreadable.
|
||||
*/
|
||||
.wire-typography[data-gap="sm"] {
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.wire-typography[data-gap="md"] {
|
||||
column-gap: 2rem;
|
||||
}
|
||||
|
||||
.wire-typography[data-gap="lg"] {
|
||||
column-gap: 3rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.wire-typography[data-columns="2"] {
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.wire-typography[data-columns="3"] {
|
||||
column-count: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.wire-typography :where(h1, h2, h3, h4) {
|
||||
color: var(--wire-color-text);
|
||||
font-weight: 700;
|
||||
|
||||
@@ -1,46 +1,162 @@
|
||||
// Container -- a centred, width-limited page wrapper.
|
||||
//
|
||||
// <Container maxWidth="lg">...</Container>
|
||||
//
|
||||
// It also accepts columns and gap, because it always has and applications
|
||||
// depend on it. Reach for Grid when a grid is the point; reach for Container
|
||||
// when the point is the reading width.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
component Container {
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
// Stays 2 to match what shipped. A published default cannot be changed
|
||||
// without silently reflowing every Container already in use.
|
||||
columns: number = 2
|
||||
gap: string = "md"
|
||||
maxWidth: string = "xl"
|
||||
centered: boolean = true
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
functions {
|
||||
shared function columnCount() {
|
||||
var value = Number(columns)
|
||||
if (!value || value < 1) {
|
||||
return 1
|
||||
}
|
||||
return Math.min(6, value)
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Container"
|
||||
class='wire-container {class}'
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-columns='{columns}'
|
||||
data-gap='{gap}'
|
||||
data-max-width='{maxWidth}'
|
||||
class='mx-auto w-full {class}'
|
||||
class:max-w-3xl='maxWidth === "md"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl" || maxWidth === "wide"'
|
||||
class:max-w-screen-2xl='maxWidth === "2xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
class:px-4='size === "compact" || size === "default"'
|
||||
class:px-5='size === "comfortable"'
|
||||
class:px-6='size === "spacious"'
|
||||
class:sm:px-6='size !== "compact"'
|
||||
class:lg:px-8='size === "default" || size === "comfortable" || size === "spacious"'
|
||||
class:grid='columns > 1'
|
||||
class:grid-cols-1='columns > 1'
|
||||
class:sm:grid-cols-2='columns === 2 || columns === 3 || columns === 4 || columns === 5 || columns === 6'
|
||||
class:lg:grid-cols-3='columns === 3'
|
||||
class:lg:grid-cols-4='columns === 4'
|
||||
class:lg:grid-cols-5='columns === 5'
|
||||
class:lg:grid-cols-6='columns === 6'
|
||||
class:gap-2='gap === "xs"'
|
||||
class:gap-3='gap === "sm"'
|
||||
class:gap-5='gap === "md"'
|
||||
class:gap-8='gap === "lg"'
|
||||
class:gap-10='gap === "xl"'
|
||||
data-centered='{centered}'
|
||||
data-columns='{columnCount()}'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-container {
|
||||
--container-gap: 1.25rem;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
.wire-container[data-centered="true"] {
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Reading widths, not breakpoints: the cap is about line length. */
|
||||
.wire-container[data-max-width="md"] {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.wire-container[data-max-width="lg"] {
|
||||
max-width: 64rem;
|
||||
}
|
||||
|
||||
.wire-container[data-max-width="xl"],
|
||||
.wire-container[data-max-width="wide"] {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
.wire-container[data-max-width="2xl"] {
|
||||
max-width: 96rem;
|
||||
}
|
||||
|
||||
.wire-container[data-max-width="full"] {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.wire-container[data-size="compact"] {
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
.wire-container[data-size="comfortable"] {
|
||||
padding-inline: 1.25rem;
|
||||
}
|
||||
|
||||
.wire-container[data-size="spacious"] {
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
.wire-container[data-gap="xs"] {
|
||||
--container-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.wire-container[data-gap="sm"] {
|
||||
--container-gap: 0.75rem;
|
||||
}
|
||||
|
||||
.wire-container[data-gap="lg"] {
|
||||
--container-gap: 2rem;
|
||||
}
|
||||
|
||||
.wire-container[data-gap="xl"] {
|
||||
--container-gap: 2.5rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Columns are opt-in. One column stays plain flow so a container does not
|
||||
* quietly turn every page into a grid.
|
||||
*/
|
||||
.wire-container:not([data-columns="1"]) {
|
||||
display: grid;
|
||||
gap: var(--container-gap);
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.wire-container[data-columns="2"],
|
||||
.wire-container[data-columns="3"],
|
||||
.wire-container[data-columns="4"],
|
||||
.wire-container[data-columns="5"],
|
||||
.wire-container[data-columns="6"] {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-container[data-size="default"],
|
||||
.wire-container[data-size="comfortable"],
|
||||
.wire-container[data-size="spacious"] {
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.wire-container[data-columns="3"] {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-container[data-columns="4"] {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-container[data-columns="5"] {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-container[data-columns="6"] {
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-container[data-size="default"],
|
||||
.wire-container[data-size="comfortable"],
|
||||
.wire-container[data-size="spacious"] {
|
||||
padding-inline: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +1,141 @@
|
||||
// Divider -- a rule between sections, optionally labelled.
|
||||
//
|
||||
// <Divider />
|
||||
// <Divider label="or" />
|
||||
// <Divider orientation="vertical" />
|
||||
//
|
||||
// The label sits between two rules rather than on top of one, so the text
|
||||
// never overlaps the line at any width.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
component Divider {
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
label: string = ""
|
||||
orientation: string = "horizontal"
|
||||
variant: string = "solid"
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
functions {
|
||||
shared function isVertical() {
|
||||
return orientation === "vertical"
|
||||
}
|
||||
|
||||
// The rule only takes the component colour when there is a label. A plain
|
||||
// divider is chrome and should stay the border token.
|
||||
shared function tone() {
|
||||
return label ? color : "border"
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Divider"
|
||||
class='wire-divider {class}'
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-tone='{tone()}'
|
||||
data-variant='{variant}'
|
||||
data-orientation='{orientation}'
|
||||
class='w-full {class}'
|
||||
class:h-full='orientation === "vertical"'
|
||||
class:w-auto='orientation === "vertical"'
|
||||
>
|
||||
{#if orientation === "vertical"}
|
||||
<div
|
||||
data-labelled='{label ? "true" : "false"}'
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
class="mx-3 h-full min-h-6 border-l border-[var(--wire-color-border)]"
|
||||
class:border-l-2='size === "lg"'
|
||||
class:border-[var(--wire-color-primary)]='color === "primary"'
|
||||
class:border-[var(--wire-color-secondary)]='color === "secondary"'
|
||||
class:border-[var(--wire-color-success)]='color === "success"'
|
||||
class:border-[var(--wire-color-warning)]='color === "warning"'
|
||||
class:border-[var(--wire-color-danger)]='color === "danger"'
|
||||
></div>
|
||||
{:else}
|
||||
<div
|
||||
role="separator"
|
||||
aria-orientation="horizontal"
|
||||
class="flex w-full items-center gap-4"
|
||||
aria-orientation='{isVertical() ? "vertical" : "horizontal"}'
|
||||
>
|
||||
<span
|
||||
class="h-px flex-1 bg-[var(--wire-color-border)]"
|
||||
class:h-0.5='size === "lg"'
|
||||
class:bg-[var(--wire-color-primary)]='color === "primary" && label !== ""'
|
||||
class:bg-[var(--wire-color-secondary)]='color === "secondary" && label !== ""'
|
||||
class:bg-[var(--wire-color-success)]='color === "success" && label !== ""'
|
||||
class:bg-[var(--wire-color-warning)]='color === "warning" && label !== ""'
|
||||
class:bg-[var(--wire-color-danger)]='color === "danger" && label !== ""'
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
|
||||
{#if label}
|
||||
<span class="shrink-0 text-xs font-bold uppercase tracking-[0.16em] text-[var(--wire-color-text-muted)]">
|
||||
{label}
|
||||
</span>
|
||||
<span
|
||||
class="h-px flex-1 bg-[var(--wire-color-border)]"
|
||||
class:h-0.5='size === "lg"'
|
||||
class:bg-[var(--wire-color-primary)]='color === "primary"'
|
||||
class:bg-[var(--wire-color-secondary)]='color === "secondary"'
|
||||
class:bg-[var(--wire-color-success)]='color === "success"'
|
||||
class:bg-[var(--wire-color-warning)]='color === "warning"'
|
||||
class:bg-[var(--wire-color-danger)]='color === "danger"'
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<span class="wire-divider__rule" aria-hidden="true"></span>
|
||||
<span class="wire-divider__label" data-show="label">{label}</span>
|
||||
<span class="wire-divider__rule" data-show="label" aria-hidden="true"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-divider {
|
||||
--divider-tone: var(--wire-color-border);
|
||||
--divider-thickness: 1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wire-divider[data-tone="primary"] {
|
||||
--divider-tone: var(--wire-color-primary);
|
||||
}
|
||||
|
||||
.wire-divider[data-tone="secondary"] {
|
||||
--divider-tone: var(--wire-color-secondary);
|
||||
}
|
||||
|
||||
.wire-divider[data-tone="success"] {
|
||||
--divider-tone: var(--wire-color-success);
|
||||
}
|
||||
|
||||
.wire-divider[data-tone="warning"] {
|
||||
--divider-tone: var(--wire-color-warning);
|
||||
}
|
||||
|
||||
.wire-divider[data-tone="danger"] {
|
||||
--divider-tone: var(--wire-color-danger);
|
||||
}
|
||||
|
||||
.wire-divider[data-size="lg"] {
|
||||
--divider-thickness: 2px;
|
||||
}
|
||||
|
||||
.wire-divider__rule {
|
||||
flex: 1 1 auto;
|
||||
height: var(--divider-thickness);
|
||||
background: var(--divider-tone);
|
||||
}
|
||||
|
||||
.wire-divider[data-variant="dashed"] .wire-divider__rule {
|
||||
height: 0;
|
||||
background: transparent;
|
||||
border-top: var(--divider-thickness) dashed var(--divider-tone);
|
||||
}
|
||||
|
||||
.wire-divider[data-variant="dotted"] .wire-divider__rule {
|
||||
height: 0;
|
||||
background: transparent;
|
||||
border-top: var(--divider-thickness) dotted var(--divider-tone);
|
||||
}
|
||||
|
||||
.wire-divider__label {
|
||||
flex: 0 0 auto;
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wire-divider[data-orientation="vertical"] {
|
||||
flex-direction: column;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
min-height: 1.5rem;
|
||||
margin-inline: 0.75rem;
|
||||
}
|
||||
|
||||
.wire-divider[data-orientation="vertical"] .wire-divider__rule {
|
||||
width: var(--divider-thickness);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.wire-divider[data-orientation="vertical"][data-variant="dashed"] .wire-divider__rule {
|
||||
width: 0;
|
||||
border-top: 0;
|
||||
border-left: var(--divider-thickness) dashed var(--divider-tone);
|
||||
}
|
||||
|
||||
.wire-divider[data-orientation="vertical"][data-variant="dotted"] .wire-divider__rule {
|
||||
width: 0;
|
||||
border-top: 0;
|
||||
border-left: var(--divider-thickness) dotted var(--divider-tone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+117
-19
@@ -1,3 +1,13 @@
|
||||
// Grid -- an equal-track grid that collapses on small screens.
|
||||
//
|
||||
// <Grid columns={3} gap="lg">...</Grid>
|
||||
//
|
||||
// Pass minItemWidth to let the browser decide the count instead: the track
|
||||
// list becomes auto-fit, which reflows continuously rather than at fixed
|
||||
// breakpoints and suits card decks whose item count is not known.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
component Grid {
|
||||
props {
|
||||
size: string = "default"
|
||||
@@ -5,36 +15,124 @@ component Grid {
|
||||
columns: number = 2
|
||||
gap: string = "md"
|
||||
maxWidth: string = "xl"
|
||||
// When set, the count is derived from the available width instead of the
|
||||
// columns prop. Accepts any CSS length.
|
||||
minItemWidth: string = ""
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
functions {
|
||||
shared function columnCount() {
|
||||
var value = Number(columns)
|
||||
if (!value || value < 1) {
|
||||
return 1
|
||||
}
|
||||
return Math.min(6, value)
|
||||
}
|
||||
|
||||
shared function autoTrack() {
|
||||
return minItemWidth ? "repeat(auto-fit, minmax(" + minItemWidth + ", 1fr))" : ""
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Grid"
|
||||
class='wire-grid {class}'
|
||||
data-size='{size}'
|
||||
data-color='{color}'
|
||||
data-columns='{columns}'
|
||||
data-gap='{gap}'
|
||||
class='grid w-full grid-cols-1 {class}'
|
||||
class:max-w-3xl='maxWidth === "md"'
|
||||
class:max-w-5xl='maxWidth === "lg"'
|
||||
class:max-w-7xl='maxWidth === "xl"'
|
||||
class:max-w-screen-2xl='maxWidth === "2xl"'
|
||||
class:max-w-none='maxWidth === "full"'
|
||||
class:sm:grid-cols-2='columns >= 2'
|
||||
class:lg:grid-cols-3='columns === 3'
|
||||
class:lg:grid-cols-4='columns === 4'
|
||||
class:lg:grid-cols-5='columns === 5'
|
||||
class:lg:grid-cols-6='columns === 6'
|
||||
class:gap-2='gap === "xs"'
|
||||
class:gap-3='gap === "sm"'
|
||||
class:gap-5='gap === "md"'
|
||||
class:gap-8='gap === "lg"'
|
||||
class:gap-10='gap === "xl"'
|
||||
class:items-start='size === "compact"'
|
||||
class:items-stretch='size !== "compact"'
|
||||
data-max-width='{maxWidth}'
|
||||
data-columns='{columnCount()}'
|
||||
data-auto='{minItemWidth ? "true" : "false"}'
|
||||
style='--grid-auto-track:{autoTrack()};'
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-grid {
|
||||
--grid-gap: 1.25rem;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
gap: var(--grid-gap);
|
||||
grid-template-columns: 1fr;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.wire-grid[data-size="compact"] {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.wire-grid[data-max-width="md"] {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
||||
.wire-grid[data-max-width="lg"] {
|
||||
max-width: 64rem;
|
||||
}
|
||||
|
||||
.wire-grid[data-max-width="xl"] {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
.wire-grid[data-max-width="2xl"] {
|
||||
max-width: 96rem;
|
||||
}
|
||||
|
||||
.wire-grid[data-max-width="full"] {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.wire-grid[data-gap="xs"] {
|
||||
--grid-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.wire-grid[data-gap="sm"] {
|
||||
--grid-gap: 0.75rem;
|
||||
}
|
||||
|
||||
.wire-grid[data-gap="lg"] {
|
||||
--grid-gap: 2rem;
|
||||
}
|
||||
|
||||
.wire-grid[data-gap="xl"] {
|
||||
--grid-gap: 2.5rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* auto-fit needs no breakpoints, so it wins over the fixed counts below
|
||||
* and applies at every width.
|
||||
*/
|
||||
.wire-grid[data-auto="true"] {
|
||||
grid-template-columns: var(--grid-auto-track);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.wire-grid[data-auto="false"]:not([data-columns="1"]) {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.wire-grid[data-auto="false"][data-columns="3"] {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-grid[data-auto="false"][data-columns="4"] {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-grid[data-auto="false"][data-columns="5"] {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.wire-grid[data-auto="false"][data-columns="6"] {
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3019,3 +3019,42 @@ test("custom scrollbar styles the scrollbar and drops its fake output", async ()
|
||||
expect(style).toContain("--scrollbar-thickness");
|
||||
expect(style).toContain("18rem");
|
||||
});
|
||||
|
||||
test("layout components ship their own styles instead of Tailwind utilities", () => {
|
||||
/*
|
||||
* These were built from utility classes and class: conditionals. That works
|
||||
* only where Tailwind is present, and a variant cost a dozen lines of
|
||||
* conditionals. They now carry wire-* classes, a local style block, and
|
||||
* data attributes the style block selects on.
|
||||
*/
|
||||
const migrated = [
|
||||
"Container",
|
||||
"Columns",
|
||||
"Grid",
|
||||
"Divider",
|
||||
"Image",
|
||||
"Link",
|
||||
"Typography",
|
||||
"Kbd",
|
||||
"LayoutSplitter",
|
||||
"CustomScrollbar",
|
||||
];
|
||||
const utility =
|
||||
/class:(grid-cols-|sm:|lg:|md:|max-w-|gap-[0-9]|px-[0-9]|py-[0-9]|border-l|bg-\[|h-0|text-xs|items-)/;
|
||||
|
||||
for (const name of migrated) {
|
||||
const source = readFileSync(uiComponentPath(name), "utf8");
|
||||
expect({ name, hasStyleBlock: /^\s{2}style \{/m.test(source) }).toEqual({
|
||||
name,
|
||||
hasStyleBlock: true,
|
||||
});
|
||||
expect({ name, usesUtilityConditionals: utility.test(source) }).toEqual({
|
||||
name,
|
||||
usesUtilityConditionals: false,
|
||||
});
|
||||
expect({ name, hasWireClass: /class='wire-[a-z-]+/.test(source) }).toEqual({
|
||||
name,
|
||||
hasWireClass: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user