Adding spinner

This commit is contained in:
Ronan Berder
2025-11-03 13:51:40 +08:00
parent 0c16664e4b
commit e5aed697c8
11 changed files with 365 additions and 348 deletions
+2 -2
View File
@@ -225,7 +225,7 @@ toc:
<h3 id="example-dialog"><a href="#example-dialog">Dialog</a></h3>
<section class="prose">
<p>You can use some simple JavaScript to open the command menu with a keyboard shortcut (in this case <kbd>⌘J</kbd>).</p>
<p>You can use some simple JavaScript to open the command menu with a keyboard shortcut (in this case <kbd class="kbd">⌘J</kbd>).</p>
</section>
{% set code_dialog %}
@@ -235,7 +235,7 @@ toc:
onclick="document.getElementById('demo-command-dialog').showModal()"
>
Open command menu
<kbd>⌘J</kbd>
<kbd class="kbd">⌘J</kbd>
</button>
{% call command_dialog(id="demo-command-dialog") %}
+3 -55
View File
@@ -5,9 +5,6 @@ description: Display empty states with icons, titles, descriptions, and actions.
toc:
- label: Usage
id: usage
children:
- label: HTML structure
id: html-structure
- label: Examples
id: examples
children:
@@ -58,57 +55,8 @@ toc:
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>The example simply uses Tailwind utility classes to create an empty state layout.</p>
</section>
{{ code_block(code_default) }}
<h3 id="html-structure"><a href="#html-structure">HTML structure</a></h3>
<section class="prose">
<dl>
<dt><code class="highlight language-html">&lt;div&gt;</code></dt>
<dd>Main container with centered layout utilities:
<ul>
<li><code>flex flex-col items-center justify-center</code>: centers content</li>
<li><code>gap-6</code>: spacing between sections</li>
<li><code>p-6 md:p-12</code>: responsive padding</li>
<li><code>text-center text-balance</code>: centered text with better wrapping</li>
</ul>
<dl>
<dt><code class="highlight language-html">&lt;header&gt;</code></dt>
<dd>Header section containing the icon/avatar, title, and description:
<ul>
<li><code>max-w-sm</code>: constrains width for readability</li>
<li><code>gap-2</code> or <code>gap-3</code>: spacing between elements</li>
</ul>
<dl>
<dt><code class="highlight language-html">&lt;div&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Media container for icon or avatar:
<ul>
<li>For icons: <code>bg-muted text-foreground size-10 rounded-lg</code></li>
<li>For avatars: <code>size-12 rounded-full grayscale</code></li>
<li>For avatar groups: <code>flex -space-x-2</code> with ring utilities</li>
</ul>
</dd>
<dt><code class="highlight language-html">&lt;h3&gt;</code></dt>
<dd>Title with <code>text-lg font-medium tracking-tight</code></dd>
<dt><code class="highlight language-html">&lt;p&gt;</code></dt>
<dd>Description with <code>text-muted-foreground text-sm/relaxed</code></dd>
</dl>
</dd>
<dt><code class="highlight language-html">&lt;section&gt;</code> <span class="badge-secondary">Optional</span></dt>
<dd>Content area for actions (buttons, inputs, etc.):
<ul>
<li><code>max-w-sm</code> or <code>max-w-md</code>: constrains width</li>
<li><code>flex flex-col items-center gap-3</code>: vertical layout with spacing</li>
</ul>
</dd>
<dt><code class="highlight language-html">&lt;a&gt;</code> or other elements <span class="badge-secondary">Optional</span></dt>
<dd>Additional links or actions outside the main content section</dd>
</dl>
</dd>
</dl>
<p>Empty states are pure HTML composition using Tailwind utility classes. Use flex layout utilities to center content, and compose with semantic elements like <code>&lt;header&gt;</code>, <code>&lt;h3&gt;</code>, <code>&lt;p&gt;</code>, and <code>&lt;button&gt;</code>.</p>
<p>Browse the examples below and copy the pattern that fits your needs.</p>
</section>
<h2 id="examples"><a href="#examples">Examples</a></h2>
@@ -186,7 +134,7 @@ toc:
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
{% set code_input_group %}
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
<h3 class="text-lg font-semibold tracking-tight">404 - Not Found</h3>
<p class="text-muted-foreground text-sm/relaxed">
+20 -9
View File
@@ -173,8 +173,19 @@ toc:
<div class="grid gap-6">
<div class="relative">
<input type="text" readonly class="input pr-9" value="https://x.com/hunvreus">
<button class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-icon-ghost text-muted-foreground hover:text-accent-foreground size-6">{% lucide "copy" %}</button>
<button
data-copied="false"
onclick="
this.dataset.copied = 'true';
setTimeout(() => this.dataset.copied = 'false', 2000);
"
class="group absolute right-1.5 top-1/2 -translate-y-1/2 btn-icon-ghost text-muted-foreground hover:text-accent-foreground size-6"
>
{% lucide "copy", { "class": "group-data-[copied=true]:hidden" } %}
{% lucide "check", { "class": "hidden group-data-[copied=true]:block" } %}
</button>
</div>
<div class="relative">
<input type="text" class="input pl-21 pr-9 rounded-full">
@@ -264,15 +275,15 @@ toc:
<div class="grid gap-6">
<div class="relative">
<input type="text" class="input pr-9" placeholder="Searching..." disabled>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 animate-spin opacity-50">
{% lucide "loader-circle" %}
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
{% lucide "loader-circle", { "class": "animate-spin" } %}
</div>
</div>
<div class="relative">
<input type="text" class="input pl-9" placeholder="Processing..." disabled>
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 animate-spin opacity-50">
{% lucide "loader-circle" %}
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
{% lucide "loader-circle", { "class": "animate-spin" } %}
</div>
</div>
@@ -280,16 +291,16 @@ toc:
<input type="text" class="input pr-24" placeholder="Saving changes..." disabled>
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 text-sm text-muted-foreground opacity-50">
<div>Saving...</div>
<div class="[&>svg]:size-4 animate-spin">
{% lucide "loader-circle" %}
<div class="[&>svg]:size-4">
{% lucide "loader-circle", { "class": "animate-spin" } %}
</div>
</div>
</div>
<div class="relative">
<input type="text" class="input pl-9 pr-26" placeholder="Refreshing data..." disabled>
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 animate-spin opacity-50">
{% lucide "loader" %}
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
{% lucide "loader-circle", { "class": "animate-spin" } %}
</div>
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 text-sm text-muted-foreground opacity-50">
Please wait...
+4 -4
View File
@@ -45,7 +45,7 @@ toc:
</article>
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border py-3 px-4 gap-2.5">
<div class="flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5 bg-transparent [&_svg]:size-5">
<div class="flex shrink-0 items-center justify-center gap-2 [&_svg]:pointer-events-none bg-transparent [&_svg]:size-5">
{% lucide "badge-check" %}
</div>
<div class="flex flex-1 flex-col gap-1">
@@ -174,7 +174,7 @@ toc:
</div>
<div class="flex flex-col gap-1 flex-none text-center">
<p data-slot="item-description" class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">3:45</p>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">3:45</p>
</div>
</a>
@@ -187,7 +187,7 @@ toc:
</div>
<div class="flex flex-col gap-1 flex-none text-center">
<p data-slot="item-description" class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">4:05</p>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">4:05</p>
</div>
</a>
@@ -200,7 +200,7 @@ toc:
</div>
<div class="flex flex-col gap-1 flex-none text-center">
<p data-slot="item-description" class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">3:30</p>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">3:30</p>
</div>
</a>
</div>
+12 -12
View File
@@ -20,15 +20,15 @@ toc:
{% set code_html %}
<div class="flex flex-col items-center gap-4">
<span class="inline-flex items-center gap-1">
<kbd>⌘</kbd>
<kbd>⇧</kbd>
<kbd>⌥</kbd>
<kbd>⌃</kbd>
<kbd class="kbd">⌘</kbd>
<kbd class="kbd">⇧</kbd>
<kbd class="kbd">⌥</kbd>
<kbd class="kbd">⌃</kbd>
</span>
<span class="inline-flex items-center gap-1">
<kbd>Ctrl</kbd>
<kbd class="kbd">Ctrl</kbd>
<span>+</span>
<kbd>B</kbd>
<kbd class="kbd">B</kbd>
</span>
</div>
{% endset %}
@@ -38,11 +38,11 @@ toc:
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Simply use the <code>&lt;kbd&gt;</code> element:</p>
<p>Use the <code>&lt;kbd&gt;</code> element with the <code>kbd</code> class:</p>
</section>
{% set code_simple %}
<kbd>⌘K</kbd>
<kbd class="kbd">⌘K</kbd>
{% endset %}
{{ code_block(code_simple | prettyHtml, "html") }}
@@ -55,8 +55,8 @@ toc:
<p class="text-muted-foreground text-sm">
Use
<span class="inline-flex items-center gap-1">
<kbd>Ctrl + B</kbd>
<kbd>Ctrl + K</kbd>
<kbd class="kbd">Ctrl + B</kbd>
<kbd class="kbd">Ctrl + K</kbd>
</span>
to open the command palette
</p>
@@ -68,8 +68,8 @@ toc:
{% set button_html %}
<div class="flex flex-wrap items-center gap-x-4">
<button class="btn-sm-outline pr-2">Accept <kbd>⏎</kbd></button>
<button class="btn-sm-outline pr-2">Cancel <kbd>Esc</kbd></button>
<button class="btn-sm-outline pr-2">Accept <kbd class="kbd">⏎</kbd></button>
<button class="btn-sm-outline pr-2">Cancel <kbd class="kbd">Esc</kbd></button>
</div>
{% endset %}
+44
View File
@@ -0,0 +1,44 @@
---
layout: layouts/page.njk
title: Progress
description: Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.
toc:
- label: Usage
id: usage
---
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated Progress component in Basecoat.</h2>
</div>
{% set code_default %}
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
<div id="demo-progress" class="bg-primary h-full w-full flex-1 transition-all" style="width: 17%"></div>
</div>
<script>
setTimeout(() => {
document.getElementById('demo-progress').style.width = '66%';
}, 500);
</script>
{% endset %}
{{ code_preview("progress-default", code_default, class="w-full max-w-sm") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Progress bars are pure HTML composition using Tailwind utility classes. Use a container div for the track and an inner div for the indicator. Set the indicator's width with inline styles or via JavaScript.</p>
</section>
{% set code_usage %}
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
<div class="bg-primary h-full w-full flex-1 transition-all" style="width: 66%"></div>
</div>
{% endset %}
{{ code_block(code_usage) }}
+215
View File
@@ -0,0 +1,215 @@
---
layout: layouts/page.njk
title: Spinner
description: An indicator that can be used to show a loading state.
toc:
- label: Usage
id: usage
- label: Examples
id: examples
children:
- label: Size
id: example-size
- label: Color
id: example-color
- label: Button
id: example-button
- label: Badge
id: example-badge
- label: Input Group
id: example-input-group
- label: Empty
id: example-empty
- label: Item
id: example-item
---
{% from "macros/code_preview.njk" import code_preview %}
{% from "macros/code_block.njk" import code_block %}
<div class="alert mb-6">
{% lucide "circle-alert" %}
<h2>There is no dedicated Spinner component in Basecoat.</h2>
</div>
{% set code_default %}
<article class="group/item flex items-center border text-sm rounded-md transition-colors flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent bg-muted/50 p-4 gap-4">
<div class="flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:size-4">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
</div>
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Processing payment...</h3>
</div>
<div class="flex flex-col gap-1 flex-none text-center">
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">$100.00</p>
</div>
</article>
{% endset %}
{{ code_preview("spinner-default", code_default, class="w-full max-w-xs") }}
<h2 id="usage"><a href="#usage">Usage</a></h2>
<section class="prose">
<p>Spinners are pure HTML using the <code>loader-circle</code> Lucide icon with the <code>animate-spin</code> Tailwind utility. Add <code>role="status"</code> and <code>aria-label="Loading"</code> for accessibility.</p>
<p>Browse the examples below and copy the pattern that fits your needs.</p>
</section>
<h2 id="examples"><a href="#examples">Examples</a></h2>
<h3 id="example-size"><a href="#example-size">Size</a></h3>
<section class="prose">
<p>Use the <code>size-*</code> utility class to change the size of the spinner.</p>
</section>
{% set code_size %}
<div class="flex items-center gap-6">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-3 animate-spin" } %}
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-4 animate-spin" } %}
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin" } %}
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-8 animate-spin" } %}
</div>
{% endset %}
{{ code_preview("spinner-size", code_size) }}
<h3 id="example-color"><a href="#example-color">Color</a></h3>
<section class="prose">
<p>Use the <code>text-*</code> utility class to change the color of the spinner.</p>
</section>
{% set code_color %}
<div class="flex items-center gap-6">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-red-500" } %}
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-green-500" } %}
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-blue-500" } %}
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-yellow-500" } %}
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-6 animate-spin text-purple-500" } %}
</div>
{% endset %}
{{ code_preview("spinner-color", code_color) }}
<h3 id="example-button"><a href="#example-button">Button</a></h3>
<section class="prose">
<p>Add a spinner to a button to indicate a loading state.</p>
</section>
{% set code_button %}
<div class="flex flex-col items-center gap-4">
<button class="btn-sm" disabled>
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Loading...
</button>
<button class="btn-sm-outline" disabled>
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Please wait
</button>
<button class="btn-sm-secondary" disabled>
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Processing
</button>
</div>
{% endset %}
{{ code_preview("spinner-button", code_button) }}
<h3 id="example-badge"><a href="#example-badge">Badge</a></h3>
{% set code_badge %}
<div class="flex items-center gap-4">
<span class="badge">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Syncing
</span>
<span class="badge-secondary">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Updating
</span>
<span class="badge-outline">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
Processing
</span>
</div>
{% endset %}
{{ code_preview("spinner-badge", code_badge) }}
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
<section class="prose">
<p>Input groups can have spinners positioned inside them.</p>
</section>
{% set code_input_group %}
<div class="flex w-full max-w-md flex-col gap-4">
<div class="relative">
<input type="text" class="input pr-9" placeholder="Send a message..." disabled>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}
</div>
</div>
<div class="relative">
<textarea class="textarea pr-10 min-h-27 pb-12" placeholder="Send a message..." disabled></textarea>
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "size-4 animate-spin text-muted-foreground" } %}
<span class="text-sm text-muted-foreground">Validating...</span>
<button type="button" class="btn-sm-icon h-6 w-7.5 ml-auto rounded">
{% lucide "arrow-up" %}
</button>
</footer>
</div>
</div>
{% endset %}
{{ code_preview("spinner-input-group", code_input_group, class="w-full max-w-md") }}
<h3 id="example-empty"><a href="#example-empty">Empty</a></h3>
<section class="prose">
<p>Use a spinner in empty states to indicate a loading or processing state.</p>
</section>
{% set code_empty %}
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
<div class="mb-2 bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-6">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin size-4" } %}
</div>
<h3 class="text-lg font-semibold tracking-tight">Processing your request</h3>
<p class="text-muted-foreground text-sm/relaxed">Please wait while we process your request. Do not refresh the page.</p>
</header>
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
<button class="btn-sm-outline">Cancel</button>
</section>
</div>
{% endset %}
{{ code_preview("spinner-empty", code_empty) }}
<h3 id="example-item"><a href="#example-item">Item</a></h3>
{% set code_item %}
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
<div class="flex shrink-0 items-center justify-center gap-2 self-start [&_svg]:pointer-events-none size-8 border rounded-md bg-muted [&_svg:not([class*='size-'])]:size-4">
{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin size-4 text-muted-foreground" } %}
</div>
<div class="flex flex-1 flex-col gap-1">
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Downloading...</h3>
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">129 MB / 1000 MB</p>
</div>
<button class="btn-sm-outline self-start">Cancel</button>
<footer class="w-full">
<div class="bg-primary/20 relative h-2 w-full overflow-hidden rounded-full">
<div id="demo-progress" class="bg-primary h-full w-full flex-1 transition-all" style="width: 13%"></div>
</div>
</footer>
</article>
{% endset %}
{{ code_preview("spinner-item", code_item, class="w-full max-w-md") }}