mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Prepare 1.0 docs and style migration
This commit is contained in:
@@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added an optional Chart.js helper with `window.basecoat.chart()`, chart CSS, generated tooltips, generated legends, and docs.
|
||||
- Added shared Alert action layout support for direct child `<footer>` action regions.
|
||||
- Added dedicated Avatar and Avatar Group component styles and docs.
|
||||
|
||||
## [1.0.0-beta.2] - 2026-06-14
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# TODO
|
||||
|
||||
## Chart
|
||||
|
||||
- Verify chart tick label color against upstream shadcn/ui across light/dark and all style packs. The target is `muted-foreground`; Chart.js renders labels on canvas, so it may not visually match Recharts SVG text exactly even with the same token.
|
||||
- Add an explicit legend interaction option if needed. Upstream shadcn/ui's `ChartLegendContent` is display-only by default, so Basecoat should not toggle series visibility unless users opt into it.
|
||||
- Document chart sizing escape hatches after visual QA. Default should stay `aspect-video`, matching upstream `ChartContainer`; users can override with height, `min-h-*`, or `aspect-*` on the generated container path once the public API is settled.
|
||||
- Add chart examples for line, area, and tooltip indicator variants once the MVP bar chart behavior is visually aligned.
|
||||
+9
-14
@@ -3,18 +3,22 @@ import reallySimpleDocs from 'reallysimpledocs/astro';
|
||||
|
||||
export default defineConfig({
|
||||
site: process.env.SITE_URL || 'https://basecoatui.com',
|
||||
vite: {
|
||||
resolve: {
|
||||
dedupe: ['basecoat-css'],
|
||||
},
|
||||
},
|
||||
integrations: [
|
||||
reallySimpleDocs({
|
||||
docsDir: './docs/generated',
|
||||
docsDir: './docs/src',
|
||||
routeBase: '/',
|
||||
style: 'nova',
|
||||
customCss: ['./src/docs.css'],
|
||||
style: 'vega',
|
||||
components: {
|
||||
SidebarHeader: './src/components/SidebarHeader.astro',
|
||||
ContentHeader: './docs/src/site/ContentHeader.astro',
|
||||
},
|
||||
site: {
|
||||
title: 'Basecoat',
|
||||
subtitle: 'v1.0.0-beta.1',
|
||||
subtitle: 'v1.0.0-beta.2',
|
||||
description: 'A components library built with Tailwind CSS that works with any web stack.',
|
||||
url: process.env.SITE_URL || 'https://basecoatui.com',
|
||||
keywords: ['components', 'component library', 'component system', 'UI', 'UI kit', 'shadcn', 'shadcn/ui', 'Tailwind CSS', 'Tailwind', 'CSS', 'HTML', 'Jinja', 'Nunjucks', 'JS', 'JavaScript', 'vanilla JS', 'vanilla JavaScript'],
|
||||
@@ -30,15 +34,6 @@ export default defineConfig({
|
||||
logo: {
|
||||
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>',
|
||||
},
|
||||
links: [
|
||||
{
|
||||
label: 'GitHub',
|
||||
icon: '<svg aria-hidden="true" class="lucide lucide-github" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>',
|
||||
iconOnly: true,
|
||||
url: 'https://github.com/hunvreus/basecoat',
|
||||
attrs: { target: '_blank', rel: 'noopener noreferrer', class: 'btn-sm-icon-outline' },
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
/* Basecoat docs font override. This is intentionally not part of the package default. */
|
||||
:root {
|
||||
--font-sans: "Geist Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-heading: "Geist Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
/* HTMX */
|
||||
.htmx-indicator {
|
||||
@apply invisible opacity-0 transition-discrete transition-all;
|
||||
}
|
||||
.htmx-indicator:not(.htmx-request) {
|
||||
@apply hidden;
|
||||
}
|
||||
.htmx-request,
|
||||
.htmx-request.htmx-indicator {
|
||||
@apply visible opacity-100;
|
||||
}
|
||||
|
||||
.docs-search-trigger {
|
||||
@apply hidden h-8 w-full min-w-0 max-w-72 cursor-text items-center gap-x-2 text-muted-foreground outline-none transition-colors md:flex;
|
||||
|
||||
svg {
|
||||
@apply size-4 shrink-0;
|
||||
}
|
||||
.kbd {
|
||||
@apply -me-1.5 ms-auto;
|
||||
}
|
||||
}
|
||||
|
||||
:root:not([data-style-variant]),
|
||||
:root[data-style-variant='nova'] {
|
||||
.docs-search-trigger {
|
||||
@apply border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 rounded-lg border bg-transparent px-2.5 py-1 text-sm focus-visible:ring-3;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-style-variant='vega'] .docs-search-trigger {
|
||||
@apply border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border bg-transparent px-2.5 py-1 text-sm shadow-xs focus-visible:ring-3;
|
||||
}
|
||||
|
||||
:root[data-style-variant='maia'] .docs-search-trigger {
|
||||
@apply border-input bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 rounded-4xl border px-3 py-1 text-sm focus-visible:ring-[3px];
|
||||
}
|
||||
|
||||
:root[data-style-variant='lyra'] .docs-search-trigger {
|
||||
@apply border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 rounded-none border bg-transparent px-2.5 py-1 text-xs focus-visible:ring-1;
|
||||
}
|
||||
|
||||
:root[data-style-variant='mira'] .docs-search-trigger {
|
||||
@apply border-input bg-input/20 dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/30 rounded-md border px-2 py-0.5 text-xs/relaxed focus-visible:ring-2;
|
||||
}
|
||||
|
||||
:root[data-style-variant='luma'] .docs-search-trigger {
|
||||
@apply bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 rounded-3xl border border-transparent px-3 py-1 text-sm focus-visible:ring-3;
|
||||
}
|
||||
|
||||
:root[data-style-variant='sera'] .docs-search-trigger {
|
||||
@apply focus-visible:border-b-ring rounded-none border border-transparent border-b-input bg-transparent px-0 py-1 text-sm transition-[color,border-color];
|
||||
}
|
||||
|
||||
:root[data-style-variant='rhea'] .docs-search-trigger {
|
||||
@apply bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 rounded-2xl border border-transparent px-2.5 py-1 text-sm duration-200 focus-visible:ring-3;
|
||||
}
|
||||
|
||||
.content {
|
||||
pre {
|
||||
@apply grid max-h-[650px] overflow-y-auto rounded-lg bg-muted/40 text-sm;
|
||||
}
|
||||
pre > code {
|
||||
@apply block rounded-[inherit] bg-transparent p-3.5 font-mono;
|
||||
}
|
||||
pre > code.hljs {
|
||||
@apply !bg-transparent;
|
||||
}
|
||||
|
||||
> h2 {
|
||||
@apply mt-12 lg:mt-20 mb-6 scroll-m-22 text-2xl font-semibold tracking-tight first:mt-0;
|
||||
}
|
||||
> h3 {
|
||||
@apply mt-8 mb-6 scroll-m-22 text-xl font-semibold tracking-tight;
|
||||
}
|
||||
> h4 {
|
||||
@apply my-6 scroll-m-22 font-semibold tracking-tight;
|
||||
}
|
||||
.prose {
|
||||
@apply my-6;
|
||||
|
||||
a {
|
||||
@apply font-medium underline underline-offset-4;
|
||||
}
|
||||
code:not(pre > code) {
|
||||
@apply relative rounded-md bg-muted/50 px-[0.3rem] py-[0.2rem] font-mono text-sm;
|
||||
}
|
||||
blockquote {
|
||||
@apply border-l-4 border-border pl-4;
|
||||
}
|
||||
ol {
|
||||
@apply list-decimal ml-6;
|
||||
}
|
||||
ul {
|
||||
@apply list-disc ml-6;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
li {
|
||||
@apply mt-2;
|
||||
}
|
||||
}
|
||||
dl {
|
||||
@apply border-b pb-3 my-6;
|
||||
dt {
|
||||
@apply border-t pt-3 mt-3 first:mt-0;
|
||||
}
|
||||
dd {
|
||||
@apply mt-2 space-y-2;
|
||||
}
|
||||
dl {
|
||||
@apply mt-4 border-b-0 pb-0 pl-8 my-0;
|
||||
}
|
||||
}
|
||||
b,
|
||||
strong {
|
||||
@apply font-semibold;
|
||||
}
|
||||
> p,
|
||||
> blockquote,
|
||||
> ul,
|
||||
> ol,
|
||||
> dl,
|
||||
> table,
|
||||
> pre,
|
||||
> p {
|
||||
@apply my-6;
|
||||
}
|
||||
> *:first-child {
|
||||
@apply !mt-0;
|
||||
}
|
||||
> *:last-child {
|
||||
@apply !mb-0;
|
||||
}
|
||||
}
|
||||
.step {
|
||||
counter-increment: step;
|
||||
|
||||
&:before {
|
||||
@apply absolute w-8 h-8 md:w-9 md:h-9 bg-muted rounded-full font-mono font-semibold text-center text-sm inline-flex items-center justify-center -indent-px border-4 mr-2 border-background;
|
||||
@apply md:ml-[-50px] md:mt-[-4px];
|
||||
content: counter(step);
|
||||
}
|
||||
> h3 {
|
||||
@apply max-md:ml-10 min-h-8 flex items-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-400-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 500;
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-500-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-600-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-700-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-400-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 500;
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-500-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 600;
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-600-normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 700;
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-700-normal.woff2") format("woff2");
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
Custom Highlight.js theme with CSS variables
|
||||
derived from github.min.css and github-dark.min.css
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Base */
|
||||
--hljs-bg: #fff;
|
||||
--hljs-color: #24292e;
|
||||
|
||||
/* Tokens */
|
||||
--hljs-keyword: #d73a49;
|
||||
--hljs-title: #6f42c1;
|
||||
--hljs-literal: #005cc5;
|
||||
--hljs-string: #032f62;
|
||||
--hljs-symbol: #e36209;
|
||||
--hljs-comment: #6a737d;
|
||||
--hljs-tag: #22863a;
|
||||
--hljs-section: #005cc5;
|
||||
--hljs-bullet: #735c0f;
|
||||
|
||||
/* Diffs */
|
||||
--hljs-addition-color: #22863a;
|
||||
--hljs-addition-bg: #f0fff4;
|
||||
--hljs-deletion-color: #b31d28;
|
||||
--hljs-deletion-bg: #ffeef0;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
/* Base */
|
||||
--hljs-bg: #0d1117;
|
||||
--hljs-color: #c9d1d9;
|
||||
|
||||
/* Tokens */
|
||||
--hljs-keyword: #ff7b72;
|
||||
--hljs-title: #d2a8ff;
|
||||
--hljs-literal: #79c0ff;
|
||||
--hljs-string: #a5d6ff;
|
||||
--hljs-symbol: #ffa657;
|
||||
--hljs-comment: #8b949e;
|
||||
--hljs-tag: #7ee787;
|
||||
--hljs-section: #1f6feb;
|
||||
--hljs-bullet: #f2cc60;
|
||||
|
||||
/* Diffs */
|
||||
--hljs-addition-color: #aff5b4;
|
||||
--hljs-addition-bg: #033a16;
|
||||
--hljs-deletion-color: #ffdcd7;
|
||||
--hljs-deletion-bg: #67060c;
|
||||
}
|
||||
|
||||
/* --- Base --- */
|
||||
pre code.hljs{
|
||||
display:block;
|
||||
overflow-x:auto;
|
||||
padding:1em
|
||||
}
|
||||
code.hljs{
|
||||
padding:3px 5px
|
||||
}
|
||||
|
||||
/* --- Mapped styles --- */
|
||||
.hljs {
|
||||
color: var(--hljs-color);
|
||||
background: var(--hljs-bg);
|
||||
}
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable.language_ {
|
||||
color: var(--hljs-keyword);
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-title.class_,
|
||||
.hljs-title.class_.inherited__,
|
||||
.hljs-title.function_ {
|
||||
color: var(--hljs-title);
|
||||
}
|
||||
|
||||
.hljs-attr,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-number,
|
||||
.hljs-operator,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id,
|
||||
.hljs-variable {
|
||||
color: var(--hljs-literal);
|
||||
}
|
||||
|
||||
.hljs-meta .hljs-string,
|
||||
.hljs-regexp,
|
||||
.hljs-string {
|
||||
color: var(--hljs-string);
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-symbol {
|
||||
color: var(--hljs-symbol);
|
||||
}
|
||||
|
||||
.hljs-code,
|
||||
.hljs-comment,
|
||||
.hljs-formula {
|
||||
color: var(--hljs-comment);
|
||||
}
|
||||
|
||||
.hljs-name,
|
||||
.hljs-quote,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-selector-tag {
|
||||
color: var(--hljs-tag);
|
||||
}
|
||||
|
||||
.hljs-subst {
|
||||
color: var(--hljs-color);
|
||||
}
|
||||
|
||||
.hljs-section {
|
||||
color: var(--hljs-section);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hljs-bullet {
|
||||
color: var(--hljs-bullet);
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: var(--hljs-addition-color);
|
||||
background-color: var(--hljs-addition-bg);
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: var(--hljs-deletion-color);
|
||||
background-color: var(--hljs-deletion-bg);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-luma.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-lyra.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-maia.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-mira.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-nova.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-rhea.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-sera.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-vega.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "./fonts.css";
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-vega.css";
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
@@ -1,95 +0,0 @@
|
||||
.theme-claude {
|
||||
&:root {
|
||||
--background: oklch(0.98 0.01 95.10);
|
||||
--foreground: oklch(0.34 0.03 95.72);
|
||||
--card: oklch(0.98 0.01 95.10);
|
||||
--card-foreground: oklch(0.19 0.00 106.59);
|
||||
--popover: oklch(1.00 0 0);
|
||||
--popover-foreground: oklch(0.27 0.02 98.94);
|
||||
--primary: oklch(0.62 0.14 39.04);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.92 0.01 92.99);
|
||||
--secondary-foreground: oklch(0.43 0.02 98.60);
|
||||
--muted: oklch(0.93 0.02 90.24);
|
||||
--muted-foreground: oklch(0.61 0.01 97.42);
|
||||
--accent: oklch(0.92 0.01 92.99);
|
||||
--accent-foreground: oklch(0.27 0.02 98.94);
|
||||
--destructive: oklch(0.19 0.00 106.59);
|
||||
--destructive-foreground: oklch(1.00 0 0);
|
||||
--border: oklch(0.88 0.01 97.36);
|
||||
--input: oklch(0.76 0.02 98.35);
|
||||
--ring: oklch(0.59 0.17 253.06);
|
||||
--chart-1: oklch(0.56 0.13 43.00);
|
||||
--chart-2: oklch(0.69 0.16 290.41);
|
||||
--chart-3: oklch(0.88 0.03 93.13);
|
||||
--chart-4: oklch(0.88 0.04 298.18);
|
||||
--chart-5: oklch(0.56 0.13 42.06);
|
||||
--sidebar: oklch(0.97 0.01 98.88);
|
||||
--sidebar-foreground: oklch(0.36 0.01 106.65);
|
||||
--sidebar-primary: oklch(0.62 0.14 39.04);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.92 0.01 92.99);
|
||||
--sidebar-accent-foreground: oklch(0.33 0 0);
|
||||
--sidebar-border: oklch(0.94 0 0);
|
||||
--sidebar-ring: oklch(0.77 0 0);
|
||||
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
--background: oklch(0.27 0.00 106.64);
|
||||
--foreground: oklch(0.81 0.01 93.01);
|
||||
--card: oklch(0.27 0.00 106.64);
|
||||
--card-foreground: oklch(0.98 0.01 95.10);
|
||||
--popover: oklch(0.31 0.00 106.60);
|
||||
--popover-foreground: oklch(0.92 0.00 106.48);
|
||||
--primary: oklch(0.67 0.13 38.76);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.98 0.01 95.10);
|
||||
--secondary-foreground: oklch(0.31 0.00 106.60);
|
||||
--muted: oklch(0.22 0.00 106.71);
|
||||
--muted-foreground: oklch(0.77 0.02 99.07);
|
||||
--accent: oklch(0.21 0.01 95.42);
|
||||
--accent-foreground: oklch(0.97 0.01 98.88);
|
||||
--destructive: oklch(0.64 0.21 25.33);
|
||||
--destructive-foreground: oklch(1.00 0 0);
|
||||
--border: oklch(0.36 0.01 106.89);
|
||||
--input: oklch(0.43 0.01 100.22);
|
||||
--ring: oklch(0.59 0.17 253.06);
|
||||
--chart-1: oklch(0.56 0.13 43.00);
|
||||
--chart-2: oklch(0.69 0.16 290.41);
|
||||
--chart-3: oklch(0.21 0.01 95.42);
|
||||
--chart-4: oklch(0.31 0.05 289.32);
|
||||
--chart-5: oklch(0.56 0.13 42.06);
|
||||
--sidebar: oklch(0.24 0.00 67.71);
|
||||
--sidebar-foreground: oklch(0.81 0.01 93.01);
|
||||
--sidebar-primary: oklch(0.33 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.17 0.00 106.62);
|
||||
--sidebar-accent-foreground: oklch(0.81 0.01 93.01);
|
||||
--sidebar-border: oklch(0.94 0 0);
|
||||
--sidebar-ring: oklch(0.77 0 0);
|
||||
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
.theme-doom-64 {
|
||||
&:root {
|
||||
--background: oklch(0.85 0 0);
|
||||
--foreground: oklch(0.24 0 0);
|
||||
--card: oklch(0.76 0 0);
|
||||
--card-foreground: oklch(0.24 0 0);
|
||||
--popover: oklch(0.76 0 0);
|
||||
--popover-foreground: oklch(0.24 0 0);
|
||||
--primary: oklch(0.50 0.19 27.48);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.50 0.09 126.19);
|
||||
--secondary-foreground: oklch(1.00 0 0);
|
||||
--muted: oklch(0.78 0 0);
|
||||
--muted-foreground: oklch(0.41 0 0);
|
||||
--accent: oklch(0.59 0.10 245.74);
|
||||
--accent-foreground: oklch(1.00 0 0);
|
||||
--destructive: oklch(0.71 0.20 46.46);
|
||||
--destructive-foreground: oklch(0 0 0);
|
||||
--border: oklch(0.43 0 0);
|
||||
--input: oklch(0.43 0 0);
|
||||
--ring: oklch(0.50 0.19 27.48);
|
||||
--chart-1: oklch(0.50 0.19 27.48);
|
||||
--chart-2: oklch(0.50 0.09 126.19);
|
||||
--chart-3: oklch(0.59 0.10 245.74);
|
||||
--chart-4: oklch(0.71 0.20 46.46);
|
||||
--chart-5: oklch(0.57 0.04 40.43);
|
||||
--sidebar: oklch(0.76 0 0);
|
||||
--sidebar-foreground: oklch(0.24 0 0);
|
||||
--sidebar-primary: oklch(0.50 0.19 27.48);
|
||||
--sidebar-primary-foreground: oklch(1.00 0 0);
|
||||
--sidebar-accent: oklch(0.59 0.10 245.74);
|
||||
--sidebar-accent-foreground: oklch(1.00 0 0);
|
||||
--sidebar-border: oklch(0.43 0 0);
|
||||
--sidebar-ring: oklch(0.50 0.19 27.48);
|
||||
--font-sans: "Oxanium", sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: "Source Code Pro", monospace;
|
||||
--radius: 0px;
|
||||
--shadow-2xs: 0px 2px 4px 0px hsl(0 0% 0% / 0.20);
|
||||
--shadow-xs: 0px 2px 4px 0px hsl(0 0% 0% / 0.20);
|
||||
--shadow-sm: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 1px 2px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 1px 2px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow-md: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 2px 4px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow-lg: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 4px 6px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow-xl: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 8px 10px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow-2xl: 0px 2px 4px 0px hsl(0 0% 0% / 1.00);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
--background: oklch(0.22 0 0);
|
||||
--foreground: oklch(0.91 0 0);
|
||||
--card: oklch(0.29 0 0);
|
||||
--card-foreground: oklch(0.91 0 0);
|
||||
--popover: oklch(0.29 0 0);
|
||||
--popover-foreground: oklch(0.91 0 0);
|
||||
--primary: oklch(0.61 0.21 27.03);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.64 0.15 133.01);
|
||||
--secondary-foreground: oklch(0 0 0);
|
||||
--muted: oklch(0.26 0 0);
|
||||
--muted-foreground: oklch(0.71 0 0);
|
||||
--accent: oklch(0.75 0.12 244.75);
|
||||
--accent-foreground: oklch(0 0 0);
|
||||
--destructive: oklch(0.78 0.17 68.09);
|
||||
--destructive-foreground: oklch(0 0 0);
|
||||
--border: oklch(0.41 0 0);
|
||||
--input: oklch(0.41 0 0);
|
||||
--ring: oklch(0.61 0.21 27.03);
|
||||
--chart-1: oklch(0.61 0.21 27.03);
|
||||
--chart-2: oklch(0.64 0.15 133.01);
|
||||
--chart-3: oklch(0.75 0.12 244.75);
|
||||
--chart-4: oklch(0.78 0.17 68.09);
|
||||
--chart-5: oklch(0.65 0.03 40.80);
|
||||
--sidebar: oklch(0.19 0 0);
|
||||
--sidebar-foreground: oklch(0.91 0 0);
|
||||
--sidebar-primary: oklch(0.61 0.21 27.03);
|
||||
--sidebar-primary-foreground: oklch(1.00 0 0);
|
||||
--sidebar-accent: oklch(0.75 0.12 244.75);
|
||||
--sidebar-accent-foreground: oklch(0 0 0);
|
||||
--sidebar-border: oklch(0.41 0 0);
|
||||
--sidebar-ring: oklch(0.61 0.21 27.03);
|
||||
--font-sans: "Oxanium", sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: "Source Code Pro", monospace;
|
||||
--radius: 0px;
|
||||
--shadow-2xs: 0px 2px 5px 0px hsl(0 0% 0% / 0.30);
|
||||
--shadow-xs: 0px 2px 5px 0px hsl(0 0% 0% / 0.30);
|
||||
--shadow-sm: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 1px 2px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 1px 2px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow-md: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 2px 4px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow-lg: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 4px 6px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow-xl: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 8px 10px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow-2xl: 0px 2px 5px 0px hsl(0 0% 0% / 1.50);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
.theme-supabase {
|
||||
&:root {
|
||||
--background: oklch(0.99 0 0);
|
||||
--foreground: oklch(0.20 0 0);
|
||||
--card: oklch(0.99 0 0);
|
||||
--card-foreground: oklch(0.20 0 0);
|
||||
--popover: oklch(0.99 0 0);
|
||||
--popover-foreground: oklch(0.44 0 0);
|
||||
--primary: oklch(0.83 0.13 160.91);
|
||||
--primary-foreground: oklch(0.26 0.01 166.46);
|
||||
--secondary: oklch(0.99 0 0);
|
||||
--secondary-foreground: oklch(0.20 0 0);
|
||||
--muted: oklch(0.95 0 0);
|
||||
--muted-foreground: oklch(0.24 0 0);
|
||||
--accent: oklch(0.95 0 0);
|
||||
--accent-foreground: oklch(0.24 0 0);
|
||||
--destructive: oklch(0.55 0.19 32.73);
|
||||
--destructive-foreground: oklch(0.99 0.00 17.21);
|
||||
--border: oklch(0.90 0 0);
|
||||
--input: oklch(0.90 0 0);
|
||||
--ring: oklch(0.83 0.13 160.91);
|
||||
--chart-1: oklch(0.83 0.13 160.91);
|
||||
--chart-2: oklch(0.62 0.19 259.81);
|
||||
--chart-3: oklch(0.61 0.22 292.72);
|
||||
--chart-4: oklch(0.77 0.16 70.08);
|
||||
--chart-5: oklch(0.70 0.15 162.48);
|
||||
--sidebar: oklch(0.99 0 0);
|
||||
--sidebar-foreground: oklch(0.55 0 0);
|
||||
--sidebar-primary: oklch(0.83 0.13 160.91);
|
||||
--sidebar-primary-foreground: oklch(0.26 0.01 166.46);
|
||||
--sidebar-accent: oklch(0.95 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.24 0 0);
|
||||
--sidebar-border: oklch(0.90 0 0);
|
||||
--sidebar-ring: oklch(0.83 0.13 160.91);
|
||||
--font-sans: Outfit, sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.43);
|
||||
--tracking-normal: 0.025em;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
--background: oklch(0.18 0 0);
|
||||
--foreground: oklch(0.93 0.01 255.51);
|
||||
--card: oklch(0.20 0 0);
|
||||
--card-foreground: oklch(0.93 0.01 255.51);
|
||||
--popover: oklch(0.26 0 0);
|
||||
--popover-foreground: oklch(0.73 0 0);
|
||||
--primary: oklch(0.44 0.10 156.76);
|
||||
--primary-foreground: oklch(0.92 0.01 167.16);
|
||||
--secondary: oklch(0.26 0 0);
|
||||
--secondary-foreground: oklch(0.99 0 0);
|
||||
--muted: oklch(0.24 0 0);
|
||||
--muted-foreground: oklch(0.71 0 0);
|
||||
--accent: oklch(0.31 0 0);
|
||||
--accent-foreground: oklch(0.99 0 0);
|
||||
--destructive: oklch(0.31 0.09 29.79);
|
||||
--destructive-foreground: oklch(0.94 0.00 34.31);
|
||||
--border: oklch(0.28 0 0);
|
||||
--input: oklch(0.28 0 0);
|
||||
--ring: oklch(0.80 0.18 151.71);
|
||||
--chart-1: oklch(0.80 0.18 151.71);
|
||||
--chart-2: oklch(0.71 0.14 254.62);
|
||||
--chart-3: oklch(0.71 0.16 293.54);
|
||||
--chart-4: oklch(0.84 0.16 84.43);
|
||||
--chart-5: oklch(0.78 0.13 181.91);
|
||||
--sidebar: oklch(0.18 0 0);
|
||||
--sidebar-foreground: oklch(0.63 0 0);
|
||||
--sidebar-primary: oklch(0.44 0.10 156.76);
|
||||
--sidebar-primary-foreground: oklch(0.92 0.01 167.16);
|
||||
--sidebar-accent: oklch(0.31 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.99 0 0);
|
||||
--sidebar-border: oklch(0.28 0 0);
|
||||
--sidebar-ring: oklch(0.80 0.18 151.71);
|
||||
--font-sans: Outfit, sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.43);
|
||||
}
|
||||
|
||||
body {
|
||||
letter-spacing: var(--tracking-normal);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"menu": [
|
||||
{
|
||||
"type": "group",
|
||||
"label": "Getting started",
|
||||
"items": [
|
||||
"introduction",
|
||||
"installation",
|
||||
"customization",
|
||||
"cli",
|
||||
{
|
||||
"type": "item",
|
||||
"label": "Kitchen sink",
|
||||
"url": "/kitchen-sink/",
|
||||
"icon": "layout-dashboard"
|
||||
},
|
||||
{
|
||||
"type": "item",
|
||||
"label": "GitHub",
|
||||
"url": "https://github.com/hunvreus/basecoat",
|
||||
"icon": "github",
|
||||
"external": true,
|
||||
"attrs": { "target": "_blank", "rel": "noopener" }
|
||||
},
|
||||
{
|
||||
"type": "item",
|
||||
"label": "Discord",
|
||||
"url": "https://basecoatui.com/chat",
|
||||
"icon": "discord",
|
||||
"external": true,
|
||||
"attrs": { "target": "_blank", "rel": "noopener" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "group",
|
||||
"label": "Components",
|
||||
"items": [
|
||||
"components/accordion",
|
||||
"components/alert",
|
||||
"components/alert-dialog",
|
||||
"components/avatar",
|
||||
"components/badge",
|
||||
"components/breadcrumb",
|
||||
"components/button",
|
||||
"components/button-group",
|
||||
"components/card",
|
||||
"components/carousel",
|
||||
"components/chart",
|
||||
"components/checkbox",
|
||||
"components/combobox",
|
||||
"components/command",
|
||||
"components/dialog",
|
||||
"components/dropdown-menu",
|
||||
"components/empty",
|
||||
"components/field",
|
||||
"components/input",
|
||||
"components/input-group",
|
||||
"components/item",
|
||||
"components/kbd",
|
||||
"components/label",
|
||||
"components/native-select",
|
||||
"components/pagination",
|
||||
"components/popover",
|
||||
"components/progress",
|
||||
"components/radio-group",
|
||||
"components/select",
|
||||
"components/sidebar",
|
||||
"components/skeleton",
|
||||
"components/slider",
|
||||
"components/spinner",
|
||||
"components/switch",
|
||||
"components/table",
|
||||
"components/tabs",
|
||||
"components/textarea",
|
||||
"components/theme-switcher",
|
||||
"components/toast",
|
||||
"components/tooltip"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export default {
|
||||
title: "Basecoat",
|
||||
description: "A components library built with Tailwind CSS that works with any web stack.",
|
||||
keywords: ["components", "component library", "component system", "UI", "UI kit", "shadcn", "shadcn/ui", "Tailwind CSS", "Tailwind", "CSS", "HTML", "Jinja", "Nunjucks", "JS", "JavaScript", "vanilla JS", "vanilla JavaScript"],
|
||||
author: {
|
||||
name: "Ronan Berder",
|
||||
x: "@hunvreus"
|
||||
},
|
||||
url: process.env.ELEVENTY_ENV === 'development' ? "http://localhost:8080" : "https://basecoatui.com"
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{# Dark mode #}
|
||||
<script>
|
||||
(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem('themeMode');
|
||||
if (stored ? stored === 'dark'
|
||||
: matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
})();
|
||||
</script>
|
||||
{# Theme variant #}
|
||||
<script>
|
||||
(function() {
|
||||
try {
|
||||
const storedTheme = localStorage.getItem('themeVariant');
|
||||
if (storedTheme) document.documentElement.classList.add(`theme-${storedTheme}`);
|
||||
} catch (event) {
|
||||
console.error('Could not apply theme variant from localStorage', event);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{# Meta #}
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Language" content="en">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{# Title & Description #}
|
||||
{% set meta_title = title ~ ' | ' ~ site.title if title else site.title %}
|
||||
{% set meta_description = description or site.description %}
|
||||
<title>{{ meta_title }}</title>
|
||||
<meta name="description" content="{{ meta_description }}"/>
|
||||
<meta name="title" content="{{ meta_title }}">
|
||||
{% if site.keywords %}<meta name="keywords" content="{{ site.keywords | join(',') }}" />{% endif %}
|
||||
{% if siteUrl %}<link rel="canonical" href="{{ siteUrl }}{{ page.url }}">{% endif %}
|
||||
{# Favicon #}
|
||||
<link rel="icon" type="image/svg+xml" href="{{ siteUrl }}/assets/favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ siteUrl }}/assets/apple-touch-icon.png">
|
||||
{# Open Graph / Facebook #}
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ siteUrl }}{{ page.url }}">
|
||||
<meta property="og:title" content="{{ meta_title }}">
|
||||
<meta property="og:description" content="{{ meta_description }}">
|
||||
<meta property="og:image" content="{{ siteUrl }}/assets/social.png">
|
||||
<meta property="og:site_name" content="{{ site.title }}">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="og:author" content="{{ site.author.name }}">
|
||||
{# X/Twitter #}
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:url" content="{{ siteUrl }}{{ page.url }}">
|
||||
<meta name="twitter:title" content="{{ meta_title }}">
|
||||
<meta name="twitter:description" content="{{ meta_description }}">
|
||||
<meta name="twitter:image" content="{{ siteUrl }}/assets/social.png">
|
||||
<meta name="twitter:creator" content="{{ site.author.x }}">
|
||||
{# CSS #}
|
||||
<script>
|
||||
(() => {
|
||||
const styles = new Set(['vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea']);
|
||||
let styleVariant = 'nova';
|
||||
try {
|
||||
const storedStyle = localStorage.getItem('styleVariant');
|
||||
if (styles.has(storedStyle)) styleVariant = storedStyle;
|
||||
} catch (event) {
|
||||
console.error('Could not apply style variant from localStorage', event);
|
||||
}
|
||||
|
||||
document.documentElement.dataset.styleVariant = styleVariant;
|
||||
document.write(`<link rel="stylesheet" id="style-variant-stylesheet" href="/assets/styles-${styleVariant}.css">`);
|
||||
})();
|
||||
</script>
|
||||
<noscript><link rel="stylesheet" id="style-variant-stylesheet" href="/assets/styles-nova.css"></noscript>
|
||||
{# JS #}
|
||||
<script src="/assets/js/basecoat.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/command.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/combobox.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/dropdown-menu.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/popover.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/range.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/select.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/sidebar.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/tabs.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/toast.js?v={{ pkg.version }}" defer></script>
|
||||
{# HTMX #}
|
||||
<script src="/assets/js/htmx.min.js" defer></script>
|
||||
{# Highlight.js #}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/django.min.js"></script>
|
||||
<script>
|
||||
const highlight = () => {
|
||||
if (!window.hljs) return;
|
||||
document
|
||||
.querySelectorAll('pre code:not([data-highlighted]), code.highlight:not([data-highlighted])')
|
||||
.forEach(el => hljs.highlightElement(el));
|
||||
};
|
||||
|
||||
if (!window._hljsInit) {
|
||||
window._hljsInit = true;
|
||||
document.addEventListener('DOMContentLoaded', highlight);
|
||||
document.addEventListener('htmx:afterSwap', highlight);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('htmx:historyRestore', () => {
|
||||
if (window.basecoat) window.basecoat.initAll();
|
||||
});
|
||||
</script>
|
||||
{% if head %}{{ head | safe }}{% endif %}
|
||||
</head>
|
||||
<body hx-boost="true" hx-target="#content" hx-select="#content" hx-swap="outerHTML show:window:top">
|
||||
{{ content | safe }}
|
||||
|
||||
{% from "toast.njk" import toaster %}
|
||||
{{ toaster() }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
---
|
||||
|
||||
{% include "partials/sidebar.njk" %}
|
||||
|
||||
<main id="content">
|
||||
<header class="bg-background sticky inset-x-0 top-0 isolate flex shrink-0 items-center gap-2 border-b z-10">
|
||||
{% include "partials/header.njk" %}
|
||||
</header>
|
||||
|
||||
<div class="p-4 md:p-6 xl:p-12">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</main>
|
||||
@@ -1,124 +0,0 @@
|
||||
---
|
||||
layout: layouts/layout.njk
|
||||
---
|
||||
{% set docsMenu = docs.menu if docs and docs.menu else menu %}
|
||||
{% set navigation = docsMenu | getNavigation(collections) %}
|
||||
|
||||
<div class="mx-auto flex w-full max-w-screen-lg gap-10">
|
||||
<article class="min-w-0 flex-1 wrap-break-word">
|
||||
<div class="mx-auto w-full max-w-screen-md">
|
||||
<header class="space-y-2">
|
||||
<div class="flex items-start gap-x-2">
|
||||
<h1 class="text-2xl font-semibold tracking-tight sm:text-3xl xl:text-4xl mr-auto">{{ title }}</h1>
|
||||
<div class="flex items-center gap-x-2 pt-1">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-sm-secondary h-7 px-2.5 text-[13px] max-sm:size-7"
|
||||
data-md-url="{{ page.url | markdownUrl }}"
|
||||
onclick="copyPageMarkdown(this)"
|
||||
>
|
||||
{% lucide "copy" %}
|
||||
<span class="hidden sm:block">Copy page</span>
|
||||
</button>
|
||||
<div role="group" class="button-group">
|
||||
{% if navigation.prev %}
|
||||
<a href="{{ navigation.prev.url }}" class="btn-sm-icon-secondary hidden sm:flex size-7">
|
||||
<span class="sr-only">{{ navigation.prev.label }}</span>
|
||||
{% lucide "arrow-left" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if navigation.prev and navigation.next %}
|
||||
<hr role="separator">
|
||||
{% endif %}
|
||||
{% if navigation.next %}
|
||||
<a href="{{ navigation.next.url }}" class="btn-sm-icon-secondary hidden sm:flex size-7">
|
||||
<span class="sr-only">{{ navigation.next.label }}</span>
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted-foreground text-[1.05rem] sm:text-base">{{ description }}</p>
|
||||
</header>
|
||||
<div class="pb-12 mt-8 content">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
{% if navigation.prev or navigation.next %}
|
||||
<footer class="flex items-center gap-2 border-t pt-6 mt-12">
|
||||
{% if navigation.prev %}
|
||||
<a href="{{ navigation.prev.url }}" class="btn-sm-secondary mr-auto">
|
||||
{% lucide "arrow-left" %}
|
||||
{{ navigation.prev.label }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if navigation.next %}
|
||||
<a href="{{ navigation.next.url }}" class="btn-sm-secondary ml-auto">
|
||||
{{ navigation.next.label }}
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</footer>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
<aside class="hidden xl:block w-56 shrink-0">
|
||||
<div class="sticky top-20 text-sm [&_a]:text-muted-foreground [&_a]:hover:text-primary space-y-2 [&_ol]:space-y-2 [&_ol_ol]:pl-3 [&_ol_ol]:mt-2">
|
||||
<h2 class="font-medium flex items-center gap-1">
|
||||
{% lucide "table-of-contents", { class: "size-4" } %}
|
||||
On this page
|
||||
</h2>
|
||||
{{ content | toc | safe }}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function copyPageMarkdown(button) {
|
||||
const markdownUrl = button?.dataset?.mdUrl;
|
||||
if (!markdownUrl) return;
|
||||
|
||||
const label = button.querySelector('span');
|
||||
const originalLabel = label?.textContent;
|
||||
|
||||
try {
|
||||
const absoluteMarkdownUrl = new URL(markdownUrl, window.location.href).toString();
|
||||
const response =
|
||||
(await fetch(markdownUrl, { credentials: 'same-origin', cache: 'no-store' }).catch(() => null)) ||
|
||||
(await fetch(absoluteMarkdownUrl, { credentials: 'same-origin', cache: 'no-store' }));
|
||||
if (!response.ok) throw new Error('Failed to fetch markdown');
|
||||
const markdown = await response.text();
|
||||
await writeToClipboard(markdown);
|
||||
|
||||
if (label) {
|
||||
label.textContent = 'Copied';
|
||||
window.setTimeout(() => {
|
||||
label.textContent = originalLabel || 'Copy page';
|
||||
}, 1200);
|
||||
}
|
||||
} catch (error) {
|
||||
const absoluteMarkdownUrl = new URL(markdownUrl, window.location.href).toString();
|
||||
window.open(absoluteMarkdownUrl, '_blank', 'noopener');
|
||||
}
|
||||
}
|
||||
|
||||
async function writeToClipboard(text) {
|
||||
if (navigator.clipboard?.writeText && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return;
|
||||
}
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.top = '-9999px';
|
||||
textarea.style.left = '-9999px';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
const ok = document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
if (!ok) throw new Error('Copy failed');
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +0,0 @@
|
||||
{%- macro code_block(code, language="html", class="my-4") -%}
|
||||
<div class="relative {{ class }}">
|
||||
<pre class="scrollbar"><code class="language-{{ language }}">{{- code | escape -}}</code></pre>
|
||||
<button onclick="const button = this; const code = this.parentElement.querySelector('pre code'); if (!code) return; navigator.clipboard.writeText(code.textContent || '').then(() => { button.classList.add('copied'); setTimeout(() => { button.classList.remove('copied') }, 2000); }).catch(err => { console.error('Failed to copy text: ', err); });" class="btn-icon-ghost size-8 absolute right-2.5 top-2 text-muted-foreground hover:text-foreground group">{% lucide "copy", {"class": "group-[.copied]:hidden" } %}{% lucide "check", {"class": "hidden group-[.copied]:block" } %}</button>
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
@@ -1,26 +0,0 @@
|
||||
{%- macro code_preview(id, code, class=None, default_code=false) -%}
|
||||
{% from "./code_block.njk" import code_block %}
|
||||
{% set code_panel = code_block(code, class="") %}
|
||||
{% set preview_panel %}
|
||||
<div class="ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 relative rounded-md border">
|
||||
<div class="preview flex min-h-[350px] w-full justify-center p-10 items-center">
|
||||
<div {% if class %}class="{{ class }}"{% endif %}>
|
||||
{{ code | safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
<div class="relative my-6">
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "Preview", panel: preview_panel },
|
||||
{ tab: "Code", panel: code_panel }
|
||||
] %}
|
||||
|
||||
{{ tabs(
|
||||
tabsets=tabsets,
|
||||
default_tab_index=(2 if default_code else 1)
|
||||
) }}
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
@@ -1,20 +0,0 @@
|
||||
{% macro toc_nav(items=[]) %}
|
||||
<nav class="sticky top-22 space-y-2 [&_ul]:m-0 [&_ul]:list-none [&_ul_ul]:pl-4 [&_li]:mt-0 [&_li]:pt-2 [&_a]:inline-block [&_a]:no-underline [&_a]:transition-colors [&_a]:hover:text-foreground [&_a]:text-muted-foreground">
|
||||
<h4 class="font-medium">On This Page</h4>
|
||||
{% macro toc_list(items=[]) %}
|
||||
{% if items and items.length %}
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li>
|
||||
<a href="#{{ item.id }}">{{ item.label }}</a>
|
||||
{% if item.children and item.children.length %}
|
||||
{{ toc_list(item.children) }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{{ toc_list(items) }}
|
||||
</nav>
|
||||
{% endmacro %}
|
||||
@@ -1,127 +0,0 @@
|
||||
{% from "command.njk" import command_dialog %}
|
||||
|
||||
{% set docsMenu = docs.menu if docs and docs.menu else menu %}
|
||||
{% set commandMenu = docsMenu | commandMenu(collections) %}
|
||||
{{ command_dialog(id="command-search", items=commandMenu, placeholder="Search the docs...") }}
|
||||
<script>
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
|
||||
event.preventDefault();
|
||||
const dialog = document.getElementById('command-search');
|
||||
if (!dialog) return;
|
||||
if (dialog.open) dialog.close();
|
||||
else {
|
||||
dialog.showModal();
|
||||
dialog.querySelector('header input')?.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-14 w-full items-center gap-2 px-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick="document.getElementById('sidebar')?.toggle()"
|
||||
aria-label="Toggle sidebar"
|
||||
data-tooltip="Toggle sidebar"
|
||||
data-side="bottom"
|
||||
data-align="start"
|
||||
class="btn-sm-icon-ghost mr-auto size-7 -ml-1.5"
|
||||
>
|
||||
{% lucide "panel-left" %}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="docs-search-trigger"
|
||||
onclick="document.getElementById('command-search').showModal(); document.getElementById('command-search').querySelector('header input')?.focus();"
|
||||
>
|
||||
{% lucide "search" %}
|
||||
Search the docs...
|
||||
<kbd class="kbd">⌘K</kbd>
|
||||
</button>
|
||||
|
||||
<select class="select h-8 leading-none" id="theme-select" aria-label="Theme">
|
||||
<option value="">Default</option>
|
||||
<option value="claude">Claude</option>
|
||||
<option value="doom-64">Doom 64</option>
|
||||
<option value="supabase">Supabase</option>
|
||||
</select>
|
||||
<script>
|
||||
(() => {
|
||||
const themeSelect = document.getElementById('theme-select');
|
||||
const storedTheme = localStorage.getItem('themeVariant');
|
||||
if (themeSelect && storedTheme) themeSelect.value = storedTheme;
|
||||
themeSelect?.addEventListener('change', () => {
|
||||
const newTheme = themeSelect.value;
|
||||
document.documentElement.classList.forEach(c => {
|
||||
if (c.startsWith('theme-')) document.documentElement.classList.remove(c);
|
||||
});
|
||||
if (newTheme) {
|
||||
document.documentElement.classList.add(`theme-${newTheme}`);
|
||||
localStorage.setItem('themeVariant', newTheme);
|
||||
} else {
|
||||
localStorage.removeItem('themeVariant');
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<select class="select h-8 leading-none" id="style-select" aria-label="Style">
|
||||
<option value="vega">Vega</option>
|
||||
<option value="nova">Nova</option>
|
||||
<option value="maia">Maia</option>
|
||||
<option value="lyra">Lyra</option>
|
||||
<option value="mira">Mira</option>
|
||||
<option value="luma">Luma</option>
|
||||
<option value="sera">Sera</option>
|
||||
<option value="rhea">Rhea</option>
|
||||
</select>
|
||||
<script>
|
||||
(() => {
|
||||
const styleSelect = document.getElementById('style-select');
|
||||
const styleLink = document.getElementById('style-variant-stylesheet');
|
||||
const applyStyle = (styleName) => {
|
||||
const nextStyle = styleName || 'nova';
|
||||
const nextHref = `/assets/styles-${nextStyle}.css`;
|
||||
document.documentElement.dataset.styleVariant = nextStyle;
|
||||
if (styleLink && styleLink.getAttribute('href') !== nextHref) {
|
||||
styleLink.href = nextHref;
|
||||
}
|
||||
};
|
||||
|
||||
const storedStyle = localStorage.getItem('styleVariant') || 'nova';
|
||||
if (styleSelect) styleSelect.value = storedStyle;
|
||||
applyStyle(storedStyle);
|
||||
|
||||
styleSelect?.addEventListener('change', () => {
|
||||
const newStyle = styleSelect.value || 'nova';
|
||||
applyStyle(newStyle);
|
||||
localStorage.setItem('styleVariant', newStyle);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle dark mode"
|
||||
data-tooltip="Toggle dark mode"
|
||||
data-side="bottom"
|
||||
onclick="window.basecoat?.theme.toggle()"
|
||||
class="btn-icon-outline size-8"
|
||||
>
|
||||
<span class="hidden dark:block">{% lucide "sun" %}</span>
|
||||
<span class="block dark:hidden">{% lucide "moon" %}</span>
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/hunvreus/basecoat"
|
||||
class="btn-icon size-8"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-tooltip="GitHub repository"
|
||||
data-side="bottom"
|
||||
data-align="end"
|
||||
>
|
||||
{% lucide "github" %}
|
||||
</a>
|
||||
</div>
|
||||
@@ -1,89 +0,0 @@
|
||||
<section id="accordion" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Accordion</h2>
|
||||
<a href="/components/accordion" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="group grid w-full max-w-xl gap-4">
|
||||
<section class="accordion">
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it accessible?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It adheres to the WAI-ARIA design pattern.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it styled?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It comes with default styles that matches the other components' aesthetic.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it animated?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm">Yes. It's animated by default, but you can disable it if you prefer.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<section class="accordion">
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
What are the key considerations when implementing a comprehensive enterprise-level authentication system?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Implementing a robust enterprise authentication system requires careful consideration of multiple factors. This includes secure password hashing and storage, multi-factor authentication (MFA) implementation, session management, OAuth2 and SSO integration, regular security audits, rate limiting to prevent brute force attacks, and maintaining detailed audit logs. Additionally, you'll need to consider scalability, performance impact, and compliance with relevant data protection regulations such as GDPR or HIPAA.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
How does modern distributed system architecture handle eventual consistency and data synchronization across multiple regions?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm">Modern distributed systems employ various strategies to maintain data consistency across regions. This often involves using techniques like CRDT (Conflict-Free Replicated Data Types), vector clocks, and gossip protocols. Systems might implement event sourcing patterns, utilize message queues for asynchronous updates, and employ sophisticated conflict resolution strategies. Popular solutions like Amazon's DynamoDB and Google's Spanner demonstrate different approaches to solving these challenges, balancing between consistency, availability, and partition tolerance as described in the CAP theorem.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll('.accordion');
|
||||
accordions.forEach(accordion => {
|
||||
accordion.addEventListener('click', (event) => {
|
||||
const summary = event.target.closest('summary');
|
||||
if (!summary) return;
|
||||
const details = summary.closest('details');
|
||||
if (!details) return;
|
||||
accordion.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== details) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,26 +0,0 @@
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
<section id="alert-dialog" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Alert Dialog</h2>
|
||||
<a href="/components/alert-dialog" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="document.getElementById('alert-dialog-demo').close()">Cancel</button>
|
||||
<button class="btn-primary" onclick="document.getElementById('alert-dialog-demo').close()">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="alert-dialog-demo",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=false,
|
||||
close_on_overlay_click=false
|
||||
) }}
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,72 +0,0 @@
|
||||
<section id="alert" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Alert</h2>
|
||||
<a href="/components/alert" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="grid max-w-xl items-start gap-4">
|
||||
<div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "bookmark-check" %}
|
||||
<section>This is an alert with icon, description and no title.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
<section>This one has a description only. No title. No icon.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "popcorn" %}
|
||||
<h2>Let's try one with icon and title.</h2>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "shield-alert" %}
|
||||
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "gift" %}
|
||||
<section>This is a very long alert description that demonstrates how the component handles extended text content and potentially wraps across multiple lines</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This is an extremely long alert title that spans multiple lines to demonstrate how the component handles very lengthy headings while maintaining readability and proper text wrapping behavior</h2>
|
||||
<section>This is an equally long description that contains detailed information about the alert. It shows how the component can accommodate extensive content while preserving proper spacing, alignment, and readability across different screen sizes and viewport widths. This helps ensure the user experience remains consistent regardless of the content length.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>
|
||||
<p>Please verify your billing information and try again.</p>
|
||||
<ul>
|
||||
<li>Check your card details</li>
|
||||
<li>Ensure sufficient funds</li>
|
||||
<li>Verify billing address</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="alert border-amber-50 bg-amber-50 text-amber-900 dark:border-amber-950 dark:bg-amber-950 dark:text-amber-100">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Plot Twist: This Alert is Actually Amber!</h2>
|
||||
<section>This one has custom colors for light and dark mode.</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,37 +0,0 @@
|
||||
<section id="avatar" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Avatar</h2>
|
||||
<a href="/components/avatar" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-row flex-wrap items-center gap-4">
|
||||
<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
|
||||
<span class="size-8 shrink-0 bg-muted flex items-center justify-center rounded-full">CN</span>
|
||||
|
||||
<img class="size-12 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
|
||||
<img class="size-8 shrink-0 object-cover rounded-lg" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-12 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
|
||||
<div class="flex -space-x-2 hover:space-x-1 [&_img]:ring-background [&_img]:size-12 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full [&_img]:ring-2 [&_img]:grayscale [&_img]:transition-all [&_img]:ease-in-out [&_img]:duration-300">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,47 +0,0 @@
|
||||
<section id="badge" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Badge</h2>
|
||||
<a href="/components/badge" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<span class="badge">Primary</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "check" %}
|
||||
Badge
|
||||
</span>
|
||||
<span class="badge-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
Alert
|
||||
</span>
|
||||
<span class="badge rounded-full min-w-5 px-1">8</span>
|
||||
<span class="badge-destructive rounded-full min-w-5 px-1">99</span>
|
||||
<span class="badge-outline rounded-full min-w-5 px-1 font-mono tabular-nums">20+</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<a href="#" class="badge">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-secondary">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-destructive">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-outline">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,42 +0,0 @@
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
<section id="breadcrumb" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Breadcrumb</h2>
|
||||
<a href="/components/breadcrumb" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{{ dropdown_menu(
|
||||
id="demo-breadcrumb-menu",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"},
|
||||
popover_attrs={"class": "p-1"},
|
||||
items=[
|
||||
{ label: "Documentation" },
|
||||
{ label: "Themes" },
|
||||
{ label: "GitHub" }
|
||||
]
|
||||
) }}
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,101 +0,0 @@
|
||||
<section id="button" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Button</h2>
|
||||
<a href="/components/button" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-primary">Primary</button>
|
||||
<button class="btn-outline">Outline</button>
|
||||
<button class="btn-ghost">Ghost</button>
|
||||
<button class="btn-destructive">
|
||||
{% lucide "send" %}
|
||||
Danger
|
||||
</button>
|
||||
<button class="btn-secondary">Secondary</button>
|
||||
<button class="btn-link">Link</button>
|
||||
<button class="btn-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-sm-primary">Primary</button>
|
||||
<button class="btn-sm-outline">Outline</button>
|
||||
<button class="btn-sm-ghost">Ghost</button>
|
||||
<button class="btn-sm-destructive">Danger</button>
|
||||
<button class="btn-sm-secondary">Secondary</button>
|
||||
<button class="btn-sm-link">Link</button>
|
||||
<button class="btn-sm-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-sm-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-sm-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-lg-primary">Primary</button>
|
||||
<button class="btn-lg-outline">Outline</button>
|
||||
<button class="btn-lg-ghost">Ghost</button>
|
||||
<button class="btn-lg-destructive">
|
||||
{% lucide "send" %}
|
||||
Danger
|
||||
</button>
|
||||
<button class="btn-lg-secondary">Secondary</button>
|
||||
<button class="btn-lg-link">Link</button>
|
||||
<button class="btn-lg-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-lg-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-lg-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-icon-primary">
|
||||
{% lucide "download" %}
|
||||
</button>
|
||||
<button class="btn-icon-secondary">
|
||||
{% lucide "upload" %}
|
||||
</button>
|
||||
<button class="btn-icon-outline">
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-icon-ghost">
|
||||
{% lucide "ellipsis" %}
|
||||
</button>
|
||||
<button class="btn-icon-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
</button>
|
||||
<button class="btn-icon-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,147 +0,0 @@
|
||||
<section id="card" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Card</h2>
|
||||
<a href="/components/card" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col items-start gap-4">
|
||||
<div class="card w-full max-w-sm">
|
||||
<header>
|
||||
<h2>Login to your account</h2>
|
||||
<p>Enter your details below to login to your account</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-card-form-email">Email</label>
|
||||
<input class="input" type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="label" for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ml-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input class="input" type="password" id="demo-card-form-password">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer class="flex flex-col items-center gap-2">
|
||||
<button type="button" class="btn w-full">Login</button>
|
||||
<button type="button" class="btn-outline w-full">Login with Google</button>
|
||||
<p class="mt-4 text-center text-sm">Don't have an account? <a href="#" class="underline-offset-4 hover:underline">Sign up</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Meeting Notes</h2>
|
||||
<p>Transcript from the meeting with the client.</p>
|
||||
</header>
|
||||
<section class="text-sm">
|
||||
<p>Client requested dashboard redesign with focus on mobile responsiveness.</p>
|
||||
<ol class="mt-4 flex list-decimal flex-col gap-2 pl-6">
|
||||
<li>New analytics widgets for daily/weekly metrics</li>
|
||||
<li>Simplified navigation menu</li>
|
||||
<li>Dark mode support</li>
|
||||
<li>Timeline: 6 weeks</li>
|
||||
<li>Follow-up meeting scheduled for next Tuesday</li>
|
||||
</ol>
|
||||
</section>
|
||||
<footer class="flex items-center">
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Is this an image?</h2>
|
||||
<p>This is a card with an image.</p>
|
||||
</header>
|
||||
<section class="px-0">
|
||||
<img
|
||||
alt="Photo by Drew Beamer"
|
||||
loading="lazy"
|
||||
width="500"
|
||||
height="500"
|
||||
class="aspect-video object-cover" style="color:transparent"
|
||||
srcset="
|
||||
https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=640&q=75 1x,
|
||||
https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=1080&q=75 2x
|
||||
"
|
||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=1080&q=75"
|
||||
/>
|
||||
</section>
|
||||
<footer class="flex items-center gap-2">
|
||||
<span class="badge-outline">
|
||||
{% lucide "bed" %}
|
||||
1
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "bath" %}
|
||||
2
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "land-plot" %}
|
||||
350m²
|
||||
</span>
|
||||
<span class="ml-auto font-medium tabular-nums">
|
||||
$135,000
|
||||
</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full flex-wrap items-start gap-8 md:*:[.card]:basis-1/4">
|
||||
<div class="card">
|
||||
<section>Content Only</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Header Only</h2>
|
||||
<p>This is a card with a header and a description.</p>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Header and Content</h2>
|
||||
<p>This is a card with a header and a content.</p>
|
||||
</header>
|
||||
<section>Content only.</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<footer>Footer Only</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Header + Footer</h2>
|
||||
<p>This is a card with a header and a footer.</p>
|
||||
</header>
|
||||
<footer>Footer</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<section>Content</section>
|
||||
<footer>Footer</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Header + Content + Footer</h2>
|
||||
<p>This is a card with a header, content and footer.</p>
|
||||
</header>
|
||||
<section>Content</section>
|
||||
<footer>Footer</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,34 +0,0 @@
|
||||
<section id="checkbox" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Checkbox</h2>
|
||||
<a href="/components/checkbox" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6 max-w-lg">
|
||||
<label class="label gap-3">
|
||||
<input type="checkbox" class="input">
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
<div class="flex items-start gap-3">
|
||||
<input type="checkbox" id="demo-checkbox-label-and-description" class="input">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-checkbox-label-and-description" class="label">Accept terms and conditions</label>
|
||||
<p class="text-muted-foreground text-sm">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</div>
|
||||
<label class="label gap-3">
|
||||
<input type="checkbox" class="input" disabled>
|
||||
Enable notifications
|
||||
</label>
|
||||
<label class="flex items-start gap-3 border p-3 hover:bg-accent/50 rounded-lg has-[input[type='checkbox']:checked]:border-blue-600 has-[input[type='checkbox']:checked]:bg-blue-50 dark:has-[input[type='checkbox']:checked]:border-blue-900 dark:has-[input[type='checkbox']:checked]:bg-blue-950">
|
||||
<input type="checkbox" class="input checked:bg-blue-600 checked:border-blue-600 dark:checked:bg-blue-700 dark:checked:border-blue-700 checked:after:bg-white" checked>
|
||||
<div class="grid gap-2">
|
||||
<h2 class="text-sm leading-none font-medium">Enable notifications</h2>
|
||||
<p class="text-muted-foreground text-sm">You can enable or disable notifications at any time.</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,83 +0,0 @@
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
<section id="combobox" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Combobox</h2>
|
||||
<a href="/components/combobox" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% call select(
|
||||
popover_attrs={"class": "w-48"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
|
||||
{% set options_timezones = [
|
||||
{
|
||||
label: "Americas",
|
||||
timezones: [
|
||||
{ value: "America/New_York", label: "(GMT-5) New York" },
|
||||
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
||||
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
||||
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
||||
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
||||
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Europe",
|
||||
timezones: [
|
||||
{ value: "Europe/London", label: "(GMT+0) London" },
|
||||
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
||||
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
||||
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
||||
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
||||
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Asia/Pacific",
|
||||
timezones: [
|
||||
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
||||
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
||||
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
||||
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
||||
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
||||
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" }
|
||||
]
|
||||
}
|
||||
] %}
|
||||
{% call select(
|
||||
popover_attrs={"class": "w-72"},
|
||||
listbox_attrs={"data-empty": "No timezone found."},
|
||||
is_combobox=true
|
||||
) %}
|
||||
<div class="max-h-64 overflow-y-auto scrollbar">
|
||||
{% for group in options_timezones %}
|
||||
<div role="group" aria-labelledby="demo-combobox-timezones-group-{{ loop.index0 }}">
|
||||
<span id="demo-combobox-timezones-group-{{ loop.index0 }}" role="heading">{{ group.label }}</span>
|
||||
{% for timezone in group.timezones %}
|
||||
<div role="option" data-value="{{ timezone.value}}">
|
||||
{{ timezone.label }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="option">
|
||||
{% lucide "circle-plus" %}
|
||||
Create timezone
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,65 +0,0 @@
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
<section id="dialog" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Dialog</h2>
|
||||
<a href="/components/dialog" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="document.getElementById('demo-dialog-edit-profile').close()">Cancel</button>
|
||||
<button class="btn" onclick="document.getElementById('demo-dialog-edit-profile').close()">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-example",
|
||||
title="Scrollable Content",
|
||||
description="This is a dialog with scrollable content.",
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,294 +0,0 @@
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
<section id="dropdown-menu" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Dropdown Menu</h2>
|
||||
<a href="/components/dropdown-menu" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu-default",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<div role="heading" id="account-options">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu-checkboxes",
|
||||
trigger="Checkboxes",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<div role="heading" id="account-options">Account Options</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "settings" %}
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="appearance-options">
|
||||
<div role="heading" id="appearance-options">Appearance</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
aria-disabled="true"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-menu-checkboxes');
|
||||
const checkboxes = dropdownMenu.querySelectorAll('div[role="menuitemcheckbox"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('click', () => {
|
||||
const isChecked = checkbox.getAttribute('aria-checked') === 'true';
|
||||
checkbox.setAttribute('aria-checked', !isChecked);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% set profile_menu %}
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
{% lucide "star" %}
|
||||
Upgrade to Pro
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Account
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "bell" %}
|
||||
Notifications
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu-radio-group",
|
||||
trigger="Radio Group",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="position-options">
|
||||
<span id="position-options" role="heading">Panel Position</span>
|
||||
<hr role="separator">
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-menu-radio-group');
|
||||
const radioButtons = dropdownMenu.querySelectorAll('div[role="menuitemradio"]');
|
||||
radioButtons.forEach(radioButton => {
|
||||
radioButton.addEventListener('click', () => {
|
||||
radioButtons.forEach(radioButton => {
|
||||
radioButton.setAttribute('aria-checked', 'false');
|
||||
});
|
||||
radioButton.setAttribute('aria-checked', 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down", { "class": "text-muted-foreground ml-auto" } %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-outline h-12 justify-start px-2 md:max-w-[200px]"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost rounded-full size-8"},
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost"},
|
||||
popover_attrs={"class": "min-w-32"}
|
||||
) %}
|
||||
<div role="menuitem">
|
||||
{% lucide "pencil" %}
|
||||
Edit
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "share" %}
|
||||
Share
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Delete
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,106 +0,0 @@
|
||||
<section id="form" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Form</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<form class="grid w-full max-w-sm gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-form-text">Username</label>
|
||||
<input class="input" type="text" id="demo-form-text" placeholder="hunvreus">
|
||||
<p class="text-muted-foreground text-sm">This is your public display name.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-form-select">Email</label>
|
||||
<select class="select" id="demo-form-select">
|
||||
<option value="bob@example.com">m@example.com</option>
|
||||
<option value="alice@example.com">m@google.com</option>
|
||||
<option value="john@example.com">m@support.com</option>
|
||||
</select>
|
||||
<p class="text-muted-foreground text-sm">You can manage email addresses in your email settings.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-form-text">Bio</label>
|
||||
<textarea class="textarea" id="demo-form-textarea" placeholder="I like to..." rows="3"></textarea>
|
||||
<p class="text-muted-foreground text-sm">You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<label class="label" for="demo-form-radio">Notify me about...</label>
|
||||
<fieldset id="demo-form-radio" class="grid gap-3">
|
||||
<label class="label font-normal"><input class="input" type="radio" name="demo-form-radio" value="1">All new messages</label>
|
||||
<label class="label font-normal"><input class="input" type="radio" name="demo-form-radio" value="2">Direct messages and mentions</label>
|
||||
<label class="label font-normal"><input class="input" type="radio" name="demo-form-radio" value="3" >Nothing</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
|
||||
<input class="input" type="checkbox" id="demo-form-checkbox">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="demo-form-checkbox" class="label text-sm font-normal leading-snug normal-case tracking-normal">Use different settings for my mobile devices</label>
|
||||
<p class="text-muted-foreground text-sm leading-snug">You can manage your mobile notifications in the mobile settings page.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<header>
|
||||
<label for="demo-form-checkboxes" class="label text-base leading-normal">Sidebar</label>
|
||||
<p class="text-muted-foreground text-sm">Select the items you want to display in the sidebar.</p>
|
||||
</header>
|
||||
<fieldset id="demo-form-checkboxes" class="flex flex-col gap-2">
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="input" type="checkbox" name="demo-form-checkboxes" value="1" checked>
|
||||
Recents
|
||||
</label>
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="input" type="checkbox" name="demo-form-checkboxes" value="2" checked>
|
||||
Home
|
||||
</label>
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="input" type="checkbox" name="demo-form-checkboxes" value="3">
|
||||
Applications
|
||||
</label>
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="input" type="checkbox" name="demo-form-checkboxes" value="4">
|
||||
Desktop
|
||||
</label>
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="input" type="checkbox" name="demo-form-checkboxes" value="5">
|
||||
Download
|
||||
</label>
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="input" type="checkbox" name="demo-form-checkboxes" value="6">
|
||||
Documents
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-form-date">Date of birth</label>
|
||||
<input class="input" type="date" id="demo-form-date">
|
||||
<p class="text-muted-foreground text-sm">Your date of birth is used to calculate your age.</p>
|
||||
</div>
|
||||
|
||||
<section class="grid gap-4">
|
||||
<h3 class="text-lg font-medium">Email Notifications</h3>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label for="demo-form-switch" class="label text-sm font-normal leading-normal normal-case tracking-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input class="input" type="checkbox" id="demo-form-switch" role="switch">
|
||||
</div>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5 opacity-60">
|
||||
<label for="demo-form-switch-disabled" class="label text-sm font-normal leading-normal normal-case tracking-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input class="input" type="checkbox" id="demo-form-switch-disabled" role="switch" disabled>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,27 +0,0 @@
|
||||
<section id="input" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Input</h2>
|
||||
<a href="/components/input" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<input type="text" class="input" placeholder="Text">
|
||||
<input type="text" class="input" disabled placeholder="Disabled">
|
||||
<input type="text" class="input" aria-invalid="true" placeholder="Error">
|
||||
<input type="email" class="input" placeholder="Email">
|
||||
<input type="password" class="input" placeholder="Password">
|
||||
<input type="number" class="input" placeholder="Number">
|
||||
<input type="file" class="input">
|
||||
<input type="tel" class="input" placeholder="Tel">
|
||||
<input type="url" class="input" placeholder="URL">
|
||||
<input type="search" class="input" placeholder="Search">
|
||||
<input type="date" class="input">
|
||||
<input type="datetime-local" class="input">
|
||||
<input type="month" class="input">
|
||||
<input type="week" class="input">
|
||||
<input type="time" class="input">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,30 +0,0 @@
|
||||
<section id="label" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Label</h2>
|
||||
<a href="/components/label" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<div class="grid w-full max-w-sm gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" id="label-demo-terms" class="input">
|
||||
<label for="label-demo-terms" class="label">Accept terms and conditions</label>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-text" class="label">Username</label>
|
||||
<input type="text" id="label-demo-text" class="input" placeholder="Username">
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-disabled" class="label">Disabled</label>
|
||||
<input type="text" id="label-demo-disabled" class="peer input" placeholder="Disabled" disabled>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-textarea" class="label">Message</label>
|
||||
<textarea id="label-demo-textarea" class="textarea" placeholder="Message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,34 +0,0 @@
|
||||
<section id="pagination" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Pagination</h2>
|
||||
<a href="/components/pagination" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="inline-flex">
|
||||
<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
|
||||
<ul class="flex flex-row items-center gap-1">
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">{% lucide "chevron-left" %} Previous</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost size-9">1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-outline size-9">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost size-9">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">{% lucide "ellipsis" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">Next {% lucide "chevron-right" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,43 +0,0 @@
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
<section id="popover" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Popover</h2>
|
||||
<a href="/components/popover" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
{% call popover(
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-80"}
|
||||
) %}
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
<h4 class="leading-none font-medium">Dimensions</h4>
|
||||
<p class="text-muted-foreground text-sm">Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label class="label" for="demo-popover-width">Width</label>
|
||||
<input type="text" id="demo-popover-width" value="100%" class="input col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label class="label" for="demo-popover-max-width">Max. width</label>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="input col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label class="label" for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="input col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label class="label" for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="input col-span-2 h-8"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,33 +0,0 @@
|
||||
<section id="radio-group" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Radio Group</h2>
|
||||
<a href="/components/radio-group" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<fieldset class="grid gap-3">
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="default" class="input">Default</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="comfortable" class="input">Comfortable</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="compact" class="input">Compact</label>
|
||||
</fieldset>
|
||||
<fieldset class="grid gap-3 max-w-sm">
|
||||
<label class="label gap-3 items-start hover:bg-accent/50 rounded-lg border p-4 has-[input[type='radio']:checked]:border-green-600 has-[input[type='radio']:checked]:bg-green-50 dark:has-[input[type='radio']:checked]:border-green-900 dark:has-[input[type='radio']:checked]:bg-green-950">
|
||||
<input type="radio" name="demo-radio-group-styled" value="no" class="input checked:bg-green-600 checked:border-green-600 dark:checked:bg-input/30 checked:before:bg-background dark:checked:before:bg-primary">
|
||||
<div class="grid gap-1 font-normal">
|
||||
<h2 class="font-medium">Starter Plan</h2>
|
||||
<p class="text-muted-foreground leading-snug">Perfect for small businesses getting started with our platform</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label gap-3 items-start hover:bg-accent/50 rounded-lg border p-4 has-[input[type='radio']:checked]:border-green-600 has-[input[type='radio']:checked]:bg-green-50 dark:has-[input[type='radio']:checked]:border-green-900 dark:has-[input[type='radio']:checked]:bg-green-950">
|
||||
<input type="radio" name="demo-radio-group-styled" value="no" class="input checked:bg-green-600 checked:border-green-600 dark:checked:bg-input/30 checked:before:bg-background dark:checked:before:bg-primary">
|
||||
<div class="grid gap-1 font-normal">
|
||||
<h2 class="font-medium">Pro Plan</h2>
|
||||
<p class="text-muted-foreground leading-snug">Advanced features for growing businesses with higher demands</p>
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,99 +0,0 @@
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
<section id="select" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Select</h2>
|
||||
<a href="/components/select" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4 gap">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<select class="select w-[180px]">
|
||||
<optgroup label="Fruits">
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
<option>Blueberry</option>
|
||||
</optgroup>
|
||||
<optgroup label="Grapes">
|
||||
<option>Pineapple</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select class="select w-[180px]" disabled>
|
||||
<option>Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
{{ select(
|
||||
id="select-default",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
selected="blueberry",
|
||||
items=[
|
||||
{
|
||||
type: "group",
|
||||
label: "Fruits",
|
||||
items: [
|
||||
{ type: "item", label: "Apple", value: "apple" },
|
||||
{ type: "item", label: "Banana", value: "banana" },
|
||||
{ type: "item", label: "Blueberry", value: "blueberry" }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "group",
|
||||
label: "Grapes",
|
||||
items: [
|
||||
{ type: "item", label: "Pineapple", value: "pineapple" }
|
||||
]
|
||||
}
|
||||
]
|
||||
) }}
|
||||
{% call select(
|
||||
id="select-scrollbar",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"}
|
||||
) %}
|
||||
{% for i in range(0, 99) %}
|
||||
<div role="option" data-value="item-{{ i }}">Item {{ i }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
|
||||
{% call select(
|
||||
id="select-disabled",
|
||||
trigger_attrs={"disabled": "disabled", "class": "w-[180px]"}
|
||||
) %}
|
||||
<div role="option" data-value="disabled">Disabled</div>
|
||||
{% endcall %}
|
||||
|
||||
|
||||
{% call select(
|
||||
id="select-with-icon",
|
||||
name="chart-type",
|
||||
trigger_attrs={'class': 'w-[180px]'}
|
||||
) %}
|
||||
<div type="button" role="option" data-value="bar">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-bar", {"class": "text-muted-foreground"} %}
|
||||
Bar
|
||||
</span>
|
||||
</div>
|
||||
<div type="button" role="option" data-value="line">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-line", {"class": "text-muted-foreground"} %}
|
||||
Line
|
||||
</span>
|
||||
</div>
|
||||
<div type="button" role="option" data-value="pie">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-pie", {"class": "text-muted-foreground"} %}
|
||||
Pie
|
||||
</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,40 +0,0 @@
|
||||
<section id="skeleton" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Skeleton</h2>
|
||||
<a href="/components/skeleton" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="bg-accent animate-pulse size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-[150px]"></div>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex max-sm:flex-col gap-4 w-full">
|
||||
<div class="card w-full @md:w-auto @md:min-w-sm">
|
||||
<header>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-2/3"></div>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-1/2"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="bg-accent animate-pulse rounded-md aspect-square w-full"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="card w-full @md:w-auto @md:min-w-sm">
|
||||
<header>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-2/3"></div>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-1/2"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="bg-accent animate-pulse rounded-md aspect-square w-full"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,38 +0,0 @@
|
||||
<section id="slider" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Slider</h2>
|
||||
<a href="/components/slider" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="max-w-sm">
|
||||
<input
|
||||
type="range"
|
||||
class="input w-full"
|
||||
min="0"
|
||||
max="27"
|
||||
value="12"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const sliders = document.querySelectorAll('input[type="range"].input');
|
||||
if (!sliders) return;
|
||||
|
||||
const updateSlider = (el) => {
|
||||
const min = parseFloat(el.min || 0);
|
||||
const max = parseFloat(el.max || 100);
|
||||
const value = parseFloat(el.value);
|
||||
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
|
||||
el.style.setProperty('--slider-value', `${percent}%`);
|
||||
};
|
||||
|
||||
sliders.forEach(slider => {
|
||||
updateSlider(slider);
|
||||
slider.addEventListener('input', (event) => updateSlider(event.target));
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -1,27 +0,0 @@
|
||||
<section id="switch" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Switch</h2>
|
||||
<a href="/components/switch" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="inline-flex flex-col gap-y-6">
|
||||
<label class="label">
|
||||
<input type="checkbox" name="switch" role="switch" class="input">
|
||||
Airplane Mode
|
||||
</label>
|
||||
<label class="label">
|
||||
<input type="checkbox" name="switch" role="switch" class="input checked:bg-blue-500 dark:checked:bg-blue-600" checked>
|
||||
Bluetooth
|
||||
</label>
|
||||
<label class="label gap-6 leading-none border rounded-lg p-4 has-[input[type='checkbox']:checked]:border-blue-600">
|
||||
<div class="grid gap-1">
|
||||
<h2 class="font-medium text-sm">Share across devices</h2>
|
||||
<p class="text-muted-foreground text-sm">Focus is shared across devices, and turns off when you leave the app.</p>
|
||||
</div>
|
||||
<input type="checkbox" name="switch" role="switch" class="input checked:bg-blue-500 dark:checked:bg-blue-600">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,74 +0,0 @@
|
||||
<section id="table" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Table</h2>
|
||||
<a href="/components/table" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
|
||||
<div class="relative w-full overflow-x-auto">
|
||||
<table class="table">
|
||||
<caption>A list of your recent invoices.</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice</th>
|
||||
<th>Status</th>
|
||||
<th>Method</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-medium">INV001</td>
|
||||
<td>Paid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$250.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV002</td>
|
||||
<td>Pending</td>
|
||||
<td>PayPal</td>
|
||||
<td class="text-right">$150.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV003</td>
|
||||
<td>Unpaid</td>
|
||||
<td>Bank Transfer</td>
|
||||
<td class="text-right">$350.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV004</td>
|
||||
<td>Paid</td>
|
||||
<td>Paypal</td>
|
||||
<td class="text-right">$450.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV005</td>
|
||||
<td>Paid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$550.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV006</td>
|
||||
<td>Pending</td>
|
||||
<td>Bank Transfer</td>
|
||||
<td class="text-right">$200.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV007</td>
|
||||
<td>Unpaid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$300.00</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3">Total</td>
|
||||
<td class="text-right">$2,500.00</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,105 +0,0 @@
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
<section id="tabs" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Tabs</h2>
|
||||
<a href="/components/tabs" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
{% set account_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-tabs-account-name">Name</label>
|
||||
<input class="input" type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-tabs-account-username">Username</label>
|
||||
<input class="input" type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set password_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-tabs-password-current">Current password</label>
|
||||
<input class="input" type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-tabs-password-new">New password</label>
|
||||
<input class="input" type="password" id="demo-tabs-password-new"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save Password</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set tabsets = [
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets,
|
||||
main_attrs={ "class": "max-w-[300px]" },
|
||||
tablist_attrs={ "class": "w-full" }
|
||||
) }}
|
||||
|
||||
{% set tabsets = [
|
||||
{ tab: "Home" },
|
||||
{ tab: "Settings" }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-without-panels',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
|
||||
{% set tabsets = [
|
||||
{ tab: "Home" },
|
||||
{ tab: "Disabled", tab_attrs: { "disabled": "disabled" } }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-disabled',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
|
||||
{% set label_preview %}
|
||||
{% lucide 'app-window' %}
|
||||
Preview
|
||||
{% endset %}
|
||||
{% set label_code %}
|
||||
{% lucide 'code' %}
|
||||
Code
|
||||
{% endset %}
|
||||
{% set tabsets = [
|
||||
{ tab: label_preview },
|
||||
{ tab: label_code }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-icons',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,27 +0,0 @@
|
||||
<section id="textarea" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Textarea</h2>
|
||||
<a href="/components/textarea" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-10">
|
||||
<textarea class="textarea" placeholder="Type your message here"></textarea>
|
||||
<textarea class="textarea" placeholder="Type your message here" aria-invalid="true"></textarea>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-label" class="label">Label</label>
|
||||
<textarea id="textarea-demo-label" class="textarea" placeholder="Type your message here"></textarea>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-label-and-description" class="label">With label and description</label>
|
||||
<textarea id="textarea-demo-label-and-description" class="textarea" placeholder="Type your message here"></textarea>
|
||||
<p class="text-muted-foreground text-sm">Type your message and press enter to send.</p>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-disabled" class="label">Disabled</label>
|
||||
<textarea id="textarea-demo-disabled" class="textarea" placeholder="Type your message here" disabled></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,24 +0,0 @@
|
||||
<section id="toast" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Toast</h2>
|
||||
<a href="/components/toast" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button class="btn-outline" hx-trigger="click" hx-select="unset" hx-get="/fragments/toast/success" hx-target="#toaster" hx-swap="beforeend">
|
||||
Success
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-select="unset" hx-get="/fragments/toast/error" hx-target="#toaster" hx-swap="beforeend">
|
||||
Error
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-select="unset" hx-get="/fragments/toast/info" hx-target="#toaster" hx-swap="beforeend">
|
||||
Info
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-select="unset" hx-get="/fragments/toast/warning" hx-target="#toaster" hx-swap="beforeend">
|
||||
Warning
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,24 +0,0 @@
|
||||
<section id="tooltip" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Tooltip</h2>
|
||||
<a href="/components/tooltip" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<button type="button" class="btn-outline" data-tooltip="Top tooltip">
|
||||
Top
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Right tooltip" data-side="right">
|
||||
Right
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Bottom tooltip" data-side="bottom">
|
||||
Bottom
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Left tooltip" data-side="left">
|
||||
Left
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,62 +0,0 @@
|
||||
{% from "sidebar.njk" import sidebar %}
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
{% set footer %}
|
||||
{% set trigger %}
|
||||
<img src="https://github.com/hunvreus.png" class="rounded-lg shrink-0 size-8" alt="Ronan Berder">
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Ronan Berder</span>
|
||||
<span class="truncate text-xs">@hunvreus</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
trigger=trigger,
|
||||
trigger_attrs={
|
||||
"class": "btn-ghost p-2 h-12 w-full flex items-center justify-start",
|
||||
"data-keep-mobile-sidebar-open": ""
|
||||
},
|
||||
popover_attrs={
|
||||
"data-side": "top",
|
||||
"class": "w-[271px] md:w-[239px]"
|
||||
}
|
||||
) %}
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
<h2 class="font-semibold">I hope you like Basecoat...</h2>
|
||||
<p class="text-muted-foreground text-sm">My name is <a href="https://ronanberder.com" target="_blank" rel="noopener">Ronan</a> and I made this. If you find it useful, please consider sponsoring me on GitHub or following me on X.</p>
|
||||
</header>
|
||||
<footer class="grid gap-2">
|
||||
<a href="https://github.com/sponsors/hunvreus" class="btn-sm" target="_blank" rel="noopener">Sponsor me on GitHub</a>
|
||||
<a href="https://x.com/hunvreus" class="btn-sm-outline" target="_blank" rel="noopener">Follow me on X</a>
|
||||
</footer>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set header %}
|
||||
<a href="/" class="btn-ghost p-2 h-12 w-full justify-start">
|
||||
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
{% if site.logo and site.logo.svg %}
|
||||
{{ site.logo.svg | safe }}
|
||||
{% else %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-semibold">{{ site.title }}</span>
|
||||
<span class="truncate text-xs text-muted-foreground">v{{ pkg.version }}</span>
|
||||
</div>
|
||||
</a>
|
||||
{% endset %}
|
||||
|
||||
{% set docsMenu = docs.menu if docs and docs.menu else menu %}
|
||||
|
||||
{{ sidebar(
|
||||
id="sidebar",
|
||||
label="Sidebar navigation",
|
||||
header=header,
|
||||
footer=footer,
|
||||
content_attrs={ "class": "scrollbar" },
|
||||
menu=docsMenu | sidebarMenu(collections)
|
||||
) }}
|
||||
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discord</title><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
@import "../../../../packages/css/dist/basecoat-luma.cdn.css";
|
||||
@@ -0,0 +1 @@
|
||||
@import "../../../../packages/css/dist/basecoat-lyra.cdn.css";
|
||||
@@ -0,0 +1 @@
|
||||
@import "../../../../packages/css/dist/basecoat-maia.cdn.css";
|
||||
@@ -0,0 +1 @@
|
||||
@import "../../../../packages/css/dist/basecoat-mira.cdn.css";
|
||||
@@ -0,0 +1 @@
|
||||
@import "../../../../packages/css/dist/basecoat-nova.cdn.css";
|
||||
@@ -0,0 +1 @@
|
||||
@import "../../../../packages/css/dist/basecoat-rhea.cdn.css";
|
||||
@@ -0,0 +1 @@
|
||||
@import "../../../../packages/css/dist/basecoat-sera.cdn.css";
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: CLI
|
||||
description: Use the Basecoat CLI and template macros.
|
||||
icon: square-terminal
|
||||
toc:
|
||||
- label: Use the CLI
|
||||
id: cli
|
||||
- label: Use Nunjucks or Jinja macros
|
||||
id: macros
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
<h2 id="cli"><a href="#cli">Use the CLI</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>If you're using Nunjucks or Jinja, you can use the CLI to install the CSS and macros for complex components (e.g. <a href="/components/dialog">Dialog</a>). It will copy the JS and <code>.html.jinja</code> or <code>.njk</code> macros into your code.</p>
|
||||
<p>For example, to add the Dialog component, run one of the following commands:</p>
|
||||
</div>
|
||||
|
||||
{% set code_npm_exec %}npx basecoat-cli add dialog{% endset %}
|
||||
{% set code_pnpm_exec %}pnpm dlx basecoat-cli add dialog{% endset %}
|
||||
{% set code_bun_exec %}bunx basecoat-cli add dialog{% endset %}
|
||||
{% set code_yarn_exec %}yarn dlx basecoat-cli add dialog{% endset %}
|
||||
{% set exec_tabsets = [
|
||||
{ tab: "npm", panel: code_block(code_npm_exec, class="") },
|
||||
{ tab: "pnpm", panel: code_block(code_pnpm_exec, class="") },
|
||||
{ tab: "bun", panel: code_block(code_bun_exec, class="") },
|
||||
{ tab: "yarn", panel: code_block(code_yarn_exec, class="") }
|
||||
] %}
|
||||
{{ tabs(id="cli-execution", tabsets=exec_tabsets, label="Execute CLI commands to add components:") }}
|
||||
|
||||
<div class="prose my-6">
|
||||
<p>You will be asked to confirm the template engine and destination directory for the JavaScript and macros.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="macros"><a href="#macros">Use Nunjucks or Jinja macros</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>For more complex components, you can use <a href="https://mozilla.github.io/nunjucks/" target="_blank">Nunjucks</a> or <a href="https://jinja.palletsprojects.com" target="_blank">Jinja</a> macros to help generate the HTML and JavaScript code.</p>
|
||||
<p>To install them, either <a href="https://github.com/hunvreus/basecoat/tree/main/src" target="_blank">copy them directly from the GitHub repository</a> or use <a href="#cli">the CLI</a>.</p>
|
||||
<p>For example, here's how you could use the <code>select()</code> macro to generate the HTML and JavaScript code for a <a href="/components/select">Select</a> component:</p>
|
||||
</div>
|
||||
|
||||
{% set code %}{% raw %}{% call select() %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
<div role="option" data-value="pineapple">Grapes</div>
|
||||
<div role="option" data-value="pineapple">Pineapple</div>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(code, "jinja") }}
|
||||
@@ -0,0 +1,207 @@
|
||||
# CLI
|
||||
|
||||
## Install Components
|
||||
|
||||
Use the CLI to copy Basecoat component assets into your project. It can install CSS, JavaScript, and the matching Nunjucks or Jinja macros for components that ship template helpers.
|
||||
|
||||
<CodeGroup defaultTab="npm">
|
||||
<Code title="npm" lang="bash">{`npx basecoat-cli add dialog`}</Code>
|
||||
<Code title="pnpm" lang="bash">{`pnpm dlx basecoat-cli add dialog`}</Code>
|
||||
<Code title="bun" lang="bash">{`bunx basecoat-cli add dialog`}</Code>
|
||||
<Code title="yarn" lang="bash">{`yarn dlx basecoat-cli add dialog`}</Code>
|
||||
</CodeGroup>
|
||||
|
||||
The CLI asks which template engine you use and where it should copy JavaScript and macro files.
|
||||
|
||||
## Macros
|
||||
|
||||
Nunjucks and Jinja macros are optional helpers for components with larger HTML structures. They generate the same semantic Basecoat markup shown in the component docs.
|
||||
|
||||
<CodeGroup defaultTab="Nunjucks">
|
||||
<Code title="Nunjucks" lang="jinja">{`{% from "select.njk" import select %}
|
||||
|
||||
{{ select(
|
||||
name="fruit",
|
||||
items=[
|
||||
{ value: "apple", label: "Apple" },
|
||||
{ value: "banana", label: "Banana" },
|
||||
{ value: "blueberry", label: "Blueberry" }
|
||||
]
|
||||
) }}`}</Code>
|
||||
<Code title="Jinja" lang="jinja">{`{% from "select.html.jinja" import select %}
|
||||
|
||||
{{ select(
|
||||
name="fruit",
|
||||
items=[
|
||||
{ "value": "apple", "label": "Apple" },
|
||||
{ "value": "banana", "label": "Banana" },
|
||||
{ "value": "blueberry", "label": "Blueberry" }
|
||||
]
|
||||
) }}`}</Code>
|
||||
</CodeGroup>
|
||||
|
||||
### Public Macros
|
||||
|
||||
| Component | Macro | Nunjucks | Jinja |
|
||||
| --- | --- | --- | --- |
|
||||
| [Combobox](/components/combobox) | `combobox()` | [combobox.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/combobox.njk#L18) | [combobox.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/combobox.html.jinja#L4) |
|
||||
| [Command](/components/command) | `command()` | [command.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/command.njk#L12) | [command.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/command.html.jinja#L12) |
|
||||
| [Command](/components/command) | `command_dialog()` | [command.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/command.njk#L79) | [command.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/command.html.jinja#L79) |
|
||||
| [Dialog](/components/dialog) | `dialog()` | [dialog.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk#L19) | [dialog.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/dialog.html.jinja#L19) |
|
||||
| [Dropdown Menu](/components/dropdown-menu) | `dropdown_menu()` | [dropdown-menu.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dropdown-menu.njk#L11) | [dropdown-menu.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/dropdown-menu.html.jinja#L11) |
|
||||
| [Popover](/components/popover) | `popover()` | [popover.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/popover.njk#L10) | [popover.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/popover.html.jinja#L10) |
|
||||
| [Select](/components/select) | `select()` | [select.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/select.njk#L17) | [select.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/select.html.jinja#L16) |
|
||||
| [Sidebar](/components/sidebar) | `sidebar()` | [sidebar.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/sidebar.njk#L16) | [sidebar.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/sidebar.html.jinja#L16) |
|
||||
| [Tabs](/components/tabs) | `tabs()` | [tabs.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/tabs.njk#L16) | [tabs.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/tabs.html.jinja#L17) |
|
||||
| [Toast](/components/toast) | `toaster()` | [toast.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/toast.njk#L11) | [toast.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/toast.html.jinja#L11) |
|
||||
| [Toast](/components/toast) | `toast()` | [toast.njk](https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/toast.njk#L53) | [toast.html.jinja](https://github.com/hunvreus/basecoat/blob/main/src/jinja/toast.html.jinja#L53) |
|
||||
|
||||
Some macro files include internal recursive helpers such as `render_select_items()`. Those helpers are implementation details and are not part of the public API.
|
||||
|
||||
## Macro Reference
|
||||
|
||||
### `combobox()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | generated | Unique combobox id. |
|
||||
| `selected` | `string \| array \| object` | `None` / `none` | Initial selected value or values. Objects can include `value` and `label`. |
|
||||
| `name` | `string` | `None` / `none` | Hidden input name. |
|
||||
| `multiple` | `boolean` | `false` | Enables chip-based multiple selection. |
|
||||
| `placeholder` | `string` | `None` / `none` | Visible input placeholder. |
|
||||
| `close_on_select` | `boolean` | `false` | Closes the popover after each option selection in multiple mode. |
|
||||
| `auto_highlight` | `boolean` | `false` | Highlights the first matching option while filtering. |
|
||||
| `format` | `"value" \| "object"` | `"value"` | Hidden input serialization format. |
|
||||
| `main_attrs` | `object` | `{}` | Extra attributes for the `.combobox` root. |
|
||||
| `input_attrs` | `object` | `{}` | Extra attributes for the visible input or chip input wrapper. |
|
||||
| `popover_attrs` | `object` | `{}` | Extra attributes for the popover element. |
|
||||
| `listbox_attrs` | `object` | `{}` | Extra attributes for the listbox. |
|
||||
| `hidden_input_attrs` | `object` | `{}` | Extra attributes for the hidden input. |
|
||||
|
||||
### `command()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | generated | Unique command id. |
|
||||
| `items` | `array` | `None` / `none` | Optional structured item data. Use caller content when omitted. |
|
||||
| `placeholder` | `string` | `"Type a command or search..."` | Search input placeholder. |
|
||||
| `empty_text` | `string` | `"No results found."` | Empty-state text. |
|
||||
| `main_attrs` | `object` | `{}` | Extra attributes for the `.command` root. |
|
||||
| `input_attrs` | `object` | `{}` | Extra attributes for the search input. |
|
||||
| `menu_attrs` | `object` | `{}` | Extra attributes for the command menu. |
|
||||
|
||||
### `command_dialog()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | generated | Unique command dialog id. |
|
||||
| `items` | `array` | `None` / `none` | Optional structured item data. Use caller content when omitted. |
|
||||
| `placeholder` | `string` | `"Type a command or search..."` | Search input placeholder. |
|
||||
| `empty_text` | `string` | `"No results found."` | Empty-state text. |
|
||||
| `dialog_attrs` | `object` | `{}` | Extra attributes for the native dialog. |
|
||||
| `input_attrs` | `object` | `{}` | Extra attributes for the search input. |
|
||||
| `menu_attrs` | `object` | `{}` | Extra attributes for the command menu. |
|
||||
| `open` | `boolean` | `false` | Renders the dialog open by default. |
|
||||
|
||||
### `dialog()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | generated | Unique dialog id. |
|
||||
| `trigger` | `string` | `None` / `none` | Text or HTML for a generated trigger button. |
|
||||
| `title` | `string` | `None` / `none` | Dialog title. |
|
||||
| `description` | `string` | `None` / `none` | Dialog description. |
|
||||
| `footer` | `string` | `None` / `none` | HTML for the dialog footer. |
|
||||
| `dialog_attrs` | `object` | `{}` | Extra attributes for the native dialog. |
|
||||
| `trigger_attrs` | `object` | `{}` | Extra attributes for the generated trigger button. |
|
||||
| `header_attrs` | `object` | `{}` | Extra attributes for the dialog header. |
|
||||
| `body_attrs` | `object` | `{}` | Extra attributes for the dialog body section. |
|
||||
| `footer_attrs` | `object` | `{}` | Extra attributes for the dialog footer. |
|
||||
| `open` | `boolean` | `false` | Renders the dialog open by default. |
|
||||
| `close_button` | `boolean` | `true` | Includes the generated close button. |
|
||||
| `close_on_overlay_click` | `boolean` | `true` | Adds backdrop-click close behavior. |
|
||||
|
||||
### `dropdown_menu()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `trigger` | `string` | required | HTML for the trigger button. |
|
||||
| `id` | `string` | generated | Unique dropdown menu id. |
|
||||
| `items` | `array` | `None` / `none` | Optional structured menu item data. Use caller content when omitted. |
|
||||
| `main_attrs` | `object` | `{}` | Extra attributes for the `.dropdown-menu` root. |
|
||||
| `trigger_attrs` | `object` | `{}` | Extra attributes for the trigger button. |
|
||||
| `popover_attrs` | `object` | `{}` | Extra attributes for the popover element. |
|
||||
| `menu_attrs` | `object` | `{}` | Extra attributes for the menu element. |
|
||||
|
||||
### `popover()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `trigger` | `string` | required | HTML for the trigger button. |
|
||||
| `id` | `string` | generated | Unique popover id. |
|
||||
| `main_attrs` | `object` | `{}` | Extra attributes for the `.popover` root. |
|
||||
| `trigger_attrs` | `object` | `{}` | Extra attributes for the trigger button. |
|
||||
| `popover_attrs` | `object` | `{}` | Extra attributes for the popover element. |
|
||||
|
||||
### `select()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | generated | Unique select id. |
|
||||
| `selected` | `string \| array` | `None` / `none` | Initial selected value or values. |
|
||||
| `name` | `string` | generated from `id` | Hidden input name. |
|
||||
| `items` | `array` | `None` / `none` | Optional structured item data. Use caller content when omitted. |
|
||||
| `multiple` | `boolean` | `false` | Enables multiple selection. |
|
||||
| `placeholder` | `string` | `None` / `none` | Placeholder text for multiple mode with no selection. |
|
||||
| `close_on_select` | `boolean` | `false` | Closes the popover after each option selection in multiple mode. |
|
||||
| `format` | `"value" \| "object"` | `"value"` | Hidden input serialization format. |
|
||||
| `main_attrs` | `object` | `{}` | Extra attributes for the `.select` root. |
|
||||
| `trigger_attrs` | `object` | `{}` | Extra attributes for the trigger button. |
|
||||
| `popover_attrs` | `object` | `{}` | Extra attributes for the popover element. |
|
||||
| `listbox_attrs` | `object` | `{}` | Extra attributes for the listbox. |
|
||||
| `input_attrs` | `object` | `{}` | Extra attributes for the hidden input. |
|
||||
|
||||
### `sidebar()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | `None` / `none` | Optional sidebar id. Use one when multiple sidebars exist. |
|
||||
| `label` | `string` | `"Sidebar navigation"` | Navigation landmark label. |
|
||||
| `open` | `boolean` | `true` | Initial open state. |
|
||||
| `side` | `"left" \| "right"` | `"left"` | Side of the viewport. |
|
||||
| `header` | `string` | `None` / `none` | HTML for the sidebar header. |
|
||||
| `footer` | `string` | `None` / `none` | HTML for the sidebar footer. |
|
||||
| `menu` | `array` | `None` / `none` | Optional structured menu data. Use caller content when omitted. |
|
||||
| `main_attrs` | `object` | `{}` | Extra attributes for the `.sidebar` root. |
|
||||
| `header_attrs` | `object` | `{}` | Extra attributes for the header. |
|
||||
| `content_attrs` | `object` | `{}` | Extra attributes for the scrollable content section. |
|
||||
| `footer_attrs` | `object` | `{}` | Extra attributes for the footer. |
|
||||
|
||||
### `tabs()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | generated | Unique tabs id. |
|
||||
| `tabsets` | `array` | `[]` | Tab definitions. Each item can include `tab`, `panel`, `tab_attrs`, and `panel_attrs`. |
|
||||
| `main_attrs` | `object` | `{}` | Extra attributes for the `.tabs` root. |
|
||||
| `tablist_attrs` | `object` | `{}` | Extra attributes for the tablist. Use `aria-orientation="vertical"` or `data-variant="line"` here. |
|
||||
| `default_tab_index` | `number` | `1` | One-based index of the initially selected tab. |
|
||||
|
||||
### `toaster()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | `string` | `"toaster"` | Toaster container id. |
|
||||
| `toasts` | `array` | `[]` | Initial toast objects rendered inside the toaster. |
|
||||
| `attrs` | `object` | `{}` | Extra attributes for the `.toaster` container. |
|
||||
|
||||
### `toast()`
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `category` | `"success" \| "error" \| "info" \| "warning"` | `"success"` | Toast category. Controls icon and ARIA role. |
|
||||
| `title` | `string` | `""` | Toast title. |
|
||||
| `description` | `string` | `""` | Toast description. |
|
||||
| `action` | `object` | `None` / `none` | Action button/link config. Supports `label`, `onclick`, and `href`. |
|
||||
| `cancel` | `object` | `None` / `none` | Cancel button/link config. Supports `label`, `onclick`, and `href`. |
|
||||
| `attrs` | `object` | `{}` | Extra attributes for the `.toast` element. |
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Accordion
|
||||
description: A vertically stacked set of interactive headings that each reveal a section of content.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Accordion component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<section class="accordion">
|
||||
<details class="group border-b last:border-b-0 ">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it accessible?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It adheres to the WAI-ARIA design pattern.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it styled?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It comes with default styles that matches the other components' aesthetic.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it animated?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm whitespace-pre-wrap">Yes. It's animated by default, but you can disable it if you prefer.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll('.accordion');
|
||||
accordions.forEach(accordion => {
|
||||
accordion.addEventListener('click', (event) => {
|
||||
const summary = event.target.closest('summary');
|
||||
if (!summary) return;
|
||||
const details = summary.closest('details');
|
||||
if (!details) return;
|
||||
accordion.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== details) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("accordion", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Basecoat already animates <code><details></code> elements by default. The example add some Tailwind CSS utility classes for style and a bit of vanilla JavaScript to handle the open/close state.</p>
|
||||
@@ -0,0 +1,74 @@
|
||||
# Accordion
|
||||
|
||||
<Callout title="No dedicated component" icon="circle-alert">
|
||||
There is no dedicated Accordion component in Basecoat.
|
||||
</Callout>
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<section class="accordion">
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
|
||||
Is it accessible?
|
||||
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 lucide lucide-chevron-down" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It adheres to the WAI-ARIA design pattern.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
|
||||
Is it styled?
|
||||
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 lucide lucide-chevron-down" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It comes with default styles that matches the other components' aesthetic.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline">
|
||||
Is it animated?
|
||||
<svg class="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180 lucide lucide-chevron-down" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm whitespace-pre-wrap">Yes. It's animated by default, but you can disable it if you prefer.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<script>
|
||||
(() => {
|
||||
const accordions = document.querySelectorAll(".accordion");
|
||||
accordions.forEach((accordion) => {
|
||||
accordion.addEventListener("click", (event) => {
|
||||
const summary = event.target.closest("summary");
|
||||
if (!summary) return;
|
||||
const details = summary.closest("details");
|
||||
if (!details) return;
|
||||
accordion.querySelectorAll("details").forEach((detailsEl) => {
|
||||
if (detailsEl !== details) {
|
||||
detailsEl.removeAttribute("open");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Basecoat styles native `<details>` disclosure animations. Use semantic `<details>` and `<summary>` markup, then add your own utility classes for the visual treatment.
|
||||
|
||||
```html
|
||||
<details>
|
||||
<summary>Is it accessible?</summary>
|
||||
<p>Yes. It uses native disclosure semantics.</p>
|
||||
</details>
|
||||
```
|
||||
|
||||
The example above show you how to use a small script to create an accordion with a group of `<details>` elements.
|
||||
@@ -1,120 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Alert Dialog
|
||||
description: A modal dialog that interrupts the user with important content and expects a response.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
{% set code_html %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="document.getElementById('alert-dialog').close()">Cancel</button>
|
||||
<button class="btn-primary" onclick="document.getElementById('alert-dialog').close()">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="alert-dialog",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open alert dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=false,
|
||||
close_on_overlay_click=false
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("alert-dialog", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The Alert Dialog component is identical to <a href="/components/dialog" class="font-medium underline underline-offset-4">Dialog</a> except for two small differences: we do not display a close button and we do not let the user close the dialog when clicking on the backdrop (no <code>onclick</code> attribute on the <code><dialog></code> or the <code><article></code> elements).</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The trigger button to open the alert dialog (using <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal" target="_blank">the native <code>showModal()</code> method</a>).</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>Wraps the whole component and acts as the backdrop. It can also have the following attributes:
|
||||
<ul>
|
||||
<li><code>aria-labelledby="{ TITLE_ID }"</code>: linked to by the <code>aria-labelledby</code> attribute of the dialog.</li>
|
||||
<li><code>aria-describedby="{ DESCRIPTION_ID }"</code>: linked to by the <code>aria-describedby</code> attribute of the <code><dialog></code>.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><article></code></dt>
|
||||
<dd>Wraps the content of the alert dialog.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the header for the alert dialog:
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the alert dialog, must have an <code>id</code> if you use the <code>aria-labelledby</code> attribute on the <code><dialog></code>.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the alert dialog, must have an <code>id</code> if you use the <code>aria-describedby</code> attribute on the <code><dialog></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>For the content.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Usually contains actions.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dialog()</code> Nunjucks or Jinja macro for this component. Make sure to set <code>close_on_overlay_click</code> and <code>close_button</code> to <code>False</code> (or <code>false</code> for Nunjucks).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/dialog.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set footer %}
|
||||
<button class="btn-outline" onclick="document.getElementById('demo-alert-dialog').close()">Cancel</button>
|
||||
<button class="btn-primary" onclick="document.getElementById('demo-alert-dialog').close()">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="demo-alert-dialog",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open Alert Dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=False,
|
||||
close_on_overlay_click=False
|
||||
) }}{% endraw %}
|
||||
{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
@@ -0,0 +1,74 @@
|
||||
# Alert Dialog
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('demo-alert-dialog').showModal()" class="btn-outline">Open alert dialog</button>
|
||||
<dialog id="demo-alert-dialog" class="dialog" aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="alert-dialog-title">Are you absolutely sure?</h2>
|
||||
<p id="alert-dialog-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="document.getElementById('demo-alert-dialog').close()">Cancel</button>
|
||||
<button class="btn-primary" onclick="document.getElementById('demo-alert-dialog').close()">Continue</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dialog" }}>
|
||||
This component can use the `dialog()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
Alert Dialog uses the same `.dialog` class as [Dialog](/components/dialog), but the example omits the close button and does not close on backdrop click.
|
||||
|
||||
```html
|
||||
<button type="button" onclick="document.getElementById('demo-alert-dialog').showModal()" class="btn-outline">Open alert dialog</button>
|
||||
<dialog id="demo-alert-dialog" class="dialog" aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="alert-dialog-title">Are you absolutely sure?</h2>
|
||||
<p id="alert-dialog-description">This action cannot be undone. This will permanently delete your account and remove your data from our servers.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="document.getElementById('demo-alert-dialog').close()">Cancel</button>
|
||||
<button class="btn-primary" onclick="document.getElementById('demo-alert-dialog').close()">Continue</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The trigger button to open the alert dialog (using <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal" target="_blank">the native <code>showModal()</code> method</a>).</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>
|
||||
Native modal dialog. Add <code>aria-labelledby</code> and <code>aria-describedby</code> when title and description are present.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div></code></dt>
|
||||
<dd>
|
||||
Wraps the content of the alert dialog.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>
|
||||
Contains the header for the alert dialog:
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the alert dialog, must have an <code>id</code> if you use the <code>aria-labelledby</code> attribute on the <code><dialog></code>.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the alert dialog, must have an <code>id</code> if you use the <code>aria-describedby</code> attribute on the <code><dialog></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>For the content.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Usually contains actions.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -1,154 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Alert
|
||||
description: Displays a callout for user attention.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: No description
|
||||
id: example-no-description
|
||||
- label: No icon
|
||||
id: example-no-icon
|
||||
- label: Action
|
||||
id: example-action
|
||||
- label: Custom Colors
|
||||
id: example-custom-colors
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}
|
||||
<div class="grid w-full max-w-xl items-start gap-4">
|
||||
<div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
<div class="alert">
|
||||
{% lucide "popcorn" %}
|
||||
<h2>This Alert has a title and an icon. No description.</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
<div class="alert-destructive">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Unable to process your payment.</h2>
|
||||
<section>
|
||||
<p>Please verify your billing information and try again.</p>
|
||||
<ul class="list-inside list-disc text-sm">
|
||||
<li>Check your card details</li>
|
||||
<li>Ensure sufficient funds</li>
|
||||
<li>Verify billing address</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("alert", code | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code>alert</code> or <code>alert-destructive</code>.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="alert"></code></dt>
|
||||
<dd>Main container. Use <code>alert</code> for default styling or <code>alert-destructive</code> for error states.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><svg></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The icon.</dd>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title.</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description.</dd>
|
||||
<dt><code class="highlight language-html"><div data-action></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>An action aligned to the inline end of the alert.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-destructive", code) }}
|
||||
|
||||
<h3 id="example-no-description"><a href="#example-no-description">No description</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div class="alert">
|
||||
{% lucide "shield-alert" %}
|
||||
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-no-description", code) }}
|
||||
|
||||
<h3 id="example-no-icon"><a href="#example-no-icon">No icon</a></h3>
|
||||
|
||||
{% set code %}<div class="alert">
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-no-icon", code) }}
|
||||
|
||||
<h3 id="example-action"><a href="#example-action">Action</a></h3>
|
||||
|
||||
{% set code %}<div class="alert">
|
||||
{% lucide "info" %}
|
||||
<h2>Enable notifications</h2>
|
||||
<section>Receive updates when there is activity in your workspace.</section>
|
||||
<div data-action>
|
||||
<button type="button" class="btn-xs-outline">Enable</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-action", code) }}
|
||||
|
||||
<h3 id="example-custom-colors"><a href="#example-custom-colors">Custom Colors</a></h3>
|
||||
|
||||
{% set code %}<div class="alert bg-amber-50 text-amber-900 dark:bg-amber-950 dark:text-amber-200">
|
||||
{% lucide "triangle-alert" %}
|
||||
<h2>Storage almost full</h2>
|
||||
<section class="text-amber-800 dark:text-amber-300">Upgrade your plan or delete unused files.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-custom-colors", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Alerts support right-to-left layouts through logical spacing and native text flow. Add <code>dir="rtl"</code> to the alert or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div class="alert" dir="rtl">
|
||||
{% lucide "info" %}
|
||||
<h2>تم حفظ التغييرات</h2>
|
||||
<section>تم تحديث إعداداتك بنجاح.</section>
|
||||
<div data-action>
|
||||
<button type="button" class="btn-xs-outline">عرض</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-rtl", code) }}
|
||||
@@ -0,0 +1,136 @@
|
||||
# Alert
|
||||
|
||||
<Preview>
|
||||
<div class="grid w-full max-w-xl items-start gap-4">
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-circle-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-popcorn" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a2 2 0 0 0 0-4 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0 0 4" /><path d="M10 22 9 8" /><path d="m14 22 1-14" /><path d="M20 8c.5 0 .9.4.8 1l-2.6 12c-.1.5-.7 1-1.2 1H7c-.6 0-1.1-.4-1.2-1L3.2 9c-.1-.6.3-1 .8-1Z" /></svg>
|
||||
<h2>This Alert has a title and an icon. No description.</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
<div class="alert-destructive">
|
||||
<svg class="lucide lucide-circle-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>
|
||||
<h2>Unable to process your payment.</h2>
|
||||
<section>
|
||||
<p>Please verify your billing information and try again.</p>
|
||||
<ul class="list-inside list-disc text-sm">
|
||||
<li>Check your card details</li>
|
||||
<li>Ensure sufficient funds</li>
|
||||
<li>Verify billing address</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `alert` for default styling or `alert-destructive` for error states. Add a direct child `<footer>` for an inline-end action region.
|
||||
|
||||
```html
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-circle-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="alert"></code></dt>
|
||||
<dd>
|
||||
Main container. Use <code>alert</code> for default styling or <code>alert-destructive</code> for error states.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><svg></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The icon.</dd>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title.</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The action region, aligned to the inline end of the alert. Put the action element inside it, such as a button or link.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<div class="alert-destructive">
|
||||
<svg class="lucide lucide-circle-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### No description
|
||||
|
||||
<Preview>
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-shield-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="M12 8v4" /><path d="M12 16h.01" /></svg>
|
||||
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### No icon
|
||||
|
||||
<Preview>
|
||||
<div class="alert">
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Action
|
||||
|
||||
<Preview>
|
||||
<div class="grid w-full max-w-xl gap-4">
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-circle-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><circle cx="12" cy="12" r="10" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
|
||||
<h2>The selected emails have been marked as spam.</h2>
|
||||
<footer>
|
||||
<button type="button" class="btn-xs-outline">Undo</button>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="alert">
|
||||
<svg class="lucide lucide-circle-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><circle cx="12" cy="12" r="10" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
|
||||
<h2>The selected emails have been marked as spam.</h2>
|
||||
<section>This is a longer alert description that demonstrates how the action stays aligned to the inline end.</section>
|
||||
<footer>
|
||||
<span class="badge-secondary">Badge</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Custom Colors
|
||||
|
||||
<Preview>
|
||||
<div class="alert bg-amber-50 text-amber-900 dark:bg-amber-950 dark:text-amber-200">
|
||||
<svg class="lucide lucide-triangle-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
|
||||
<h2>Storage almost full</h2>
|
||||
<section class="text-amber-800 dark:text-amber-300">Upgrade your plan or delete unused files.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the alert or an ancestor.
|
||||
|
||||
<Preview>
|
||||
<div class="alert" dir="rtl">
|
||||
<svg class="lucide lucide-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
|
||||
<h2>تم حفظ التغييرات</h2>
|
||||
<section>تم تحديث إعداداتك بنجاح.</section>
|
||||
<footer>
|
||||
<button type="button" class="btn-xs-outline">عرض</button>
|
||||
</footer>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Avatar
|
||||
description: An image element with a fallback for representing the user.
|
||||
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 Avatar component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}
|
||||
<div class="flex flex-row flex-wrap items-center gap-12">
|
||||
<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img class="size-8 shrink-0 object-cover rounded-lg" alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("avatar", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Avatars are just <code><img></code> elements styled with Tailwind utility classes.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml, "html") }}
|
||||
@@ -0,0 +1,196 @@
|
||||
# Avatar
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-row flex-wrap items-center gap-12">
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar rounded-lg">
|
||||
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
|
||||
<span>ER</span>
|
||||
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
|
||||
</span>
|
||||
<div class="avatar-group [&_.avatar]:grayscale">
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/maxleiter.png" alt="@maxleiter" />
|
||||
<span>LR</span>
|
||||
</span>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
|
||||
<span>ER</span>
|
||||
</span>
|
||||
<span data-count>+3</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `.avatar`, `.avatar-sm`, or `.avatar-lg` on a small inline container. Put the image first and a fallback `<span>` after it.
|
||||
|
||||
```html
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="avatar-group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Optional parent that groups avatars with overlap.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><span class="avatar|avatar-sm|avatar-lg"></code></dt>
|
||||
<dd>
|
||||
Avatar root. Use <code>.avatar-sm</code>, <code>.avatar</code>, or <code>.avatar-lg</code>. Use it standalone or as a direct child of <code>.avatar-group</code>.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><img></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The avatar image. Provide meaningful <code>alt</code> text.</dd>
|
||||
<dt><code class="highlight language-html"><span></code></dt>
|
||||
<dd>Fallback initials or icon.</dd>
|
||||
<dt><code class="highlight language-html"><span data-badge></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Status badge positioned at the block-end inline-end corner.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><span data-count></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Count or icon indicator displayed at the end of the group.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview>
|
||||
<span class="avatar">
|
||||
<img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Badge
|
||||
|
||||
<Preview>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Badge With Icon
|
||||
|
||||
<Preview>
|
||||
<span class="avatar grayscale">
|
||||
<img src="https://github.com/pranathip.png" alt="@pranathip" />
|
||||
<span>PP</span>
|
||||
<span data-badge>
|
||||
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</span>
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Avatar Group
|
||||
|
||||
<Preview>
|
||||
<div class="avatar-group grayscale">
|
||||
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
|
||||
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
|
||||
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Avatar Group Count
|
||||
|
||||
<Preview>
|
||||
<div class="avatar-group grayscale">
|
||||
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
|
||||
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
|
||||
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
|
||||
<span data-count>+3</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Avatar Group With Icon
|
||||
|
||||
<Preview>
|
||||
<div class="avatar-group grayscale">
|
||||
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
|
||||
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
|
||||
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
|
||||
<span data-count aria-label="Additional avatars">
|
||||
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Sizes
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2 grayscale">
|
||||
<span class="avatar-sm">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar-lg">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Dropdown
|
||||
|
||||
<Preview>
|
||||
<div class="dropdown-menu">
|
||||
<button type="button" id="avatar-dropdown-trigger" aria-haspopup="menu" aria-controls="avatar-dropdown-menu" aria-expanded="false" class="btn-icon-ghost rounded-full">
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
</button>
|
||||
<div data-popover aria-hidden="true" class="w-32">
|
||||
<div role="menu" id="avatar-dropdown-menu" aria-labelledby="avatar-dropdown-trigger">
|
||||
<div role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" data-variant="destructive">Log out</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-row flex-wrap items-center gap-6 md:gap-12" dir="rtl">
|
||||
<span class="avatar">
|
||||
<img class="grayscale" src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
|
||||
<span>ER</span>
|
||||
<span data-badge class="bg-green-600 dark:bg-green-800"></span>
|
||||
</span>
|
||||
<div class="avatar-group grayscale">
|
||||
<span class="avatar"><img src="https://github.com/shadcn.png" alt="@shadcn" /><span>CN</span></span>
|
||||
<span class="avatar"><img src="https://github.com/maxleiter.png" alt="@maxleiter" /><span>LR</span></span>
|
||||
<span class="avatar"><img src="https://github.com/evilrabbit.png" alt="@evilrabbit" /><span>ER</span></span>
|
||||
<span data-count>+٣</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Badge
|
||||
description: Displays a badge or a component that looks like a badge.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Primary
|
||||
id: example-primary
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: With icon
|
||||
id: example-with-icon
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: Custom Colors
|
||||
id: example-custom-colors
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="flex w-full flex-wrap gap-2">
|
||||
<span class="badge">Badge</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
</div>
|
||||
<div class="flex w-full flex-wrap gap-2">
|
||||
<span class="badge-secondary bg-blue-500 text-white dark:bg-blue-600">
|
||||
{% lucide "badge-check" %}
|
||||
Verified
|
||||
</span>
|
||||
<span class="badge rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">8</span>
|
||||
<span class="badge-destructive rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">99</span>
|
||||
<span class="badge-outline rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">20+</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("badge", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use one of the badge classes: <code>badge</code>, <code>badge-primary</code>, <code>badge-secondary</code>, <code>badge-destructive</code>, <code>badge-outline</code>, <code>badge-ghost</code>, or <code>badge-link</code>.</p>
|
||||
<p>When an icon needs directional spacing, add <code>data-icon="inline-start"</code> or <code>data-icon="inline-end"</code> to the icon.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-primary"><a href="#example-primary">Primary</a></h3>
|
||||
|
||||
{% set code %}<span class="badge">Primary</span>{% endset %}
|
||||
{{ code_preview("badge-primary", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-secondary">Secondary</span>{% endset %}
|
||||
{{ code_preview("badge-secondary", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-destructive">Destructive</span>{% endset %}
|
||||
{{ code_preview("badge-destructive", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-outline">Outline</span>{% endset %}
|
||||
{{ code_preview("badge-outline", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
With icon
|
||||
</span>{% endset %}
|
||||
{{ code_preview("badge-with-icon", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<a href="#" class="badge-outline">
|
||||
Link
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</a>{% endset %}
|
||||
{{ code_preview("badge-link", code) }}
|
||||
|
||||
<h3 id="example-custom-colors"><a href="#example-custom-colors">Custom Colors</a></h3>
|
||||
|
||||
{% set code %}<span class="badge bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">
|
||||
Custom
|
||||
</span>{% endset %}
|
||||
{{ code_preview("badge-custom-colors", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Badges support right-to-left layouts through native text flow and logical icon spacing. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="flex flex-wrap gap-2">
|
||||
<span class="badge">نشط</span>
|
||||
<a href="#" class="badge-outline">
|
||||
التفاصيل
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</a>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("badge-rtl", code) }}
|
||||
@@ -0,0 +1,102 @@
|
||||
# Badge
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="flex w-full flex-wrap gap-2">
|
||||
<span class="badge">Badge</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
</div>
|
||||
<div class="flex w-full flex-wrap gap-2">
|
||||
<span class="badge-secondary bg-blue-500 text-white dark:bg-blue-600">
|
||||
<svg class="lucide lucide-badge-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" /><path d="m9 12 2 2 4-4" /></svg>
|
||||
Verified
|
||||
</span>
|
||||
<span class="badge rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">8</span>
|
||||
<span class="badge-destructive rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">99</span>
|
||||
<span class="badge-outline rounded-full h-5 min-w-5 px-1 font-mono tabular-nums">20+</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use one of the badge classes: `badge`, `badge-primary`, `badge-secondary`, `badge-destructive`, `badge-outline`, `badge-ghost`, or `badge-link`.
|
||||
|
||||
When an icon needs directional spacing, add `data-icon="inline-start"` or `data-icon="inline-end"` to the icon.
|
||||
|
||||
```html
|
||||
<span class="badge">Badge</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
```
|
||||
|
||||
Badges support right-to-left layouts through native text flow and logical icon spacing.
|
||||
|
||||
## Examples
|
||||
|
||||
### Primary
|
||||
|
||||
<Preview>
|
||||
<span class="badge">Primary</span>
|
||||
</Preview>
|
||||
|
||||
### Secondary
|
||||
|
||||
<Preview>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
</Preview>
|
||||
|
||||
### Outline
|
||||
|
||||
<Preview>
|
||||
<span class="badge-outline">Outline</span>
|
||||
</Preview>
|
||||
|
||||
### With icon
|
||||
|
||||
<Preview>
|
||||
<span class="badge-destructive">
|
||||
<svg class="lucide lucide-circle-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><circle cx="12" cy="12" r="10" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" /></svg>
|
||||
With icon
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Link
|
||||
|
||||
<Preview>
|
||||
<a href="#" class="badge-outline">
|
||||
Link
|
||||
<svg data-icon="inline-end" class="rtl:rotate-180 lucide lucide-arrow-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</a>
|
||||
</Preview>
|
||||
|
||||
### Custom Colors
|
||||
|
||||
<Preview>
|
||||
<span class="badge bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">
|
||||
Custom
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Flip directional icons explicitly.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="flex flex-wrap gap-2">
|
||||
<span class="badge">نشط</span>
|
||||
<a href="#" class="badge-outline">
|
||||
التفاصيل
|
||||
<svg data-icon="inline-end" class="rtl:rotate-180 lucide lucide-arrow-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Breadcrumb
|
||||
description: Displays the path to the current resource using a hierarchy of links.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Breadcrumb component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code_html %}
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="demo-breadcrumb-menu",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can style an ordered list of links combined with chevron icons as show below. The example above demonstrates a more complex use case using the <a href="/components/dropdown-menu">Dropdown Menu</a> component.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
</ol>
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml, "html") }}
|
||||
@@ -0,0 +1,62 @@
|
||||
# Breadcrumb
|
||||
|
||||
<Callout title="No dedicated component" icon="circle-alert">
|
||||
There is no dedicated Breadcrumb component in Basecoat.
|
||||
</Callout>
|
||||
|
||||
<Preview>
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg class="size-3.5 lucide lucide-chevron-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
||||
</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<div id="demo-breadcrumb-menu" class="dropdown-menu">
|
||||
<button type="button" id="demo-breadcrumb-menu-trigger" aria-haspopup="menu" aria-controls="demo-breadcrumb-menu-menu" aria-expanded="false" class="flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer">
|
||||
<svg class="lucide lucide-ellipsis" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</button>
|
||||
<div id="demo-breadcrumb-menu-popover" data-popover aria-hidden="true">
|
||||
<div role="menu" id="demo-breadcrumb-menu-menu" aria-labelledby="demo-breadcrumb-menu-trigger">
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<svg class="size-3.5 lucide lucide-chevron-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
||||
</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg class="size-3.5 lucide lucide-chevron-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
||||
</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
You can style an ordered list of links combined with chevron icons as show below. The example above demonstrates a more complex use case using the [Dropdown Menu](/components/dropdown-menu) component.
|
||||
|
||||
```html
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<svg class="size-3.5 lucide lucide-chevron-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
||||
</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
</ol>
|
||||
```
|
||||
@@ -1,387 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Button Group
|
||||
description: A container that groups related buttons together with consistent styling.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Accessibility
|
||||
id: accessibility
|
||||
- label: Button Group vs Toggle Group
|
||||
id: button-group-vs-toggle-group
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Orientation
|
||||
id: example-orientation
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: Nested
|
||||
id: example-nested
|
||||
- label: Separator
|
||||
id: example-separator
|
||||
- label: Split
|
||||
id: example-split
|
||||
- label: Input
|
||||
id: example-input
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: Dropdown Menu
|
||||
id: example-dropdown-menu
|
||||
- label: Select
|
||||
id: example-select
|
||||
- label: Popover
|
||||
id: example-popover
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
{% from "popover.njk" import popover %}
|
||||
{% from "select.njk" import select %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_default %}
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Go Back">{% lucide "arrow-left" %}</button>
|
||||
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-outline"},
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="menuitem">
|
||||
{% lucide "mail-check" %}
|
||||
Mark as Read
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "archive" %}
|
||||
Archive
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
{% lucide "clock" %}
|
||||
Snooze
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "calendar" %}
|
||||
Add to Calendar
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "list-filter-plus" %}
|
||||
Add to List
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Trash
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-default", code_default | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Wrap related buttons or inputs in a <code><div role="group" class="button-group"></code> element. Add <code>aria-label</code> or <code>aria-labelledby</code> when the group purpose is not already clear from nearby text.</p>
|
||||
<p>Use <code>data-orientation="vertical"</code> to stack items vertically. Use <code><hr role="separator"></code> to visually divide non-outline buttons. Use <code>button-group-text</code> for static text inside a group.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<div role="group" aria-label="Message actions" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml, "html") }}
|
||||
|
||||
<h2 id="accessibility"><a href="#accessibility">Accessibility</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<ul>
|
||||
<li>Use <code>role="group"</code> on the <code>button-group</code> container.</li>
|
||||
<li>Use <code>aria-label</code> or <code>aria-labelledby</code> to label groups whose purpose is not obvious.</li>
|
||||
<li>Keyboard navigation remains native: users tab through each button, input, select, dropdown trigger, or popover trigger.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2 id="button-group-vs-toggle-group"><a href="#button-group-vs-toggle-group">Button Group vs Toggle Group</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<ul>
|
||||
<li>Use Button Group for related actions, such as archive/report/snooze.</li>
|
||||
<li>Use Toggle Group when each item controls selected/on state.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-orientation"><a href="#example-orientation">Orientation</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>data-orientation="vertical"</code> to stack buttons vertically.</p>
|
||||
</section>
|
||||
|
||||
{% set code_orientation %}
|
||||
<div role="group" class="button-group" data-orientation="vertical">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Zoom in">
|
||||
{% lucide "plus" %}
|
||||
</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Zoom out">
|
||||
{% lucide "minus" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-orientation", code_orientation | prettyHtml) }}
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code_size %}
|
||||
<div class="flex flex-col items-start gap-8">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">Small</button>
|
||||
<button type="button" class="btn-sm-outline">Button</button>
|
||||
<button type="button" class="btn-sm-outline">Group</button>
|
||||
<button type="button" class="btn-sm-icon-outline">{% lucide "plus" %}</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Default</button>
|
||||
<button type="button" class="btn-outline">Button</button>
|
||||
<button type="button" class="btn-outline">Group</button>
|
||||
<button type="button" class="btn-icon-outline">{% lucide "plus" %}</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-lg-outline">Large</button>
|
||||
<button type="button" class="btn-lg-outline">Button</button>
|
||||
<button type="button" class="btn-lg-outline">Group</button>
|
||||
<button type="button" class="btn-lg-icon-outline">{% lucide "plus" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-size", code_size | prettyHtml) }}
|
||||
|
||||
<h3 id="example-nested"><a href="#example-nested">Nested</a></h3>
|
||||
|
||||
{% set code_nested %}
|
||||
<div role="group" aria-label="Pagination controls" class="button-group">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">1</button>
|
||||
<button type="button" class="btn-sm-outline">2</button>
|
||||
<button type="button" class="btn-sm-outline">3</button>
|
||||
<button type="button" class="btn-sm-outline">4</button>
|
||||
<button type="button" class="btn-sm-outline">5</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Previous page">
|
||||
{% lucide "chevron-left" %}
|
||||
</button>
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Next page">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-nested", code_nested | prettyHtml) }}
|
||||
|
||||
<h3 id="example-separator"><a href="#example-separator">Separator</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code><hr role="separator"></code> to visually divide buttons within a group.</p>
|
||||
</section>
|
||||
|
||||
{% set code_separator %}
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-secondary">Copy</button>
|
||||
<hr role="separator">
|
||||
<button type="button" class="btn-secondary">Paste</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-separator", code_separator | prettyHtml) }}
|
||||
|
||||
<h3 id="example-split"><a href="#example-split">Split</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use a separator to create a split action with a primary action and a secondary menu trigger.</p>
|
||||
</section>
|
||||
|
||||
{% set code_split %}
|
||||
<div role="group" aria-label="Split save actions" class="button-group">
|
||||
<button type="button" class="btn-secondary">Save</button>
|
||||
<hr role="separator">
|
||||
{% set trigger_split %}
|
||||
{% lucide "chevron-down" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
trigger=trigger_split,
|
||||
trigger_attrs={"class": "btn-icon-secondary", "aria-label": "More save options"},
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="menuitem">Save as draft</div>
|
||||
<div role="menuitem">Save and publish</div>
|
||||
<div role="menuitem">Save as template</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-split", code_split | prettyHtml) }}
|
||||
|
||||
<h3 id="example-input"><a href="#example-input">Input</a></h3>
|
||||
|
||||
{% set code_input %}
|
||||
<div role="group" aria-label="Search" class="button-group w-full max-w-sm">
|
||||
<input type="text" class="input" placeholder="Search...">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Search">{% lucide "search" %}</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-input", code_input | prettyHtml) }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
{% set code_input_group %}
|
||||
<div role="group" aria-label="Amount" class="button-group w-full max-w-sm">
|
||||
<div class="button-group-text">$</div>
|
||||
<input type="text" class="input" placeholder="0.00">
|
||||
<div class="button-group-text">USD</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-input-group", code_input_group | prettyHtml) }}
|
||||
|
||||
<h3 id="example-dropdown-menu"><a href="#example-dropdown-menu">Dropdown Menu</a></h3>
|
||||
|
||||
{% set code_dropdown_menu %}
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Follow</button>
|
||||
{% set trigger %}
|
||||
{% lucide "chevron-down" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-outline"},
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="menuitem">
|
||||
{% lucide "volume-off" %}
|
||||
Mute Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "check" %}
|
||||
Mark as Read
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "triangle-alert" %}
|
||||
Report Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "user-round-x" %}
|
||||
Block User
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "share" %}
|
||||
Share Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "copy" %}
|
||||
Copy Conversation
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Delete Conversation
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-dropdown-menu", code_dropdown_menu | prettyHtml) }}
|
||||
|
||||
<h3 id="example-select"><a href="#example-select">Select</a></h3>
|
||||
|
||||
{% set code_select %}
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" aria-label="Currency amount" class="button-group">
|
||||
{% call select(trigger_attrs={"class": "[&_[data-name]]:hidden"}) %}
|
||||
<div role="option" data-value="$">$ <span class="text-muted-foreground" data-name>US Dollar</span></div>
|
||||
<div role="option" data-value="€">€ <span class="text-muted-foreground" data-name>Euro</span></div>
|
||||
<div role="option" data-value="£">£ <span class="text-muted-foreground" data-name>British Pound</span></div>
|
||||
{% endcall %}
|
||||
<input type="text" class="input" placeholder="10.00">
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn-icon-outline">{% lucide "arrow-right" %}</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-select", code_select | prettyHtml) }}
|
||||
|
||||
<h3 id="example-popover"><a href="#example-popover">Popover</a></h3>
|
||||
|
||||
{% set code_popover %}
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">
|
||||
{% lucide "bot" %}
|
||||
Copilot
|
||||
</button>
|
||||
{% set trigger_popover %}
|
||||
{% lucide "chevron-down" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
trigger=trigger_popover,
|
||||
trigger_attrs={"class": "btn-icon-outline"},
|
||||
popover_attrs={"class": "w-72 p-0", "data-align": "end"}
|
||||
) %}
|
||||
<header class="px-4 py-3 text-sm font-medium border-b">Agent Tasks</header>
|
||||
<section class="p-4 text-sm space-y-2">
|
||||
<textarea class="textarea w-full mb-4" placeholder="Describe your task in natural language"></textarea>
|
||||
<p class="font-medium">Start a new task with Copilot</p>
|
||||
<p class="text-muted-foreground">
|
||||
Describe your task in natural language. Copilot will work in the
|
||||
background and open a pull request for your review.
|
||||
</p>
|
||||
</section>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-popover", code_popover | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Button groups support right-to-left layouts through logical border and radius rules. Add <code>dir="rtl"</code> to the group or an ancestor. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl">
|
||||
<div role="group" aria-label="إجراءات الرسالة" class="button-group">
|
||||
<button type="button" class="btn-outline">أرشفة</button>
|
||||
<button type="button" class="btn-outline">تقرير</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
{% lucide "arrow-right", { "class": "rtl:rotate-180" } %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-rtl", code_rtl | prettyHtml) }}
|
||||
@@ -0,0 +1,315 @@
|
||||
# Button Group
|
||||
|
||||
<Preview>
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Go Back">
|
||||
<svg class="lucide lucide-arrow-left" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7" /><path d="M19 12H5" /></svg>
|
||||
</button>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
<div id="dropdown-menu-277805" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-menu-277805-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-277805-menu" aria-expanded="false" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-ellipsis" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-277805-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="menu" id="dropdown-menu-277805-menu" aria-labelledby="dropdown-menu-277805-trigger">
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-mail-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /><path d="m16 19 2 2 4-4" /></svg>
|
||||
Mark as Read
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-archive" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="5" x="2" y="3" rx="1" /><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" /><path d="M10 12h4" /></svg>
|
||||
Archive
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-clock" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" /></svg>
|
||||
Snooze
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-calendar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
Add to Calendar
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-list-filter-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5H2" /><path d="M6 12h12" /><path d="M9 19h6" /><path d="M16 5h6" /><path d="M19 8V2" /></svg>
|
||||
Add to List
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
<svg class="lucide lucide-trash-2" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
|
||||
Trash
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Wrap related controls in a `button-group`.
|
||||
|
||||
```html
|
||||
<div role="group" aria-label="Message actions" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `data-orientation="vertical"` to stack items. Use `<hr role="separator" />` between items when you need a visible divider, such as a split action. Use direct child `<span>`, `<label>`, or `<output>` elements for non-interactive text segments. Direct child `<div>` elements are left unstyled for custom layouts.
|
||||
|
||||
## Examples
|
||||
|
||||
### Orientation
|
||||
|
||||
Use `data-orientation="vertical"` to stack buttons vertically.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group" data-orientation="vertical">
|
||||
<button type="button" class="btn-icon-outline" aria-label="Zoom in">
|
||||
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Zoom out">
|
||||
<svg class="lucide lucide-minus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Size
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-col items-start gap-8">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">Small</button>
|
||||
<button type="button" class="btn-sm-outline">Button</button>
|
||||
<button type="button" class="btn-sm-outline">Group</button>
|
||||
<button type="button" class="btn-sm-icon-outline">
|
||||
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Default</button>
|
||||
<button type="button" class="btn-outline">Button</button>
|
||||
<button type="button" class="btn-outline">Group</button>
|
||||
<button type="button" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-lg-outline">Large</button>
|
||||
<button type="button" class="btn-lg-outline">Button</button>
|
||||
<button type="button" class="btn-lg-outline">Group</button>
|
||||
<button type="button" class="btn-lg-icon-outline">
|
||||
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Nested
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Pagination controls" class="button-group">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">1</button>
|
||||
<button type="button" class="btn-sm-outline">2</button>
|
||||
<button type="button" class="btn-sm-outline">3</button>
|
||||
<button type="button" class="btn-sm-outline">4</button>
|
||||
<button type="button" class="btn-sm-outline">5</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Previous page">
|
||||
<svg class="lucide lucide-chevron-left" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6" /></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Next page">
|
||||
<svg class="lucide lucide-chevron-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Separator
|
||||
|
||||
Use `<hr role="separator" />` to visually divide buttons within a group.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-secondary">Copy</button>
|
||||
<hr role="separator" />
|
||||
<button type="button" class="btn-secondary">Paste</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Split
|
||||
|
||||
Use a separator to create a split action with a primary action and a secondary menu trigger.
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Split save actions" class="button-group">
|
||||
<button type="button" class="btn-secondary">Save</button>
|
||||
<hr role="separator" />
|
||||
|
||||
<div id="dropdown-menu-465061" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-menu-465061-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-465061-menu" aria-expanded="false" class="btn-icon-secondary" aria-label="More save options">
|
||||
<svg class="lucide lucide-chevron-down" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-465061-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="menu" id="dropdown-menu-465061-menu" aria-labelledby="dropdown-menu-465061-trigger">
|
||||
<div role="menuitem">Save as draft</div>
|
||||
<div role="menuitem">Save and publish</div>
|
||||
<div role="menuitem">Save as template</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Input
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Search" class="button-group w-full max-w-sm">
|
||||
<input type="text" class="input" placeholder="Search..." />
|
||||
<button type="button" class="btn-icon-outline" aria-label="Search">
|
||||
<svg class="lucide lucide-search" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Input Group
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Amount" class="button-group w-full max-w-sm">
|
||||
<span>$</span>
|
||||
<input type="text" class="input" placeholder="0.00" />
|
||||
<span>USD</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Dropdown Menu
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Follow</button>
|
||||
|
||||
<div id="dropdown-menu-609880" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-menu-609880-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-609880-menu" aria-expanded="false" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-chevron-down" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-609880-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="menu" id="dropdown-menu-609880-menu" aria-labelledby="dropdown-menu-609880-trigger">
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-volume-off" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 9a5 5 0 0 1 .95 2.293" /><path d="M19.364 5.636a9 9 0 0 1 1.889 9.96" /><path d="m2 2 20 20" /><path d="m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11" /><path d="M9.828 4.172A.686.686 0 0 1 11 4.657v.686" /></svg>
|
||||
Mute Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
Mark as Read
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-triangle-alert" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
|
||||
Report Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-user-round-x" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 21a8 8 0 0 1 11.873-7" /><circle cx="10" cy="8" r="5" /><path d="m17 17 5 5" /><path d="m22 17-5 5" /></svg>
|
||||
Block User
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-share" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v13" /><path d="m16 6-4-4-4 4" /><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /></svg>
|
||||
Share Conversation
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-copy" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
|
||||
Copy Conversation
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
<svg class="lucide lucide-trash-2" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
|
||||
Delete Conversation
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Select
|
||||
|
||||
<Preview>
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" aria-label="Currency amount" class="button-group">
|
||||
<div id="select-797405" class="select">
|
||||
<button type="button" class="[&_[data-name]]:hidden" id="select-797405-trigger" aria-haspopup="listbox" aria-expanded="false" aria-controls="select-797405-listbox">
|
||||
<span class="truncate">$</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="select-797405-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="select-797405-listbox" aria-orientation="vertical" aria-labelledby="select-797405-trigger">
|
||||
<div role="option" data-value="$" data-label="$" aria-selected="true">
|
||||
$
|
||||
<span class="text-muted-foreground">US Dollar</span>
|
||||
</div>
|
||||
<div role="option" data-value="€" data-label="€">
|
||||
€
|
||||
<span class="text-muted-foreground">Euro</span>
|
||||
</div>
|
||||
<div role="option" data-value="£" data-label="£">
|
||||
£
|
||||
<span class="text-muted-foreground">British Pound</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="select-797405-value" value="$" />
|
||||
</div>
|
||||
<input type="text" class="input" placeholder="10.00" />
|
||||
</div>
|
||||
<button type="button" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-arrow-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Popover
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">
|
||||
<svg class="lucide lucide-bot" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8" /><rect width="16" height="12" x="4" y="8" rx="2" /><path d="M2 14h2" /><path d="M20 14h2" /><path d="M15 13v2" /><path d="M9 13v2" /></svg>
|
||||
Copilot
|
||||
</button>
|
||||
<div id="popover-788465" class="popover">
|
||||
<button id="popover-788465-trigger" type="button" aria-expanded="false" aria-controls="popover-788465-popover" class="btn-icon-outline">
|
||||
<svg class="lucide lucide-chevron-down" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="popover-788465-popover" data-popover aria-hidden="true" class="w-72 p-0" data-align="end">
|
||||
<header class="px-4 py-3 text-sm font-medium border-b">Agent Tasks</header>
|
||||
<section class="p-4 text-sm space-y-2">
|
||||
<textarea class="textarea w-full mb-4" placeholder="Describe your task in natural language"></textarea>
|
||||
<p class="font-medium">Start a new task with Copilot</p>
|
||||
<p class="text-muted-foreground">Describe your task in natural language. Copilot will work in the background and open a pull request for your review.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the group or an ancestor. Flip directional icons explicitly.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<div role="group" aria-label="إجراءات الرسالة" class="button-group">
|
||||
<button type="button" class="btn-outline">أرشفة</button>
|
||||
<button type="button" class="btn-outline">تقرير</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
<svg class="rtl:rotate-180 lucide lucide-arrow-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,222 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Button
|
||||
description: Displays a button or a component that looks like a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Ghost
|
||||
id: example-ghost
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: With Icon
|
||||
id: example-with-icon
|
||||
- label: Rounded
|
||||
id: example-rounded
|
||||
- label: Spinner
|
||||
id: example-spinner
|
||||
- label: Button Group
|
||||
id: example-button-group
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<button type="button" class="btn">Button</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use one of the <code>btn</code> classes to style a button element:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variants</b>:
|
||||
<ul>
|
||||
<li><code>btn</code> or <code>btn-primary</code> for default/primary buttons.</li>
|
||||
<li><code>btn-secondary</code> for secondary buttons.</li>
|
||||
<li><code>btn-destructive</code> for destructive buttons.</li>
|
||||
<li><code>btn-outline</code> for outline buttons.</li>
|
||||
<li><code>btn-ghost</code> for ghost buttons.</li>
|
||||
<li><code>btn-link</code> for link buttons.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Sizes</b>:
|
||||
<ul>
|
||||
<li><code>btn-xs</code> for extra small buttons.</li>
|
||||
<li><code>btn-sm</code> for small buttons.</li>
|
||||
<li><code>btn-lg</code> for large buttons.</li>
|
||||
<li><code>btn-icon</code> for icon buttons. This can be combined with sizes and variants, for example <code>btn-sm-icon-outline</code> or <code>btn-xs-icon-destructive</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Button classes are combined into a single class name. Use <code>btn-{variant}</code>, <code>btn-{size}</code>, <code>btn-{size}-{variant}</code>, <code>btn-icon-{variant}</code>, or <code>btn-{size}-icon-{variant}</code>. Do not stack classes like <code>btn-sm btn-outline</code>; use <code>btn-sm-outline</code> instead.</p>
|
||||
|
||||
<p>Basecoat follows Tailwind v4's default button cursor behavior and does not force <code>cursor: pointer</code>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
{% set code_composition %}<button type="button" class="btn">Default</button>
|
||||
<button type="button" class="btn-outline">Outline</button>
|
||||
<button type="button" class="btn-sm">Small</button>
|
||||
<button type="button" class="btn-sm-outline">Small outline</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Settings">
|
||||
{% lucide "settings" %}
|
||||
</button>
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Settings">
|
||||
{% lucide "settings" %}
|
||||
</button>{% endset %}
|
||||
{{ code_block(code_composition) }}
|
||||
|
||||
<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 size-specific classes to change the size of the button.</p>
|
||||
</section>
|
||||
|
||||
{% set code_size %}<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-xs">Extra Small</button>
|
||||
<button type="button" class="btn-sm">Small</button>
|
||||
<button type="button" class="btn">Default</button>
|
||||
<button type="button" class="btn-lg">Large</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-size", code_size) }}
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn">Button</button>{% endset %}
|
||||
{{ code_preview("button-default", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-outline">Outline</button>{% endset %}
|
||||
{{ code_preview("button-outline", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-secondary">Secondary</button>{% endset %}
|
||||
{{ code_preview("button-secondary", code) }}
|
||||
|
||||
<h3 id="example-ghost"><a href="#example-ghost">Ghost</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-ghost">Ghost</button>{% endset %}
|
||||
{{ code_preview("button-ghost", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-destructive">Destructive</button>{% endset %}
|
||||
{{ code_preview("button-destructive", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-link">Link</button>{% endset %}
|
||||
{{ code_preview("button-link", code) }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-icon-outline" aria-label="Open">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-icon", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With Icon</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Mark inline icons with <code>data-icon="inline-start"</code> or <code>data-icon="inline-end"</code> when the button should use icon-aware spacing.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button type="button" class="btn">
|
||||
{% lucide "git-branch", { "data-icon": "inline-start" } %}
|
||||
New Branch
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-with-icon", code) }}
|
||||
|
||||
<h3 id="example-rounded"><a href="#example-rounded">Rounded</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use standard Tailwind classes to override the button radius.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button type="button" class="btn rounded-full">
|
||||
{% lucide "arrow-up" %}
|
||||
Rounded
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-rounded", code) }}
|
||||
|
||||
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
|
||||
|
||||
{% set code_spinner %}<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Generating
|
||||
</button>
|
||||
<button type="button" class="btn-outline">
|
||||
Downloading
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-spinner", code_spinner) }}
|
||||
|
||||
<h3 id="example-button-group"><a href="#example-button-group">Button Group</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>To group related buttons, use the <a href="/components/button-group/">Button Group</a> component.</p>
|
||||
</section>
|
||||
|
||||
{% set code_button_group %}<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="More options">
|
||||
{% lucide "ellipsis" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-group-demo", code_button_group) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Buttons support right-to-left layouts through logical spacing. Add <code>dir="rtl"</code> to the button or an ancestor. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}<div dir="rtl" class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-outline">زر</button>
|
||||
<button type="button" class="btn-destructive">حذف</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</button>
|
||||
<button type="button" class="btn-secondary" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
جاري التحميل
|
||||
</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-rtl", code_rtl) }}
|
||||
@@ -0,0 +1,193 @@
|
||||
# Button
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn">Button</button>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<button type="button" class="btn">Primary</button>
|
||||
<button type="button" class="btn-outline">Outline</button>
|
||||
<button type="button" class="btn-sm-secondary">Small secondary</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Settings">
|
||||
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
</button>
|
||||
```
|
||||
|
||||
Use exactly one `btn` class on a `<button>` or `<a>`. The class name combines size, icon shape, and variant:
|
||||
|
||||
| Pattern | Example | Use for |
|
||||
| --- | --- | --- |
|
||||
| `btn` | `btn` | Default-size primary button |
|
||||
| `btn-{variant}` | `btn-outline` | Default-size button with a visual variant |
|
||||
| `btn-{size}` | `btn-sm` | Sized primary button |
|
||||
| `btn-{size}-{variant}` | `btn-sm-outline` | Sized button with a visual variant |
|
||||
| `btn-icon` | `btn-icon` | Default-size icon-only primary button |
|
||||
| `btn-icon-{variant}` | `btn-icon-outline` | Default-size icon-only button with a visual variant |
|
||||
| `btn-{size}-icon` | `btn-sm-icon` | Sized icon-only primary button |
|
||||
| `btn-{size}-icon-{variant}` | `btn-sm-icon-outline` | Sized icon-only button with a visual variant |
|
||||
|
||||
Do not stack button classes like `btn-sm btn-outline`. Use the composed class, such as `btn-sm-outline`.
|
||||
|
||||
### Variants
|
||||
|
||||
| Variant | Classes |
|
||||
| --- | --- |
|
||||
| Primary | `btn`, `btn-primary` |
|
||||
| Secondary | `btn-secondary` |
|
||||
| Outline | `btn-outline` |
|
||||
| Ghost | `btn-ghost` |
|
||||
| Link | `btn-link` |
|
||||
| Destructive | `btn-destructive` |
|
||||
|
||||
Every variant can be combined with a size or icon shape. For example: `btn-xs-secondary`, `btn-lg-destructive`, `btn-icon-ghost`, `btn-sm-icon-outline`.
|
||||
|
||||
### Sizes
|
||||
|
||||
| Size | Text button | Icon-only button |
|
||||
| --- | --- | --- |
|
||||
| Extra small | `btn-xs` | `btn-xs-icon` |
|
||||
| Small | `btn-sm` | `btn-sm-icon` |
|
||||
| Default | `btn` | `btn-icon` |
|
||||
| Large | `btn-lg` | `btn-lg-icon` |
|
||||
|
||||
Use icon-only classes only when the button has no visible text. Icon-only buttons must have an accessible name, usually with `aria-label`.
|
||||
|
||||
Mark inline icons with `data-icon="inline-start"` or `data-icon="inline-end"` when a text button should use icon-aware spacing.
|
||||
|
||||
To group related buttons, use the [Button Group](/components/button-group/) component.
|
||||
|
||||
## Examples
|
||||
|
||||
### Size
|
||||
|
||||
Use size-specific classes to change the size of the button.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-xs">Extra Small</button>
|
||||
<button type="button" class="btn-sm">Small</button>
|
||||
<button type="button" class="btn">Default</button>
|
||||
<button type="button" class="btn-lg">Large</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Default
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn">Button</button>
|
||||
</Preview>
|
||||
|
||||
### Outline
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-outline">Outline</button>
|
||||
</Preview>
|
||||
|
||||
### Secondary
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-secondary">Secondary</button>
|
||||
</Preview>
|
||||
|
||||
### Ghost
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-ghost">Ghost</button>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-destructive">Destructive</button>
|
||||
</Preview>
|
||||
|
||||
### Link
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-link">Link</button>
|
||||
</Preview>
|
||||
|
||||
### Icon
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Open">
|
||||
<svg class="lucide lucide-chevron-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="m9 18 6-6-6-6" /></svg>
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
### With Icon
|
||||
|
||||
Mark inline icons with `data-icon="inline-start"` or `data-icon="inline-end"` when the button should use icon-aware spacing.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn">
|
||||
<svg data-icon="inline-start" class="lucide lucide-git-branch" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M15 6a9 9 0 0 0-9 9V3" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /></svg>
|
||||
New Branch
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
### Rounded
|
||||
|
||||
Use standard Tailwind classes to override the button radius.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn rounded-full">
|
||||
<svg class="lucide lucide-arrow-up" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg>
|
||||
Rounded
|
||||
</button>
|
||||
</Preview>
|
||||
|
||||
### Spinner
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn" disabled>
|
||||
<svg class="animate-spin lucide lucide-loader" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg>
|
||||
Generating
|
||||
</button>
|
||||
<button type="button" class="btn-outline">
|
||||
Downloading
|
||||
<svg class="animate-spin lucide lucide-loader" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Button Group
|
||||
|
||||
To group related buttons, use the [Button Group](/components/button-group/) component.
|
||||
|
||||
<Preview>
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="More options">
|
||||
<svg class="lucide lucide-ellipsis" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the button or an ancestor. Flip directional icons explicitly.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-outline">زر</button>
|
||||
<button type="button" class="btn-destructive">حذف</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
<svg data-icon="inline-end" class="rtl:rotate-180 lucide lucide-arrow-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-secondary" disabled>
|
||||
<svg class="animate-spin lucide lucide-loader" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg>
|
||||
جاري التحميل
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,28 +1,7 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Card
|
||||
description: Displays a card with header, content, and footer.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: With content and footer
|
||||
id: example-with-content-and-footer
|
||||
- label: With image
|
||||
id: example-with-image
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
# Card
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div class="card w-full">
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="card w-full">
|
||||
<header>
|
||||
<h2>Login to your account</h2>
|
||||
<p>Enter your details below to login to your account</p>
|
||||
@@ -31,14 +10,14 @@ toc:
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-card-form-email">Email</label>
|
||||
<input class="input" type="email" id="demo-card-form-email">
|
||||
<input class="input" type="email" id="demo-card-form-email" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="label" for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ms-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input class="input" type="password" id="demo-card-form-password">
|
||||
<input class="input" type="password" id="demo-card-form-password" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -47,12 +26,13 @@ toc:
|
||||
<button type="button" class="btn-outline w-full">Login with Google</button>
|
||||
<p class="mt-4 text-center text-sm">Don't have an account? <a href="#" class="underline-offset-4 hover:underline">Sign up</a></p>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card", code, "w-full max-w-sm") }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
## Usage
|
||||
|
||||
{% set code %}<div class="card">
|
||||
```html
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Card Title</h2>
|
||||
<p>Card Description</p>
|
||||
@@ -63,51 +43,54 @@ toc:
|
||||
<footer>
|
||||
<p>Card Footer</p>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
</div>
|
||||
```
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="card"></code></dt>
|
||||
<dd>The card container.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>The header of the card.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the card.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the card.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>The content of the card.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The footer of the card.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
### HTML structure
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="card|card-sm"></code></dt>
|
||||
<dd>
|
||||
The card container. Use <code>.card-sm</code> for a denser layout.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>
|
||||
The header of the card.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the card.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description of the card.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>The content of the card.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The footer of the card.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
## Examples
|
||||
|
||||
{% set code %}<div class="card" data-size="sm">
|
||||
### Size
|
||||
|
||||
<Preview>
|
||||
<div class="card-sm">
|
||||
<header>
|
||||
<h2>Small Card</h2>
|
||||
<p>A compact card with reduced spacing.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p class="text-sm">Use <code>data-size="sm"</code> for denser layouts.</p>
|
||||
<p class="text-sm">Use <code>.card-sm</code> for denser layouts.</p>
|
||||
</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-size", code) }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-with-content-and-footer"><a href="#example-with-content-and-footer">With content and footer</a></h3>
|
||||
### With content and footer
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<Preview>
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Meeting Notes</h2>
|
||||
<p>Transcript from the meeting with the client.</p>
|
||||
@@ -124,17 +107,18 @@ toc:
|
||||
</section>
|
||||
<footer class="flex items-center">
|
||||
<div class="flex -space-x-2 [&_img]:ring-card [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" />
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png" />
|
||||
</div>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-with-content-and-footer", code) }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-with-image"><a href="#example-with-image">With image</a></h3>
|
||||
### With image
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<Preview>
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Is this an image?</h2>
|
||||
<p>This is a card with an image.</p>
|
||||
@@ -155,31 +139,30 @@ toc:
|
||||
</section>
|
||||
<footer class="flex items-center gap-2">
|
||||
<span class="badge-outline">
|
||||
{% lucide "bed" %}
|
||||
<svg class="lucide lucide-bed" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M2 4v16" /><path d="M2 8h18a2 2 0 0 1 2 2v10" /><path d="M2 17h20" /><path d="M6 8v9" /></svg>
|
||||
1
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "bath" %}
|
||||
<svg class="lucide lucide-bath" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M10 4 8 6" /><path d="M17 19v2" /><path d="M2 12h20" /><path d="M7 19v2" /><path d="M9 5 7.621 3.621A2.121 2.121 0 0 0 4 5v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5" /></svg>
|
||||
2
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "land-plot" %}
|
||||
<svg class="lucide lucide-land-plot" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="m12 8 6-3-6-3v10" /><path d="m8 11.99-5.5 3.14a1 1 0 0 0 0 1.74l8.5 4.86a2 2 0 0 0 2 0l8.5-4.86a1 1 0 0 0 0-1.74L16 12" /><path d="m6.49 12.85 11.02 6.3" /><path d="M17.51 12.85 6.5 19.15" /></svg>
|
||||
350m²
|
||||
</span>
|
||||
<span class="ms-auto font-medium tabular-nums">
|
||||
$135,000
|
||||
</span>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-with-image", code) }}
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
### RTL
|
||||
|
||||
<section class="prose">
|
||||
<p>Cards support right-to-left layouts through native text flow. Add <code>dir="rtl"</code> to the card or an ancestor.</p>
|
||||
</section>
|
||||
Add `dir="rtl"` to the card or an ancestor.
|
||||
|
||||
{% set code %}<div class="card" dir="rtl">
|
||||
<Preview>
|
||||
<div class="card" dir="rtl">
|
||||
<header>
|
||||
<h2>إعدادات الحساب</h2>
|
||||
<p>راجع إعدادات الأمان والإشعارات.</p>
|
||||
@@ -190,5 +173,5 @@ toc:
|
||||
<footer>
|
||||
<button type="button" class="btn">حفظ</button>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-rtl", code) }}
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Carousel
|
||||
description: A carousel with motion and swipe.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
|
||||
<style>
|
||||
.slider {
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slides {
|
||||
display: flex;
|
||||
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
|
||||
|
||||
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
/*
|
||||
scroll-snap-points-x: repeat(300px);
|
||||
scroll-snap-type: mandatory;
|
||||
*/
|
||||
}
|
||||
.slides::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.slides {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.slides > div {
|
||||
scroll-snap-align: start;
|
||||
flex-shrink: 0;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
margin-right: 50px;
|
||||
border-radius: 10px;
|
||||
background: #eee;
|
||||
transform-origin: center center;
|
||||
transform: scale(1);
|
||||
transition: transform 0.5s;
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="slider">
|
||||
|
||||
<a href="#slide-1">1</a>
|
||||
<a href="#slide-2">2</a>
|
||||
<a href="#slide-3">3</a>
|
||||
<a href="#slide-4">4</a>
|
||||
<a href="#slide-5">5</a>
|
||||
<a href="#slide-6">6</a>
|
||||
<a href="#slide-7">7</a>
|
||||
<a href="#slide-8">8</a>
|
||||
<a href="#slide-9">9</a>
|
||||
|
||||
<div class="slides">
|
||||
<div id="slide-1">
|
||||
1
|
||||
</div>
|
||||
<div id="slide-2">
|
||||
2
|
||||
</div>
|
||||
<div id="slide-3">
|
||||
3
|
||||
</div>
|
||||
<div id="slide-4">
|
||||
4
|
||||
</div>
|
||||
<div id="slide-5">
|
||||
5
|
||||
</div>
|
||||
<div id="slide-6">
|
||||
6
|
||||
</div>
|
||||
<div id="slide-7">
|
||||
7
|
||||
</div>
|
||||
<div id="slide-8">
|
||||
8
|
||||
</div>
|
||||
<div id="slide-9">
|
||||
9
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,815 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Chart
|
||||
description: Displays the path to the current resource using a hierarchy of links.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
<div class="grid gap-6">
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Area Chart</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="chart-area-default"></canvas>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="grid gap-2 text-sm">
|
||||
<div class="flex items-center gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month
|
||||
{% lucide "trending-up", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Area Chart - Stacked</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="chart-area-stacked"></canvas>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="grid gap-2 text-sm">
|
||||
<div class="flex items-center gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month
|
||||
{% lucide "trending-up", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Bar Chart - Multiple</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="chart-bar-multiple"></canvas>
|
||||
</section>
|
||||
<footer>
|
||||
<div class="grid gap-2 text-sm">
|
||||
<div class="flex items-center gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month
|
||||
{% lucide "trending-up", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="text-muted-foreground flex items-center gap-2 leading-none">January - June 2024</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Doughnut Chart</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section class="relative">
|
||||
<div>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center">
|
||||
<div class="text-3xl font-semibold">1,125</div>
|
||||
<div class="text-muted-foreground text-xs">Visitors</div>
|
||||
</div>
|
||||
<canvas id="chart-doughnut" class="max-h-[200px] mx-auto"></canvas>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Bar Chart - Interactive</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="chart-bar-interactive"></canvas>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Bar Chart - Interactive</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section class="max-h-[150px]">
|
||||
<canvas id="chart-line-interactive"></canvas>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
// Helper function to MULTIPLY the alpha channel of oklch colors by a factor
|
||||
function setAlpha(colorString, alphaFactor) {
|
||||
colorString = colorString.trim();
|
||||
if (colorString.startsWith('oklch(')) {
|
||||
const match = colorString.match(/^oklch\(([^\)]+?)\s*(?:\/\s*([\d.]+)(%?)\s*)?\)$/);
|
||||
if (match && match[1]) {
|
||||
const lchPart = match[1];
|
||||
let currentAlpha = 1.0;
|
||||
|
||||
if (match[2]) {
|
||||
currentAlpha = parseFloat(match[2]);
|
||||
if (match[3] === '%') {
|
||||
currentAlpha /= 100;
|
||||
}
|
||||
}
|
||||
|
||||
let newAlpha = currentAlpha * alphaFactor;
|
||||
newAlpha = Math.max(0, Math.min(1, newAlpha));
|
||||
|
||||
return `oklch(${lchPart} / ${newAlpha.toFixed(3)})`;
|
||||
} else {
|
||||
console.warn('Failed to parse oklch string:', colorString);
|
||||
return colorString;
|
||||
}
|
||||
}
|
||||
|
||||
return colorString;
|
||||
}
|
||||
|
||||
const computedStyle = getComputedStyle(document.documentElement);
|
||||
const themeColors = {
|
||||
background: computedStyle.getPropertyValue('--background').trim(),
|
||||
border: computedStyle.getPropertyValue('--border').trim(),
|
||||
mutedForeground: computedStyle.getPropertyValue('--muted-foreground').trim(),
|
||||
foreground: computedStyle.getPropertyValue('--foreground').trim(),
|
||||
chart1: computedStyle.getPropertyValue('--chart-1').trim(),
|
||||
chart2: computedStyle.getPropertyValue('--chart-2').trim(),
|
||||
chart3: computedStyle.getPropertyValue('--chart-3').trim(),
|
||||
chart4: computedStyle.getPropertyValue('--chart-4').trim(),
|
||||
chart5: computedStyle.getPropertyValue('--chart-5').trim(),
|
||||
};
|
||||
|
||||
const getOrCreateTooltip = (chart) => {
|
||||
let tooltipEl = chart.canvas.parentNode.querySelector('div[role="tooltip"]');
|
||||
|
||||
if (!tooltipEl) {
|
||||
const tooltipHTML = `
|
||||
<div
|
||||
role="tooltip"
|
||||
class="border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl opacity-0 absolute pointer-events-none transition-all duration-400 space-y-1"
|
||||
>
|
||||
<h3 class="font-medium"></h3>
|
||||
<ul class="grid gap-1"></ul>
|
||||
</div>`;
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(tooltipHTML.trim(), 'text/html');
|
||||
tooltipEl = doc.body.firstChild;
|
||||
chart.canvas.parentNode.appendChild(tooltipEl);
|
||||
}
|
||||
return tooltipEl;
|
||||
};
|
||||
|
||||
const externalTooltipHandler = (context) => {
|
||||
const {chart, tooltip} = context;
|
||||
const chartType = chart.config._config.type;
|
||||
const tooltipEl = getOrCreateTooltip(chart);
|
||||
const titleEl = tooltipEl.querySelector('h3');
|
||||
const bodyEl = tooltipEl.querySelector('ul');
|
||||
|
||||
// Hide if no tooltip
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.classList.remove('opacity-100');
|
||||
tooltipEl.classList.add('opacity-0');
|
||||
return;
|
||||
}
|
||||
|
||||
let bodyHtml = '';
|
||||
if (tooltip.body) {
|
||||
const titleLines = tooltip.title || [];
|
||||
const bodyLines = tooltip.body.map(b => b.lines);
|
||||
|
||||
if (titleEl) {
|
||||
if (chartType === 'doughnut') {
|
||||
titleEl.remove();
|
||||
} else {
|
||||
titleEl.textContent = titleLines[0] || '';
|
||||
}
|
||||
}
|
||||
|
||||
bodyLines.forEach((body, i) => {
|
||||
const colors = tooltip.labelColors[i];
|
||||
const parts = body[0].split(': ');
|
||||
const label = chartType === 'doughnut'
|
||||
? titleLines[0] || ''
|
||||
: parts[0] || '';
|
||||
const value = parts[1] || '';
|
||||
|
||||
let color;
|
||||
switch (chartType) {
|
||||
case 'doughnut':
|
||||
case 'pie':
|
||||
case 'bar':
|
||||
color = colors.backgroundColor;
|
||||
break;
|
||||
default:
|
||||
color = colors.borderColor;
|
||||
}
|
||||
|
||||
bodyHtml += `<li class="flex w-full flex-wrap items-center justify-between leading-none gap-x-2">
|
||||
<div class="flex items-center gap-x-1.5">
|
||||
<div class="shrink-0 rounded-[2px] size-2.5 block" style="background:${color}"></div>
|
||||
<span class="text-muted-foreground">${label}</span>
|
||||
</div>
|
||||
<span class="text-foreground font-medium font-mono tabular-nums">${value}</span>
|
||||
</li>`;
|
||||
});
|
||||
}
|
||||
|
||||
if (bodyEl) bodyEl.innerHTML = bodyHtml;
|
||||
|
||||
const {offsetLeft: positionX, offsetTop: positionY} = chart.canvas;
|
||||
const tooltipHeight = tooltipEl.offsetHeight;
|
||||
const tooltipWidth = tooltipEl.offsetWidth;
|
||||
const caretX = tooltip.caretX;
|
||||
const caretY = tooltip.caretY;
|
||||
const xAlign = tooltip.xAlign;
|
||||
const yAlign = tooltip.yAlign;
|
||||
const tooltipOffset = 8;
|
||||
|
||||
let left = positionX + caretX;
|
||||
let top = positionY + caretY;
|
||||
|
||||
if (xAlign === 'center') {
|
||||
left -= tooltipWidth / 2;
|
||||
} else if (xAlign === 'right') {
|
||||
left -= (tooltipWidth + tooltipOffset);
|
||||
} else { // xAlign === 'left'
|
||||
left += tooltipOffset;
|
||||
}
|
||||
|
||||
if (yAlign === 'center') {
|
||||
top -= tooltipHeight / 2;
|
||||
} else if (yAlign === 'top') {
|
||||
top -= (tooltipHeight + tooltipOffset);
|
||||
} else {
|
||||
top += tooltipOffset;
|
||||
}
|
||||
|
||||
const canvasBounds = chart.canvas.getBoundingClientRect();
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
} else if (left + tooltipWidth > canvasBounds.width) {
|
||||
left = canvasBounds.width - tooltipWidth;
|
||||
}
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
} else if (top + tooltipHeight > window.innerHeight) {
|
||||
const flippedTop = positionY + caretY - tooltipHeight - tooltipOffset;
|
||||
if (flippedTop >= 0) top = flippedTop;
|
||||
}
|
||||
|
||||
tooltipEl.classList.remove('opacity-0');
|
||||
tooltipEl.classList.add('opacity-100');
|
||||
tooltipEl.style.left = left + 'px';
|
||||
tooltipEl.style.top = top + 'px';
|
||||
};
|
||||
|
||||
let ctx = document.getElementById('chart-area-default');
|
||||
let data = {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: [186, 305, 237, 73, 209, 214],
|
||||
borderWidth: 1,
|
||||
backgroundColor: setAlpha(themeColors.chart1, 0.2),
|
||||
borderColor: themeColors.chart1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart1,
|
||||
pointHoverBorderColor: themeColors.chart1,
|
||||
fill: 'origin'
|
||||
}]
|
||||
};
|
||||
let config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
tension: 0.4,
|
||||
interaction: {
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: themeColors.mutedForeground }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
|
||||
ctx = document.getElementById('chart-area-stacked');
|
||||
data = {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: [186, 305, 237, 73, 209, 214],
|
||||
borderWidth: 1,
|
||||
backgroundColor: setAlpha(themeColors.chart1, 0.2),
|
||||
borderColor: themeColors.chart1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart1,
|
||||
pointHoverBorderColor: themeColors.chart1,
|
||||
fill: 'origin'
|
||||
}, {
|
||||
label: 'Mobile',
|
||||
data: [80, 200, 120, 190, 130, 140],
|
||||
borderWidth: 1,
|
||||
backgroundColor: setAlpha(themeColors.chart2, 0.2),
|
||||
borderColor: themeColors.chart2,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart2,
|
||||
pointHoverBorderColor: themeColors.chart2,
|
||||
fill: 'origin'
|
||||
}]
|
||||
};
|
||||
config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
tension: 0.4,
|
||||
interaction: {
|
||||
mode: 'nearest',
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: themeColors.mutedForeground }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
|
||||
ctx = document.getElementById('chart-bar-multiple');
|
||||
data = {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: [186, 305, 237, 73, 209, 214],
|
||||
backgroundColor: themeColors.chart1,
|
||||
borderWidth: 0,
|
||||
borderRadius: 4,
|
||||
barPercentage: 0.7,
|
||||
categoryPercentage: 0.8
|
||||
}, {
|
||||
label: 'Mobile',
|
||||
data: [80, 200, 120, 190, 130, 140],
|
||||
backgroundColor: themeColors.chart2,
|
||||
borderWidth: 0,
|
||||
borderRadius: 4,
|
||||
barPercentage: 0.7,
|
||||
categoryPercentage: 0.8
|
||||
}]
|
||||
};
|
||||
|
||||
config = {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: themeColors.mutedForeground }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
const chartData = [
|
||||
{ date: "2024-04-01", desktop: 222, mobile: 150 },
|
||||
{ date: "2024-04-02", desktop: 97, mobile: 180 },
|
||||
{ date: "2024-04-03", desktop: 167, mobile: 120 },
|
||||
{ date: "2024-04-04", desktop: 242, mobile: 260 },
|
||||
{ date: "2024-04-05", desktop: 373, mobile: 290 },
|
||||
{ date: "2024-04-06", desktop: 301, mobile: 340 },
|
||||
{ date: "2024-04-07", desktop: 245, mobile: 180 },
|
||||
{ date: "2024-04-08", desktop: 409, mobile: 320 },
|
||||
{ date: "2024-04-09", desktop: 59, mobile: 110 },
|
||||
{ date: "2024-04-10", desktop: 261, mobile: 190 },
|
||||
{ date: "2024-04-11", desktop: 327, mobile: 350 },
|
||||
{ date: "2024-04-12", desktop: 292, mobile: 210 },
|
||||
{ date: "2024-04-13", desktop: 342, mobile: 380 },
|
||||
{ date: "2024-04-14", desktop: 137, mobile: 220 },
|
||||
{ date: "2024-04-15", desktop: 120, mobile: 170 },
|
||||
{ date: "2024-04-16", desktop: 138, mobile: 190 },
|
||||
{ date: "2024-04-17", desktop: 446, mobile: 360 },
|
||||
{ date: "2024-04-18", desktop: 364, mobile: 410 },
|
||||
{ date: "2024-04-19", desktop: 243, mobile: 180 },
|
||||
{ date: "2024-04-20", desktop: 89, mobile: 150 },
|
||||
{ date: "2024-04-21", desktop: 137, mobile: 200 },
|
||||
{ date: "2024-04-22", desktop: 224, mobile: 170 },
|
||||
{ date: "2024-04-23", desktop: 138, mobile: 230 },
|
||||
{ date: "2024-04-24", desktop: 387, mobile: 290 },
|
||||
{ date: "2024-04-25", desktop: 215, mobile: 250 },
|
||||
{ date: "2024-04-26", desktop: 75, mobile: 130 },
|
||||
{ date: "2024-04-27", desktop: 383, mobile: 420 },
|
||||
{ date: "2024-04-28", desktop: 122, mobile: 180 },
|
||||
{ date: "2024-04-29", desktop: 315, mobile: 240 },
|
||||
{ date: "2024-04-30", desktop: 454, mobile: 380 },
|
||||
{ date: "2024-05-01", desktop: 165, mobile: 220 },
|
||||
{ date: "2024-05-02", desktop: 293, mobile: 310 },
|
||||
{ date: "2024-05-03", desktop: 247, mobile: 190 },
|
||||
{ date: "2024-05-04", desktop: 385, mobile: 420 },
|
||||
{ date: "2024-05-05", desktop: 481, mobile: 390 },
|
||||
{ date: "2024-05-06", desktop: 498, mobile: 520 },
|
||||
{ date: "2024-05-07", desktop: 388, mobile: 300 },
|
||||
{ date: "2024-05-08", desktop: 149, mobile: 210 },
|
||||
{ date: "2024-05-09", desktop: 227, mobile: 180 },
|
||||
{ date: "2024-05-10", desktop: 293, mobile: 330 },
|
||||
{ date: "2024-05-11", desktop: 335, mobile: 270 },
|
||||
{ date: "2024-05-12", desktop: 197, mobile: 240 },
|
||||
{ date: "2024-05-13", desktop: 197, mobile: 160 },
|
||||
{ date: "2024-05-14", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-05-15", desktop: 473, mobile: 380 },
|
||||
{ date: "2024-05-16", desktop: 338, mobile: 400 },
|
||||
{ date: "2024-05-17", desktop: 499, mobile: 420 },
|
||||
{ date: "2024-05-18", desktop: 315, mobile: 350 },
|
||||
{ date: "2024-05-19", desktop: 235, mobile: 180 },
|
||||
{ date: "2024-05-20", desktop: 177, mobile: 230 },
|
||||
{ date: "2024-05-21", desktop: 82, mobile: 140 },
|
||||
{ date: "2024-05-22", desktop: 81, mobile: 120 },
|
||||
{ date: "2024-05-23", desktop: 252, mobile: 290 },
|
||||
{ date: "2024-05-24", desktop: 294, mobile: 220 },
|
||||
{ date: "2024-05-25", desktop: 201, mobile: 250 },
|
||||
{ date: "2024-05-26", desktop: 213, mobile: 170 },
|
||||
{ date: "2024-05-27", desktop: 420, mobile: 460 },
|
||||
{ date: "2024-05-28", desktop: 233, mobile: 190 },
|
||||
{ date: "2024-05-29", desktop: 78, mobile: 130 },
|
||||
{ date: "2024-05-30", desktop: 340, mobile: 280 },
|
||||
{ date: "2024-05-31", desktop: 178, mobile: 230 },
|
||||
{ date: "2024-06-01", desktop: 178, mobile: 200 },
|
||||
{ date: "2024-06-02", desktop: 470, mobile: 410 },
|
||||
{ date: "2024-06-03", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-04", desktop: 439, mobile: 380 },
|
||||
{ date: "2024-06-05", desktop: 88, mobile: 140 },
|
||||
{ date: "2024-06-06", desktop: 294, mobile: 250 },
|
||||
{ date: "2024-06-07", desktop: 323, mobile: 370 },
|
||||
{ date: "2024-06-08", desktop: 385, mobile: 320 },
|
||||
{ date: "2024-06-09", desktop: 438, mobile: 480 },
|
||||
{ date: "2024-06-10", desktop: 155, mobile: 200 },
|
||||
{ date: "2024-06-11", desktop: 92, mobile: 150 },
|
||||
{ date: "2024-06-12", desktop: 492, mobile: 420 },
|
||||
{ date: "2024-06-13", desktop: 81, mobile: 130 },
|
||||
{ date: "2024-06-14", desktop: 426, mobile: 380 },
|
||||
{ date: "2024-06-15", desktop: 307, mobile: 350 },
|
||||
{ date: "2024-06-16", desktop: 371, mobile: 310 },
|
||||
{ date: "2024-06-17", desktop: 475, mobile: 520 },
|
||||
{ date: "2024-06-18", desktop: 107, mobile: 170 },
|
||||
{ date: "2024-06-19", desktop: 341, mobile: 290 },
|
||||
{ date: "2024-06-20", desktop: 408, mobile: 450 },
|
||||
{ date: "2024-06-21", desktop: 169, mobile: 210 },
|
||||
{ date: "2024-06-22", desktop: 317, mobile: 270 },
|
||||
{ date: "2024-06-23", desktop: 480, mobile: 530 },
|
||||
{ date: "2024-06-24", desktop: 132, mobile: 180 },
|
||||
{ date: "2024-06-25", desktop: 141, mobile: 190 },
|
||||
{ date: "2024-06-26", desktop: 434, mobile: 380 },
|
||||
{ date: "2024-06-27", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-06-28", desktop: 149, mobile: 200 },
|
||||
{ date: "2024-06-29", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-30", desktop: 446, mobile: 400 },
|
||||
]
|
||||
|
||||
|
||||
data = {
|
||||
labels: chartData.map(d => d.date),
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: chartData.map(d => d.desktop),
|
||||
backgroundColor: themeColors.chart2,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0, // Stacking usually looks better without individual radius
|
||||
}, {
|
||||
label: 'Mobile',
|
||||
data: chartData.map(d => d.mobile),
|
||||
backgroundColor: themeColors.chart1,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0, // Stacking usually looks better without individual radius
|
||||
}]
|
||||
};
|
||||
|
||||
ctx = document.getElementById('chart-bar-interactive');
|
||||
|
||||
// Restore helper function to format date for labels/tooltips
|
||||
const formatDateLabel = (dateString) => {
|
||||
try {
|
||||
const date = new Date(dateString + 'T00:00:00');
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); // e.g., "Apr 1"
|
||||
} catch (event) {
|
||||
return dateString; // Fallback
|
||||
}
|
||||
};
|
||||
|
||||
// Restore data preparation with formatted date labels
|
||||
data = {
|
||||
labels: chartData.map(d => formatDateLabel(d.date)),
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: chartData.map(d => d.desktop),
|
||||
backgroundColor: themeColors.chart4,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
}, {
|
||||
label: 'Mobile',
|
||||
data: chartData.map(d => d.mobile),
|
||||
backgroundColor: themeColors.chart5,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
}]
|
||||
};
|
||||
|
||||
config = {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false }
|
||||
},
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
color: themeColors.mutedForeground,
|
||||
callback: function(value, index, ticks) {
|
||||
const label = this.getLabelForValue(value);
|
||||
return index % 7 === 0 ? label : null;
|
||||
},
|
||||
maxRotation: 0,
|
||||
minRotation: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
// Same chart but with line chart
|
||||
// We start off from the previous config and just change the type to line
|
||||
|
||||
ctx = document.getElementById('chart-line-interactive');
|
||||
|
||||
|
||||
// Prepare data with formatted date labels AND offset for Mobile
|
||||
const desktopData = chartData.map(d => d.desktop + 400); // Store raw desktop data
|
||||
const mobileDataOffset = chartData.map(d => d.mobile); // Store offset mobile data
|
||||
|
||||
// Prepare data with gradient background
|
||||
data = {
|
||||
labels: chartData.map(d => formatDateLabel(d.date)),
|
||||
datasets: [{
|
||||
label: 'Desktop',
|
||||
data: desktopData,
|
||||
borderColor: themeColors.chart1,
|
||||
// Make backgroundColor scriptable
|
||||
backgroundColor: function(context) {
|
||||
const chart = context.chart;
|
||||
const { ctx, chartArea } = chart;
|
||||
// Use the helper function defined above
|
||||
return createGradient(ctx, chartArea, themeColors.chart1);
|
||||
},
|
||||
borderWidth: 1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart1,
|
||||
pointHoverBorderColor: themeColors.chart1,
|
||||
fill: 'start' // Fill from the start (usually y=0 or axis baseline)
|
||||
}, {
|
||||
label: 'Mobile (Offset)', // Adjusted label
|
||||
data: mobileDataOffset,
|
||||
borderColor: themeColors.chart2,
|
||||
// Make backgroundColor scriptable
|
||||
backgroundColor: function(context) {
|
||||
const chart = context.chart;
|
||||
const { ctx, chartArea } = chart;
|
||||
// Use the helper function defined above
|
||||
return createGradient(ctx, chartArea, themeColors.chart2);
|
||||
},
|
||||
borderWidth: 1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: themeColors.chart2,
|
||||
pointHoverBorderColor: themeColors.chart2,
|
||||
fill: 'start'
|
||||
}]
|
||||
};
|
||||
|
||||
// Add the createGradient helper function before the data object
|
||||
function createGradient(ctx, chartArea, color) {
|
||||
if (!chartArea) {
|
||||
return setAlpha(color, 0.1); // Fallback during initial setup
|
||||
}
|
||||
const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
|
||||
gradient.addColorStop(0, setAlpha(color, 0.5)); // Start color at top
|
||||
gradient.addColorStop(1, setAlpha(color, 0)); // End color (transparent) at bottom
|
||||
return gradient;
|
||||
}
|
||||
|
||||
// Calculate min/max for Y-axis scaling using the PLOTTED data
|
||||
const allPlottedValues = [...desktopData, ...mobileDataOffset]; // Combine actual plotted values
|
||||
const dataMin = Math.min(...allPlottedValues);
|
||||
const dataMax = Math.max(...allPlottedValues);
|
||||
const yPadding = (dataMax - dataMin) * 0.1; // Add 10% padding
|
||||
|
||||
config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
aspectRatio: 1,
|
||||
tension: 0.4, // Keep tension from original line config
|
||||
interaction: {
|
||||
mode: 'index', // Use 'index' mode for better hover on multiple lines
|
||||
axis: 'x',
|
||||
intersect: false
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
border: { display: false },
|
||||
grid: {
|
||||
color: setAlpha(themeColors.border, 0.5),
|
||||
lineWidth: 1
|
||||
},
|
||||
ticks: { display: false },
|
||||
// Set min and max for Y-axis
|
||||
min: Math.max(0, dataMin - yPadding), // Ensure min is not negative
|
||||
max: dataMax + yPadding
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
color: themeColors.mutedForeground,
|
||||
// Restore callback to reduce ticks
|
||||
callback: function(value, index, ticks) {
|
||||
const label = this.getLabelForValue(value);
|
||||
return index % 7 === 0 ? label : null;
|
||||
},
|
||||
maxRotation: 0,
|
||||
minRotation: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: externalTooltipHandler,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
new Chart(ctx, config);
|
||||
|
||||
|
||||
const chartDataDoughnut = [
|
||||
{ browser: "chrome", visitors: 275, fill: themeColors.chart1 }, // Use resolved colors
|
||||
{ browser: "safari", visitors: 200, fill: themeColors.chart2 },
|
||||
{ browser: "firefox", visitors: 287, fill: themeColors.chart3 },
|
||||
{ browser: "edge", visitors: 173, fill: themeColors.chart4 },
|
||||
{ browser: "other", visitors: 190, fill: themeColors.chart5 },
|
||||
]
|
||||
|
||||
// Prepare data for the doughnut chart
|
||||
const doughnutData = {
|
||||
labels: chartDataDoughnut.map(d => d.browser),
|
||||
datasets: [{
|
||||
label: 'Visitors by Browser',
|
||||
data: chartDataDoughnut.map(d => d.visitors),
|
||||
backgroundColor: chartDataDoughnut.map(d => d.fill),
|
||||
borderColor: themeColors.background, // Use background for border
|
||||
borderWidth: 0, // Add border between segments
|
||||
hoverBorderColor: themeColors.background,
|
||||
hoverBorderWidth: 0
|
||||
}]
|
||||
};
|
||||
|
||||
// Configure the doughnut chart
|
||||
const doughnutConfig = {
|
||||
type: 'doughnut',
|
||||
data: doughnutData,
|
||||
options: {
|
||||
cutout: '70%', // Adjust hole size
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false, // Hide default legend
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false, // Keep this false as we use external handler
|
||||
position: 'nearest', // Keep this for external handler logic
|
||||
external: externalTooltipHandler, // Use the existing handler
|
||||
},
|
||||
centerText: {} // Keep the custom plugin for center text
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Assume you have a canvas with id="chart-doughnut" in your HTML
|
||||
const doughnutCtx = document.getElementById('chart-doughnut').getContext('2d');
|
||||
new Chart(doughnutCtx, doughnutConfig);
|
||||
</script>
|
||||
@@ -0,0 +1,170 @@
|
||||
import ChartExample from "../site/ChartExample.astro"
|
||||
|
||||
# Chart
|
||||
|
||||
Use Chart.js with Basecoat defaults for themed colors, external tooltips, and optional generated legends.
|
||||
|
||||
<ChartExample />
|
||||
|
||||
Chart is an optional JavaScript helper. Load Chart.js first, then `basecoat.js`, then `chart.js`.
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/chart.min.js" defer></script>
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<canvas id="visitors-chart" aria-label="Monthly visitors"></canvas>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.basecoat.chart("#visitors-chart", {
|
||||
type: "bar",
|
||||
labelKey: "month",
|
||||
data: [
|
||||
{ month: "Jan", desktop: 186, mobile: 80 },
|
||||
{ month: "Feb", desktop: 305, mobile: 200 },
|
||||
{ month: "Mar", desktop: 237, mobile: 120 },
|
||||
{ month: "Apr", desktop: 73, mobile: 190 },
|
||||
{ month: "May", desktop: 209, mobile: 130 },
|
||||
{ month: "Jun", desktop: 214, mobile: 140 },
|
||||
],
|
||||
series: {
|
||||
desktop: { label: "Desktop", color: "var(--chart-1)" },
|
||||
mobile: { label: "Mobile", color: "var(--chart-2)" },
|
||||
},
|
||||
})
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
`basecoat.chart()` targets the `<canvas>` directly and creates Basecoat's internal `.chart` container for Chart.js sizing. You do not need to add a wrapper or class in your HTML.
|
||||
|
||||
Charts default to `aspect-video`. Use a height, `min-h-*`, or `aspect-*` utility on a parent element when you need a different measuring box.
|
||||
|
||||
## Card Composition
|
||||
|
||||
Chart does not own titles, descriptions, or framing. Compose those with Card or your own markup.
|
||||
|
||||
```html
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Bar Chart - Multiple</h2>
|
||||
<p>Showing total visitors for the last 6 months</p>
|
||||
</header>
|
||||
<section>
|
||||
<canvas id="visitors-chart" aria-label="Monthly visitors"></canvas>
|
||||
</section>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Legend
|
||||
|
||||
Set `legend: true` to generate a Basecoat-styled legend after the canvas.
|
||||
|
||||
The generated legend is display-only by default, matching shadcn/ui. Use a Chart.js plugin or custom legend markup if you need click-to-toggle behavior.
|
||||
|
||||
```js
|
||||
window.basecoat.chart("#visitors-chart", {
|
||||
type: "bar",
|
||||
labelKey: "month",
|
||||
data,
|
||||
series,
|
||||
legend: true,
|
||||
})
|
||||
```
|
||||
|
||||
## Series
|
||||
|
||||
`series` maps object keys from each data row to labels, colors, and optional Chart.js dataset settings.
|
||||
|
||||
```js
|
||||
window.basecoat.chart("#revenue-chart", {
|
||||
type: "line",
|
||||
labelKey: "month",
|
||||
data,
|
||||
series: {
|
||||
recurring: {
|
||||
label: "Recurring",
|
||||
color: "var(--chart-1)",
|
||||
dataset: {
|
||||
tension: 0.35,
|
||||
},
|
||||
},
|
||||
new: {
|
||||
label: "New",
|
||||
color: "var(--chart-2)",
|
||||
dataset: {
|
||||
borderDash: [4, 4],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Escape Hatches
|
||||
|
||||
Pass raw Chart.js options and plugins through `options` and `plugins`.
|
||||
|
||||
```js
|
||||
const chart = window.basecoat.chart("#visitors-chart", {
|
||||
type: "bar",
|
||||
labelKey: "month",
|
||||
data,
|
||||
series,
|
||||
options: {
|
||||
indexAxis: "x",
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "index",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [myChartJsPlugin],
|
||||
})
|
||||
```
|
||||
|
||||
Pass complete Chart.js data through `chartData` when the simplified `data` plus `series` mapper is not enough.
|
||||
|
||||
```js
|
||||
window.basecoat.chart("#custom-chart", {
|
||||
type: "scatter",
|
||||
chartData: {
|
||||
datasets: [
|
||||
{
|
||||
label: "Samples",
|
||||
data: [{ x: 1, y: 2 }, { x: 2, y: 5 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: { type: "linear" },
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
`basecoat.chart()` returns the Chart.js instance for single targets and an array of instances for multi-target selectors.
|
||||
|
||||
## API
|
||||
|
||||
| Option | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `type` | `"bar"` | Chart.js chart type. |
|
||||
| `labelKey` | `"label"` | Row key used for labels. |
|
||||
| `data` | `[]` | Array of row objects, or raw Chart.js data with `datasets`. |
|
||||
| `series` | `{}` | Series config mapped from row keys. |
|
||||
| `legend` | `false` | Generates a Basecoat legend after the canvas. |
|
||||
| `tooltip` | `true` | Uses the Basecoat external tooltip. |
|
||||
| `options` | `{}` | Raw Chart.js options. |
|
||||
| `plugins` | `[]` | Raw Chart.js plugins. |
|
||||
| `chartData` | `undefined` | Complete Chart.js data object. Overrides the data mapper. |
|
||||
@@ -1,167 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Checkbox
|
||||
description: A control that allows the user to toggle between checked and not checked.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Checked state
|
||||
id: checked-state
|
||||
- label: Invalid state
|
||||
id: invalid-state
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Description
|
||||
id: example-description
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Group
|
||||
id: example-group
|
||||
- label: Table
|
||||
id: example-table
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="checkbox" id="terms" class="input">
|
||||
<label for="terms">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>input</code> class to your <code><input type="checkbox"></code> elements. Pair it with <a href="/components/field">Field</a> and a native <code><label></code> for proper layout and labeling.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="checked-state"><a href="#checked-state">Checked state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use native HTML attributes for state. Add <code>checked</code> for an initially checked checkbox, or read and update the element's <code>checked</code> property in JavaScript.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" class="input" checked>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="invalid-state"><a href="#invalid-state">Invalid state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>aria-invalid="true"</code> on the checkbox and, when using <code>field</code>, set <code>data-invalid="true"</code> on the field wrapper.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start gap-3" data-invalid="true">
|
||||
<input type="checkbox" id="invalid-checkbox" class="input" aria-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="invalid-checkbox">Accept terms and conditions</label>
|
||||
<p class="field-error">You must accept the terms to continue.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-invalid", code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="checkbox" id="checkbox-basic" class="input">
|
||||
<label for="checkbox-basic">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-basic", code) }}
|
||||
|
||||
<h3 id="example-description"><a href="#example-description">Description</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-description" class="input" aria-describedby="checkbox-description-text">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="checkbox-description">Accept terms and conditions</label>
|
||||
<p id="checkbox-description-text" class="field-description">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-description", code) }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3" data-disabled="true">
|
||||
<input type="checkbox" id="checkbox-disabled" class="input" disabled>
|
||||
<label for="checkbox-disabled">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-disabled", code) }}
|
||||
|
||||
<h3 id="example-group"><a href="#example-group">Group</a></h3>
|
||||
|
||||
{% set code %}<fieldset class="fieldset">
|
||||
<legend>Sidebar</legend>
|
||||
<p>Select the items you want to display in the sidebar.</p>
|
||||
<div role="group" aria-label="Sidebar items" data-slot="checkbox-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-recents" checked>
|
||||
<label for="checkbox-recents">Recents</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-home" checked>
|
||||
<label for="checkbox-home">Home</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-applications">
|
||||
<label for="checkbox-applications">Applications</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-desktop">
|
||||
<label for="checkbox-desktop">Desktop</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("checkbox-group", code) }}
|
||||
|
||||
<h3 id="example-table"><a href="#example-table">Table</a></h3>
|
||||
|
||||
{% set code %}<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" class="input" aria-label="Select all"></th>
|
||||
<th>Status</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 1" checked></td>
|
||||
<td>Active</td>
|
||||
<td>olivia@example.com</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 2"></td>
|
||||
<td>Inactive</td>
|
||||
<td>noah@example.com</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-table", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Checkbox layout uses document direction and logical spacing. Set <code>dir="rtl"</code> on a parent to render right-to-left.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="field flex-row items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-rtl" class="input" aria-describedby="checkbox-rtl-description" checked>
|
||||
<div class="grid gap-1.5">
|
||||
<label for="checkbox-rtl">قبول الشروط والأحكام</label>
|
||||
<p id="checkbox-rtl-description" class="field-description">يمكنك تغيير هذا الخيار في أي وقت.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-rtl", code) }}
|
||||
@@ -0,0 +1,126 @@
|
||||
# Checkbox
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="terms" class="input" />
|
||||
<label for="terms">Accept terms and conditions</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Add `input` to checkbox inputs. Pair it with [Field](/components/field) and a native `<label>` for layout and labeling.
|
||||
|
||||
```html
|
||||
<input type="checkbox" class="input" />
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="checkbox-basic" class="input" />
|
||||
<label for="checkbox-basic">Accept terms and conditions</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Description
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="checkbox-description" class="input" aria-describedby="checkbox-description-text" />
|
||||
<section>
|
||||
<label for="checkbox-description">Accept terms and conditions</label>
|
||||
<p id="checkbox-description-text" class="field-description">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal" data-invalid="true">
|
||||
<input type="checkbox" id="checkbox-invalid" class="input" aria-invalid="true" />
|
||||
<label for="checkbox-invalid">Accept terms and conditions</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-orientation="horizontal" data-disabled="true">
|
||||
<input type="checkbox" id="checkbox-disabled" class="input" disabled />
|
||||
<label for="checkbox-disabled">Accept terms and conditions</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Group
|
||||
|
||||
<Preview>
|
||||
<fieldset class="fieldset">
|
||||
<legend>Sidebar</legend>
|
||||
<p>Select the items you want to display in the sidebar.</p>
|
||||
<div role="group" aria-label="Sidebar items" data-slot="checkbox-group">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="checkbox-recents" checked />
|
||||
<label for="checkbox-recents">Recents</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="checkbox-home" checked />
|
||||
<label for="checkbox-home">Home</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="checkbox-applications" />
|
||||
<label for="checkbox-applications">Applications</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="checkbox-desktop" />
|
||||
<label for="checkbox-desktop">Desktop</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</Preview>
|
||||
|
||||
### Table
|
||||
|
||||
<Preview>
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" class="input" aria-label="Select all" /></th>
|
||||
<th>Status</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 1" checked /></td>
|
||||
<td>Active</td>
|
||||
<td>olivia@example.com</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 2" /></td>
|
||||
<td>Inactive</td>
|
||||
<td>noah@example.com</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Checkbox layout uses document direction and logical spacing. Set `dir="rtl"` on a parent to render right-to-left.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="checkbox-rtl" class="input" aria-describedby="checkbox-rtl-description" checked />
|
||||
<section>
|
||||
<label for="checkbox-rtl">قبول الشروط والأحكام</label>
|
||||
<p id="checkbox-rtl-description" class="field-description">يمكنك تغيير هذا الخيار في أي وقت.</p>
|
||||
</section>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,349 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Combobox
|
||||
description: Autocomplete input with a list of suggestions.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your combobox HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript methods
|
||||
id: usage-html-js-5
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Multiple
|
||||
id: example-multiple
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Custom Items
|
||||
id: example-custom
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Auto Highlight
|
||||
id: example-auto-highlight
|
||||
- label: Top Side
|
||||
id: example-top
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "combobox.njk" import combobox %}
|
||||
|
||||
{% set frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% set code_html %}
|
||||
{% call combobox(
|
||||
id="framework-combobox",
|
||||
name="framework",
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("combobox", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/combobox.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/combobox.js" target="_blank">
|
||||
combobox.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your combobox HTML</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Combobox is input-first. The visible input filters the list; the hidden input stores the submitted value.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="combobox"></code></dt>
|
||||
<dd>Root element. Add <code>data-auto-highlight="true"</code> to highlight the first match while filtering. Add <code>data-format="object"</code> to serialize selected values as <code>{ value, label }</code> objects in the hidden input. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote autocomplete or a local Lunr index.</dd>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>The editable control. It should include <code>aria-expanded</code>, <code>aria-controls</code>, and <code>aria-autocomplete="list"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div class="combobox-chips"></code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Wraps selected chips and the editable input in multiple mode.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover></code></dt>
|
||||
<dd>The suggestions popup. Use <code>data-side</code> and <code>data-align</code> like Popover.</dd>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>The suggestions list. Add <code>aria-multiselectable="true"</code> in multiple mode and <code>data-empty</code> for the empty message.</dd>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="..."></code></dt>
|
||||
<dd>Selectable option. Options can contain custom HTML; use <code>data-label</code> when the visible input should use different text than the rendered content.</dd>
|
||||
<dt><code class="highlight language-html"><input type="hidden"></code></dt>
|
||||
<dd>Submitted value. Single mode stores a string; multiple mode stores a JSON array. With <code>data-format="object"</code>, single mode stores a JSON object and multiple mode stores a JSON array of objects.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Dispatched on the component after initialization.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>Dispatched on <code>document</code> when the popup opens so other popup components can close.</dd>
|
||||
<dt><code>change</code></dt>
|
||||
<dd>Dispatched on selection changes with <code>event.detail.value</code> and <code>event.detail.selected</code>. <code>value</code> is the canonical value or array of values; <code>selected</code> is the matching <code>{ value, label }</code> object or array.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript methods</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>value</code></dt>
|
||||
<dd>Gets or sets the selected value. Multiple mode uses an array.</dd>
|
||||
<dt><code>selected</code></dt>
|
||||
<dd>Gets the selected <code>{ value, label }</code> object. Multiple mode returns an array.</dd>
|
||||
<dt><code>setValue(value)</code></dt>
|
||||
<dd>Sets the selected value. Accepts plain values or <code>{ value, label }</code> objects, which is useful when hydrating remote autocomplete selections before the option exists in the list.</dd>
|
||||
<dt><code>select(value)</code></dt>
|
||||
<dd>Selects an option by value.</dd>
|
||||
<dt><code>deselect(value)</code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Removes a selected value.</dd>
|
||||
<dt><code>toggle(value)</code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Toggles a selected value.</dd>
|
||||
<dt><code>refresh()</code></dt>
|
||||
<dd>Rescans options after changing children inside the existing <code>role="listbox"</code> element.</dd>
|
||||
<dt><code>data-filter="manual"</code></dt>
|
||||
<dd>Disables Basecoat's built-in text filtering. Your app owns the visible suggestion set: add, remove, or update options, set <code>aria-hidden="true"</code> on hidden options, then call <code>combobox.refresh()</code>.</dd>
|
||||
<dt><code>window.basecoat.refresh(combobox)</code></dt>
|
||||
<dd>Calls the component refresh method through the global dispatcher.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code>combobox()</code> macro for Nunjucks or Jinja.</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/combobox.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/combobox.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="nuxtjs">Nuxt.js</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-multiple"><a href="#example-multiple">Multiple</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
id="frameworks-combobox",
|
||||
name="frameworks",
|
||||
multiple=true,
|
||||
selected=["Next.js"],
|
||||
placeholder="Add framework",
|
||||
input_attrs={"class": "w-[320px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
auto_highlight=true
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-multiple", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
id="timezone-combobox",
|
||||
placeholder="Search timezone",
|
||||
input_attrs={"class": "w-[280px]"},
|
||||
listbox_attrs={"data-empty": "No timezone found."}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="timezone-americas">
|
||||
<div role="heading" id="timezone-americas">Americas</div>
|
||||
<div role="option" data-value="America/New_York">(GMT-5) New York</div>
|
||||
<div role="option" data-value="America/Los_Angeles">(GMT-8) Los Angeles</div>
|
||||
<div role="option" data-value="America/Sao_Paulo">(GMT-3) São Paulo</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="timezone-europe">
|
||||
<div role="heading" id="timezone-europe">Europe</div>
|
||||
<div role="option" data-value="Europe/London">(GMT+0) London</div>
|
||||
<div role="option" data-value="Europe/Paris">(GMT+1) Paris</div>
|
||||
<div role="option" data-value="Europe/Berlin">(GMT+1) Berlin</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-groups", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-custom"><a href="#example-custom">Custom Items</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
id="country-combobox",
|
||||
placeholder="Search countries",
|
||||
input_attrs={"class": "w-[280px]"},
|
||||
listbox_attrs={"data-empty": "No country found."}
|
||||
) %}
|
||||
<div role="option" data-value="argentina" data-label="Argentina" data-filter="Argentina South America">
|
||||
<span class="flex flex-col">
|
||||
<span>Argentina</span>
|
||||
<span class="text-muted-foreground text-xs">South America (ar)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="japan" data-label="Japan" data-filter="Japan Asia">
|
||||
<span class="flex flex-col">
|
||||
<span>Japan</span>
|
||||
<span class="text-muted-foreground text-xs">Asia (jp)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="united-states" data-label="United States" data-filter="United States North America USA">
|
||||
<span class="flex flex-col">
|
||||
<span>United States</span>
|
||||
<span class="text-muted-foreground text-xs">North America (us)</span>
|
||||
</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-custom", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="invalid-combobox-input">Framework</label>
|
||||
{% call combobox(
|
||||
id="invalid-combobox",
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"id": "invalid-combobox-input", "aria-invalid": "true", "class": "w-[240px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
<p class="field-error" role="alert">Select a framework to continue.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-invalid", code | prettyHtml, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"disabled": "disabled", "class": "w-[240px]"}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-disabled", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-auto-highlight"><a href="#example-auto-highlight">Auto Highlight</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
auto_highlight=true
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-auto-highlight", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-top"><a href="#example-top">Top Side</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
popover_attrs={"data-side": "top"}
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-top", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% call combobox(
|
||||
placeholder="اختر إطار عمل",
|
||||
input_attrs={"class": "w-[240px]"}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="astro">Astro</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-rtl", code | prettyHtml) }}
|
||||
@@ -0,0 +1,284 @@
|
||||
# Combobox
|
||||
|
||||
<Preview>
|
||||
<div id="framework-combobox" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="framework-combobox-listbox" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="framework-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="framework-combobox-listbox" aria-orientation="vertical" data-empty="No framework found.">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="framework" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#combobox" }}>
|
||||
This component ships a `combobox()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/combobox.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your combobox HTML">
|
||||
|
||||
Combobox is input-first. The visible input filters the list; the hidden input stores the submitted value.
|
||||
|
||||
```html
|
||||
<div id="framework-combobox" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="framework-combobox-listbox" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="framework-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="framework-combobox-listbox" aria-orientation="vertical" data-empty="No framework found.">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="framework" value="" />
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="combobox"></code></dt>
|
||||
<dd>
|
||||
Root element. Add <code>data-auto-highlight="true"</code> to highlight the first match while filtering. Add <code>data-format="object"</code> to serialize selected values as <code>{ value, label }</code> objects in the hidden input. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote autocomplete or a local Lunr index.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>The editable control. It should include <code>aria-expanded</code>, <code>aria-controls</code>, and <code>aria-autocomplete="list"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div class="combobox-chips"></code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>
|
||||
Wraps selected chips and the editable input in multiple mode.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>The editable control in multiple mode.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover></code></dt>
|
||||
<dd>
|
||||
The suggestions popup. Use <code>data-side</code> and <code>data-align</code> like Popover.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>
|
||||
The suggestions list. Add <code>aria-multiselectable="true"</code> in multiple mode and <code>data-empty</code> for the empty message.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="..."></code></dt>
|
||||
<dd>Selectable option. Options can contain custom HTML; use <code>data-label</code> when the visible input should use different text than the rendered content.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Option group. Use <code>aria-labelledby</code> to connect it to its heading.</dd>
|
||||
<dt><code class="highlight language-html"><div role="heading"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups or options.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><input type="hidden"></code></dt>
|
||||
<dd>Submitted value. Single mode stores a string; multiple mode stores a JSON array. With <code>data-format="object"</code>, single mode stores a JSON object and multiple mode stores a JSON array of objects.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `change` | Event | Dispatched on selection changes with `event.detail.value` and `event.detail.selected`. |
|
||||
| `combobox.setValue(value)` | Method | Sets the selected value from a plain value or `{ value, label }` object, useful for remote autocomplete hydration. |
|
||||
| `combobox.refresh()` | Method | Rescans options after children change inside the existing `role="listbox"` element. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Multiple
|
||||
|
||||
<Preview>
|
||||
<div id="frameworks-combobox" class="combobox" data-auto-highlight="true">
|
||||
<div class="combobox-chips w-[320px]">
|
||||
<input type="text" role="combobox" placeholder="Add framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="frameworks-combobox-listbox" />
|
||||
</div>
|
||||
<div id="frameworks-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="frameworks-combobox-listbox" aria-orientation="vertical" aria-multiselectable="true" data-empty="No framework found.">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="frameworks" value='["Next.js"]' />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Groups
|
||||
|
||||
<Preview>
|
||||
<div id="timezone-combobox" class="combobox w-[280px]">
|
||||
<input type="text" role="combobox" placeholder="Search timezone" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="timezone-combobox-listbox" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="timezone-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="timezone-combobox-listbox" aria-orientation="vertical" data-empty="No timezone found.">
|
||||
<div role="group" aria-labelledby="timezone-americas">
|
||||
<div role="heading" id="timezone-americas">Americas</div>
|
||||
<div role="option" data-value="America/New_York">(GMT-5) New York</div>
|
||||
<div role="option" data-value="America/Los_Angeles">(GMT-8) Los Angeles</div>
|
||||
<div role="option" data-value="America/Sao_Paulo">(GMT-3) São Paulo</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="timezone-europe">
|
||||
<div role="heading" id="timezone-europe">Europe</div>
|
||||
<div role="option" data-value="Europe/London">(GMT+0) London</div>
|
||||
<div role="option" data-value="Europe/Paris">(GMT+1) Paris</div>
|
||||
<div role="option" data-value="Europe/Berlin">(GMT+1) Berlin</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Custom Items
|
||||
|
||||
<Preview>
|
||||
<div id="country-combobox" class="combobox w-[280px]">
|
||||
<input type="text" role="combobox" placeholder="Search countries" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="country-combobox-listbox" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="country-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="country-combobox-listbox" aria-orientation="vertical" data-empty="No country found.">
|
||||
<div role="option" data-value="argentina" data-label="Argentina" data-filter="Argentina South America">
|
||||
<span class="flex flex-col">
|
||||
<span>Argentina</span>
|
||||
<span class="text-muted-foreground text-xs">South America (ar)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="japan" data-label="Japan" data-filter="Japan Asia">
|
||||
<span class="flex flex-col">
|
||||
<span>Japan</span>
|
||||
<span class="text-muted-foreground text-xs">Asia (jp)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="united-states" data-label="United States" data-filter="United States North America USA">
|
||||
<span class="flex flex-col">
|
||||
<span>United States</span>
|
||||
<span class="text-muted-foreground text-xs">North America (us)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="invalid-combobox-input">Framework</label>
|
||||
<div id="invalid-combobox" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="invalid-combobox-listbox" id="invalid-combobox-input" aria-invalid="true" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="invalid-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="invalid-combobox-listbox" aria-orientation="vertical" data-empty="No framework found.">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
<p class="field-error" role="alert">Select a framework to continue.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview>
|
||||
<div id="combobox-489412" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-489412-listbox" disabled="disabled" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="combobox-489412-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="combobox-489412-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Auto Highlight
|
||||
|
||||
<Preview>
|
||||
<div id="combobox-906805" class="combobox w-[240px]" data-auto-highlight="true">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-906805-listbox" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="combobox-906805-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="combobox-906805-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Top Side
|
||||
|
||||
<Preview>
|
||||
<div id="combobox-775269" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-775269-listbox" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="combobox-775269-popover" data-popover aria-hidden="true" data-side="top">
|
||||
<div role="listbox" id="combobox-775269-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
<div role="option" data-value="SvelteKit">SvelteKit</div>
|
||||
<div role="option" data-value="Nuxt.js">Nuxt.js</div>
|
||||
<div role="option" data-value="Remix">Remix</div>
|
||||
<div role="option" data-value="Astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<div id="combobox-998046" class="combobox w-[240px]">
|
||||
<input type="text" role="combobox" placeholder="اختر إطار عمل" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="combobox-998046-listbox" />
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="combobox-trigger-icon"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div id="combobox-998046-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="combobox-998046-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="astro">Astro</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,342 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Command
|
||||
description: Command menu for search and quick actions.
|
||||
toc:
|
||||
- label: About
|
||||
id: about
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your command HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript API
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Shortcuts
|
||||
id: example-shortcuts
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Scrollable
|
||||
id: example-scrollable
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "command.njk" import command, command_dialog %}
|
||||
|
||||
{% macro command_items(suggestions_id="suggestions", settings_id="settings", shortcuts=false) %}
|
||||
<div role="group" aria-labelledby="{{ suggestions_id }}">
|
||||
<span role="heading" id="{{ suggestions_id }}">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
{% lucide "calendar" %}
|
||||
<span>Calendar</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘K</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
{% lucide "smile" %}
|
||||
<span>Search Emoji</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘E</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
{% lucide "calculator" %}
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="{{ settings_id }}">
|
||||
<span role="heading" id="{{ settings_id }}">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
{% lucide "user" %}
|
||||
<span>Profile</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘P</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
{% lucide "credit-card" %}
|
||||
<span>Billing</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘B</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
{% lucide "settings" %}
|
||||
<span>Settings</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘S</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% set code_standalone %}
|
||||
{% call command(id="demo-command-standalone", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("suggestions", "settings") }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("command-standalone", code_standalone | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="about"><a href="#about">About</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>shadcn/ui's Command component is built on <code>cmdk</code>. Basecoat implements the same command-menu pattern with vanilla HTML, CSS, and a small JavaScript controller for filtering, active item navigation, and dialog closing.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/command.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/command.js" target="_blank">
|
||||
command.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your command HTML</a></h4>
|
||||
|
||||
{{ code_block(code_standalone | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code><dialog class="command-dialog"></code> for the command palette variant.</p>
|
||||
</section>
|
||||
|
||||
{% set code_dialog_usage %}<button class="btn-outline" onclick="document.getElementById('command-palette').showModal()">Open</button>
|
||||
<dialog id="command-palette" class="command-dialog" aria-label="Command menu">
|
||||
<div class="command">
|
||||
<!-- Command content -->
|
||||
</div>
|
||||
</dialog>{% endset %}
|
||||
{{ code_block(code_dialog_usage | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>The command menu can be used standalone or inside a native dialog. The structure is:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><dialog class="command-dialog"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog variant. Wraps one <code><div class="command"></code>. Add <code>aria-label</code> or <code>aria-labelledby</code> for an accessible name.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="command"></code></dt>
|
||||
<dd>Root command menu. Can also be used standalone without the dialog wrapper. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote search or a local Lunr index.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Search input wrapper. The macro includes a search icon and input.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>Filter input. Use <code>aria-expanded="true"</code> and <code>aria-controls="{ MENU_ID }"</code> to point to the command list.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div role="menu" id="{ MENU_ID }"></code></dt>
|
||||
<dd>Command list. Set <code>data-empty</code> to customize the empty state.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related command items. Hidden automatically when every item in the group is filtered out.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading. Use an <code>id</code> when the parent group uses <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code> or <code class="highlight language-html"><a role="menuitem"></code></dt>
|
||||
<dd>Selectable command item. Use <code><a></code> for navigation and <code><div></code> for actions. Supports <code>data-filter</code>, <code>data-keywords</code>, <code>data-force</code>, <code>data-keep-command-open</code>, and <code>aria-disabled="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><span data-indicator></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Item indicator. It becomes visible with <code>data-checked="true"</code> or <code>aria-selected="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/items. Hidden while filtering.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript API</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>command.refresh()</code></dt>
|
||||
<dd>Rescans command items after changing children inside the existing <code>role="menu"</code> list.</dd>
|
||||
<dt><code>data-filter="manual"</code></dt>
|
||||
<dd>Disables Basecoat's built-in text filtering. Your app owns the visible result set: add, remove, or update items, set <code>aria-hidden="true"</code> on hidden items, then call <code>command.refresh()</code>.</dd>
|
||||
<dt><code>window.basecoat.refresh(command)</code></dt>
|
||||
<dd>Calls the component refresh method through the global dispatcher.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">command()</code> or <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">command_dialog()</code> Nunjucks or Jinja macros for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/command.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/command.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call command() %}
|
||||
<div role="menuitem" data-keywords="date event">
|
||||
<svg>...</svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-keywords="config preferences">
|
||||
<svg>...</svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>A simple command menu in a dialog. The button also shows the keyboard shortcut used in this example.</p>
|
||||
</section>
|
||||
|
||||
{% set code_basic %}
|
||||
<button type="button" class="btn-outline" onclick="document.getElementById('command-basic').showModal()">
|
||||
Open command menu
|
||||
<kbd class="kbd">⌘J</kbd>
|
||||
</button>
|
||||
{% call command_dialog(id="command-basic") %}
|
||||
{{ command_items("command-basic-suggestions", "command-basic-settings") }}
|
||||
{% endcall %}
|
||||
<script>
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'j') {
|
||||
event.preventDefault();
|
||||
const dialog = document.getElementById('command-basic');
|
||||
dialog.open ? dialog.close() : dialog.showModal();
|
||||
dialog.querySelector('header input')?.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("command-basic", code_basic | prettyHtml) }}
|
||||
|
||||
<h3 id="example-shortcuts"><a href="#example-shortcuts">Shortcuts</a></h3>
|
||||
|
||||
{% set code_shortcuts %}
|
||||
{% call command(id="command-shortcuts", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("command-shortcuts-suggestions", "command-shortcuts-settings", true) }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-shortcuts", code_shortcuts | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code_groups %}
|
||||
{% call command(id="command-groups", main_attrs={ "class": "border" }) %}
|
||||
{{ command_items("command-groups-suggestions", "command-groups-settings") }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-groups", code_groups | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% set code_scrollable %}
|
||||
{% call command(id="command-scrollable", main_attrs={ "class": "border" }) %}
|
||||
<div role="group" aria-labelledby="command-scrollable-heading">
|
||||
<span role="heading" id="command-scrollable-heading">Pages</span>
|
||||
{% for i in range(0, 18) %}
|
||||
<div role="menuitem" data-filter="Page {{ i + 1 }}">
|
||||
{% lucide "file" %}
|
||||
<span>Page {{ i + 1 }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-scrollable", code_scrollable | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Command supports document direction. Set <code>dir="rtl"</code> on the command root or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl">
|
||||
{% call command(id="command-rtl", placeholder="اكتب أمراً أو ابحث...", main_attrs={ "class": "border" }) %}
|
||||
<div role="group" aria-labelledby="command-rtl-suggestions">
|
||||
<span role="heading" id="command-rtl-suggestions">اقتراحات</span>
|
||||
<div role="menuitem" data-filter="Calendar">
|
||||
{% lucide "calendar" %}
|
||||
<span>التقويم</span>
|
||||
<span data-shortcut>⌘K</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings">
|
||||
{% lucide "settings" %}
|
||||
<span>الإعدادات</span>
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("command-rtl", code_rtl | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.command</code></dt>
|
||||
<dd>Root command menu.</dd>
|
||||
<dt><code class="highlight language-html">.command-dialog</code></dt>
|
||||
<dd>Native dialog variant. Closes automatically when an item is clicked unless that item has <code>data-keep-command-open</code>.</dd>
|
||||
<dt><code class="highlight language-html">data-filter</code></dt>
|
||||
<dd>Overrides item text used by the filter.</dd>
|
||||
<dt><code class="highlight language-html">data-keywords</code></dt>
|
||||
<dd>Additional whitespace- or comma-separated search terms.</dd>
|
||||
<dt><code class="highlight language-html">data-force</code></dt>
|
||||
<dd>Always keeps an item visible while filtering.</dd>
|
||||
<dt><code class="highlight language-html">data-empty</code></dt>
|
||||
<dd>Custom empty-state text on the menu element.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -0,0 +1,446 @@
|
||||
# Command
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="demo-command-standalone" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="demo-command-standalone-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="demo-command-standalone-menu" />
|
||||
</header>
|
||||
<div role="menu" id="demo-command-standalone-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="suggestions">
|
||||
<span role="heading" id="suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="settings">
|
||||
<span role="heading" id="settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## About
|
||||
|
||||
shadcn/ui's Command component is built on `cmdk`. Basecoat implements the same command-menu pattern with vanilla HTML, CSS, and a small JavaScript controller for filtering, active item navigation, and dialog closing.
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#command" }}>
|
||||
This component ships `command()` and `command_dialog()` macros for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/command.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your command HTML">
|
||||
|
||||
```html
|
||||
<div id="demo-command-standalone" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="demo-command-standalone-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="demo-command-standalone-menu" />
|
||||
</header>
|
||||
<div role="menu" id="demo-command-standalone-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="suggestions">
|
||||
<span role="heading" id="suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="settings">
|
||||
<span role="heading" id="settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `<dialog class="command-dialog">` for the command palette variant.
|
||||
|
||||
```html
|
||||
<button class="btn-outline" onclick="document.getElementById('command-palette').showModal()">Open</button>
|
||||
<dialog id="command-palette" class="command-dialog" aria-label="Command menu">
|
||||
<div class="command">
|
||||
<!-- Command content -->
|
||||
</div>
|
||||
</dialog>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
The command menu can be used standalone or inside a native dialog. The structure is:
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><dialog class="command-dialog"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Dialog variant. Wraps one <code><div class="command"></code>. Add <code>aria-label</code> or <code>aria-labelledby</code> for an accessible name.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="command"></code></dt>
|
||||
<dd>
|
||||
Root command menu. Can also be used standalone without the dialog wrapper. Add <code>data-filter="manual"</code> when your app owns filtering, such as remote search or a local Lunr index.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>
|
||||
Search input wrapper. The macro includes a search icon and input.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>Filter input. Use <code>aria-expanded="true"</code> and <code>aria-controls="{ MENU_ID }"</code> to point to the command list.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div role="menu" id="{ MENU_ID }"></code></dt>
|
||||
<dd>
|
||||
Command list. Set <code>data-empty</code> to customize the empty state.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related command items. Hidden automatically when every item in the group is filtered out.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading. Use an <code>id</code> when the parent group uses <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code> or <code class="highlight language-html"><a role="menuitem"></code></dt>
|
||||
<dd>Selectable command item. Use <code><a></code> for navigation and <code><div></code> for actions. Supports <code>data-filter</code>, <code>data-keywords</code>, <code>data-force</code>, <code>data-keep-command-open</code>, and <code>aria-disabled="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><span data-indicator></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Item indicator. It becomes visible with <code>data-checked="true"</code> or <code>aria-selected="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/items. Hidden while filtering.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `command.refresh()` | Method | Rescans command items after children change inside the existing `role="menu"` list. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
A simple command menu in a dialog. The button also shows the keyboard shortcut used in this example.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn-outline" onclick="document.getElementById('command-basic').showModal()">
|
||||
Open command menu
|
||||
<kbd class="kbd">⌘J</kbd>
|
||||
</button>
|
||||
<dialog id="command-basic" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
|
||||
<div class="command">
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-basic-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-basic-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-basic-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-basic-suggestions">
|
||||
<span role="heading" id="command-basic-suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-basic-settings">
|
||||
<span role="heading" id="command-basic-settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<script>
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === "j") {
|
||||
event.preventDefault();
|
||||
const dialog = document.getElementById("command-basic");
|
||||
dialog.open ? dialog.close() : dialog.showModal();
|
||||
dialog.querySelector("header input")?.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
### Shortcuts
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="command-shortcuts" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-shortcuts-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-shortcuts-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-shortcuts-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-shortcuts-suggestions">
|
||||
<span role="heading" id="command-shortcuts-suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
<span data-shortcut>⌘K</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
<span data-shortcut>⌘E</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-shortcuts-settings">
|
||||
<span role="heading" id="command-shortcuts-settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Groups
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="command-groups" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-groups-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-groups-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-groups-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-groups-suggestions">
|
||||
<span role="heading" id="command-groups-suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
<svg class="lucide lucide-calendar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
<svg class="lucide lucide-smile" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M8 14s1.5 2 4 2 4-2 4-2" /><line x1="9" x2="9.01" y1="9" y2="9" /><line x1="15" x2="15.01" y1="9" y2="9" /></svg>
|
||||
<span>Search Emoji</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
<svg class="lucide lucide-calculator" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="16" height="20" x="4" y="2" rx="2" /><line x1="8" x2="16" y1="6" y2="6" /><line x1="16" x2="16" y1="14" y2="18" /><path d="M16 10h.01" /><path d="M12 10h.01" /><path d="M8 10h.01" /><path d="M12 14h.01" /><path d="M8 14h.01" /><path d="M12 18h.01" /><path d="M8 18h.01" /></svg>
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-groups-settings">
|
||||
<span role="heading" id="command-groups-settings">Settings</span>
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
<svg class="lucide lucide-user" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
<svg class="lucide lucide-credit-card" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
<span>Billing</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Scrollable
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="command-scrollable" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-scrollable-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-scrollable-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-scrollable-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-scrollable-heading">
|
||||
<span role="heading" id="command-scrollable-heading">Pages</span>
|
||||
<div role="menuitem" data-filter="Page 1">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 1</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 2">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 2</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 3">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 3</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 4">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 4</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 5">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 5</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 6">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 6</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 7">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 7</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 8">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 8</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 9">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 9</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 10">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 10</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 11">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 11</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 12">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 12</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 13">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 13</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 14">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 14</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 15">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 15</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 16">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 16</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 17">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 17</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Page 18">
|
||||
<svg class="lucide lucide-file" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /></svg>
|
||||
<span>Page 18</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Command supports document direction. Set `dir="rtl"` on the command root or a parent element.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div dir="rtl">
|
||||
<div id="command-rtl" class="command border" aria-label="Command menu">
|
||||
<header>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-rtl-input" placeholder="اكتب أمراً أو ابحث..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-rtl-menu" />
|
||||
</header>
|
||||
<div role="menu" id="command-rtl-menu" aria-orientation="vertical" data-empty="No results found." class="scrollbar-thin">
|
||||
<div role="group" aria-labelledby="command-rtl-suggestions">
|
||||
<span role="heading" id="command-rtl-suggestions">اقتراحات</span>
|
||||
<div role="menuitem" data-filter="Calendar">
|
||||
<svg class="lucide lucide-calendar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4" /><path d="M16 2v4" /><rect width="18" height="18" x="3" y="4" rx="2" /><path d="M3 10h18" /></svg>
|
||||
<span>التقويم</span>
|
||||
<span data-shortcut>⌘K</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings">
|
||||
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span>الإعدادات</span>
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,288 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Dialog
|
||||
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML structure
|
||||
id: usage-html-structure
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Custom Close Button
|
||||
id: example-custom-close-button
|
||||
- label: No Close Button
|
||||
id: example-no-close-button
|
||||
- label: Sticky Footer
|
||||
id: example-sticky-footer
|
||||
- label: Scrollable Content
|
||||
id: example-scrollable-content
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
{% set code_html %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dialog", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat uses the native <code><dialog></code> element and <code>showModal()</code>. This differs from shadcn/ui's portalled Base UI implementation, but preserves native modality, focus handling, and inert page content without component JavaScript.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, class="") }}
|
||||
|
||||
<h3 id="usage-html-structure"><a href="#usage-html-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="dialog.showModal()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>Native modal dialog. Add <code>aria-labelledby="{ TITLE_ID }"</code> and <code>aria-describedby="{ DESCRIPTION_ID }"</code> when title and description are present. The macro also adds backdrop-click close handling.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div></code></dt>
|
||||
<dd>Dialog content surface.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog header.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2 id="{ TITLE_ID }"></code></dt>
|
||||
<dd>Dialog title. Reference it from <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p id="{ DESCRIPTION_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog description. Reference it from <code>aria-describedby</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog body/content area. Add overflow utilities when the body should scroll.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Action area. It stacks actions on small screens and aligns them to the end on larger screens.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="this.closest('dialog').close()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Close button. You can also wrap a button in <code><form method="dialog"></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dialog()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/dialog.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here.",
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="grid gap-4">...</form>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-custom-close-button"><a href="#example-custom-close-button">Custom Close Button</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-custom-close",
|
||||
title="Custom close button",
|
||||
description="The default close icon is hidden and the footer provides the close action.",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
close_button=false,
|
||||
footer=footer
|
||||
) %}
|
||||
<p class="text-sm text-muted-foreground">Use <code>close_button=false</code> and add a close control where it fits your layout.</p>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-custom-close", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-no-close-button"><a href="#example-no-close-button">No Close Button</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn" onclick="this.closest('dialog').close()">I understand</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-no-close",
|
||||
title="No close button",
|
||||
description="The dialog can still be closed with Escape or a custom action.",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
close_button=false,
|
||||
footer=footer
|
||||
) %}
|
||||
<p class="text-sm text-muted-foreground">Native dialogs remain keyboard-accessible even without the default close icon.</p>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-no-close", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-sticky-footer"><a href="#example-sticky-footer">Sticky Footer</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-sticky-footer",
|
||||
title="Sticky footer",
|
||||
description="Keep actions visible while the body scrolls.",
|
||||
trigger="Sticky Footer",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer_attrs={"class": "sticky bottom-0"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
{% for i in range(0, 8) %}
|
||||
<p>Dialog content row {{ i + 1 }}. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-sticky-footer", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-scrollable-content"><a href="#example-scrollable-content">Scrollable Content</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-scrollable",
|
||||
title="Scrollable Content",
|
||||
description="Long content can scroll inside the dialog body.",
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
{% for i in range(0, 10) %}
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row {{ i + 1 }}.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-scrollable-content", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Dialog positioning and close button placement use logical properties. Set <code>dir="rtl"</code> on the dialog or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">حفظ</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-rtl",
|
||||
title="تعديل الملف الشخصي",
|
||||
description="قم بتحديث بياناتك ثم احفظ التغييرات.",
|
||||
trigger="فتح",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="dialog-rtl-name">الاسم</label>
|
||||
<input class="input" id="dialog-rtl-name" value="ليلى">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-rtl", code | prettyHtml) }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.dialog</code></dt>
|
||||
<dd>Native dialog root. It owns the backdrop through <code>::backdrop</code> and the open state through the native <code>open</code> attribute.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div</code></dt>
|
||||
<dd>Dialog content surface. This maps to shadcn/ui's <code>DialogContent</code>.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > header</code></dt>
|
||||
<dd>Header area. Direct <code>h2</code> and <code>p</code> children are styled as title and description.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > section</code></dt>
|
||||
<dd>Body/content area. Add utilities such as <code>overflow-y-auto</code> when you need scrolling.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > footer</code></dt>
|
||||
<dd>Action area. Use Basecoat button classes for actions.</dd>
|
||||
<dt><code class="highlight language-html">close_button=false</code></dt>
|
||||
<dd>Macro option to hide the generated close icon.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -0,0 +1,253 @@
|
||||
# Dialog
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn-outline">Edit Profile</button>
|
||||
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
|
||||
<p id="demo-dialog-edit-profile-description">Make changes to your profile here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dialog" }}>
|
||||
This component ships a `dialog()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
Basecoat uses the native `<dialog>` element and `showModal()`. This differs from shadcn/ui's portalled Base UI implementation, but preserves native modality, focus handling, and inert page content without component JavaScript.
|
||||
|
||||
```html
|
||||
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn-outline">Edit Profile</button>
|
||||
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
|
||||
<p id="demo-dialog-edit-profile-description">Make changes to your profile here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
```
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="dialog.showModal()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>
|
||||
Native modal dialog. Add <code>aria-labelledby="{ TITLE_ID }"</code> and <code>aria-describedby="{ DESCRIPTION_ID }"</code> when title and description are present. The macro also adds backdrop-click close handling.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div></code></dt>
|
||||
<dd>
|
||||
Dialog content surface.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Dialog header.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2 id="{ TITLE_ID }"></code></dt>
|
||||
<dd>Dialog title. Reference it from <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p id="{ DESCRIPTION_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog description. Reference it from <code>aria-describedby</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog body/content area. Add overflow utilities when the body should scroll.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Action area. It stacks actions on small screens and aligns them to the end on larger screens.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="this.closest('dialog').close()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Close button. You can also wrap a button in <code><form method="dialog"></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom Close Button
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-custom-close').showModal()" class="btn-outline">Open</button>
|
||||
<dialog id="dialog-custom-close" class="dialog" aria-labelledby="dialog-custom-close-title" aria-describedby="dialog-custom-close-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-custom-close-title">Custom close button</h2>
|
||||
<p id="dialog-custom-close-description">The default close icon is hidden and the footer provides the close action.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Use
|
||||
<code>close_button=false</code>
|
||||
and add a close control where it fits your layout.
|
||||
</p>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### No Close Button
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-no-close').showModal()" class="btn-outline">Open</button>
|
||||
<dialog id="dialog-no-close" class="dialog" aria-labelledby="dialog-no-close-title" aria-describedby="dialog-no-close-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-no-close-title">No close button</h2>
|
||||
<p id="dialog-no-close-description">The dialog can still be closed with Escape or a custom action.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p class="text-sm text-muted-foreground">Native dialogs remain keyboard-accessible even without the default close icon.</p>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">I understand</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Sticky Footer
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-sticky-footer').showModal()" class="btn-outline">Sticky Footer</button>
|
||||
<dialog id="dialog-sticky-footer" class="dialog" aria-labelledby="dialog-sticky-footer-title" aria-describedby="dialog-sticky-footer-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-sticky-footer-title">Sticky footer</h2>
|
||||
<p id="dialog-sticky-footer-description">Keep actions visible while the body scrolls.</p>
|
||||
</header>
|
||||
<section class="overflow-y-auto scrollbar">
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
<p>Dialog content row 1. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 2. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 3. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 4. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 5. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 6. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 7. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
<p>Dialog content row 8. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="sticky bottom-0">
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Scrollable Content
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-scrollable').showModal()" class="btn-outline">Scrollable Content</button>
|
||||
<dialog id="dialog-scrollable" class="dialog" aria-labelledby="dialog-scrollable-title" aria-describedby="dialog-scrollable-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-scrollable-title">Scrollable Content</h2>
|
||||
<p id="dialog-scrollable-description">Long content can scroll inside the dialog body.</p>
|
||||
</header>
|
||||
<section class="overflow-y-auto scrollbar">
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 1.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 2.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 3.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 4.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 5.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 6.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 7.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 8.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 9.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row 10.</p>
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Dialog positioning and close button placement use logical properties. Set `dir="rtl"` on the dialog or a parent element.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<button type="button" onclick="document.getElementById('dialog-rtl').showModal()" class="btn-outline">فتح</button>
|
||||
<dialog id="dialog-rtl" class="dialog" aria-labelledby="dialog-rtl-title" aria-describedby="dialog-rtl-description" onclick="if (event.target === this) this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-rtl-title">تعديل الملف الشخصي</h2>
|
||||
<p id="dialog-rtl-description">قم بتحديث بياناتك ثم احفظ التغييرات.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="dialog-rtl-name">الاسم</label>
|
||||
<input class="input" id="dialog-rtl-name" value="ليلى" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">حفظ</button>
|
||||
</footer>
|
||||
<button type="button" class="btn-sm-icon-ghost" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18" /><path d="m6 6 12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,425 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Dropdown Menu
|
||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript file"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your dropdown menu HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript API
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Shortcuts
|
||||
id: example-shortcuts
|
||||
- label: Icons
|
||||
id: example-icons
|
||||
- label: Checkboxes
|
||||
id: example-checkboxes
|
||||
- label: Radio Group
|
||||
id: example-radio-group
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
{% set code_html %}
|
||||
{% call dropdown_menu(
|
||||
id="demo-dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="demo-dropdown-account">
|
||||
<div role="heading" id="demo-dropdown-account">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span data-shortcut>⇧⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<div role="menuitem">Keyboard shortcuts</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Log out</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat dropdown menus are inline-positioned relative to the <code>.dropdown-menu</code> wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and matches Basecoat's current popover/select positioning model.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/dropdown-menu.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/dropdown-menu.js" target="_blank">
|
||||
dropdown-menu.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your dropdown menu HTML</a></h4>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="dropdown-menu"></code></dt>
|
||||
<dd>Relative wrapper for the trigger and inline menu content.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button aria-haspopup="menu" aria-expanded="false"></code></dt>
|
||||
<dd>Trigger button. The script toggles <code>aria-expanded</code> and manages keyboard navigation.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover aria-hidden="true"></code></dt>
|
||||
<dd>Menu content popover. Set <code>data-side="top|right|bottom|left"</code> and <code>data-align="start|center|end"</code> to control placement.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="menu"></code></dt>
|
||||
<dd>Container for menu items, groups, labels, and separators.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related menu items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading/label.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code></dt>
|
||||
<dd>Standard action item. Use <code>aria-disabled="true"</code> for disabled items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemcheckbox" aria-checked="true"></code></dt>
|
||||
<dd>Checkbox-style item. Add a child with <code>data-indicator</code> for the checked icon.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemradio" aria-checked="true"></code></dt>
|
||||
<dd>Radio-style item. Add a child with <code>data-indicator</code> for the selected icon.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups or options.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript API</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>basecoat:initialized</code></dt>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the menu opens, the component dispatches a custom event on <code>document</code>. Other popover-based components listen for this to close any open popovers.</dd>
|
||||
<dt><code>dropdown.open()</code></dt>
|
||||
<dd>Opens the menu.</dd>
|
||||
<dt><code>dropdown.close()</code></dt>
|
||||
<dd>Closes the menu.</dd>
|
||||
<dt><code>dropdown.toggle()</code></dt>
|
||||
<dd>Toggles the menu.</dd>
|
||||
<dt><code>dropdown.refresh()</code></dt>
|
||||
<dd>Rescans menu items after changing children inside the existing <code>role="menu"</code> element.</dd>
|
||||
<dt><code>window.basecoat.refresh(dropdown)</code></dt>
|
||||
<dd>Calls the component refresh method through the global dispatcher.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dropdown_menu()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/dropdown-menu.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dropdown-menu.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<div role="heading" id="account-options">My Account</div>
|
||||
<div role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem">Subscription</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-basic",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="dropdown-basic-account">
|
||||
<div role="heading" id="dropdown-basic-account">My Account</div>
|
||||
<div role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-basic", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-shortcuts"><a href="#example-shortcuts">Shortcuts</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-shortcuts",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">New Tab <span data-shortcut>⌘T</span></div>
|
||||
<div role="menuitem">New Window <span data-shortcut>⌘N</span></div>
|
||||
<div role="menuitem">New Private Window <span data-shortcut>⇧⌘N</span></div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Print <span data-shortcut>⌘P</span></div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-shortcuts", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-icons"><a href="#example-icons">Icons</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-icons",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">{% lucide "user" %} Profile</div>
|
||||
<div role="menuitem">{% lucide "credit-card" %} Billing</div>
|
||||
<div role="menuitem">{% lucide "settings" %} Settings</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">{% lucide "log-out" %} Log out</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-icons", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-checkboxes"><a href="#example-checkboxes">Checkboxes</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="dropdown-checkboxes-label">
|
||||
<div role="heading" id="dropdown-checkboxes-label">Appearance</div>
|
||||
<div role="menuitemcheckbox" aria-checked="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Status Bar
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
<div role="menuitemcheckbox" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Activity Bar
|
||||
<span data-shortcut>⌘A</span>
|
||||
</div>
|
||||
<div role="menuitemcheckbox" aria-checked="false" aria-disabled="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Panel
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-checkboxes');
|
||||
const checkboxes = dropdownMenu.querySelectorAll('[role="menuitemcheckbox"]');
|
||||
checkboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener('click', () => {
|
||||
if (checkbox.getAttribute('aria-disabled') === 'true') return;
|
||||
checkbox.setAttribute('aria-checked', checkbox.getAttribute('aria-checked') !== 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-checkboxes", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-radio-group"><a href="#example-radio-group">Radio Group</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-radio-group",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="dropdown-radio-label">
|
||||
<div role="heading" id="dropdown-radio-label">Panel Position</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Top
|
||||
</div>
|
||||
<div role="menuitemradio" aria-checked="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Bottom
|
||||
</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Right
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-radio-group');
|
||||
const radios = dropdownMenu.querySelectorAll('[role="menuitemradio"]');
|
||||
radios.forEach((radio) => {
|
||||
radio.addEventListener('click', () => {
|
||||
radios.forEach((item) => item.setAttribute('aria-checked', 'false'));
|
||||
radio.setAttribute('aria-checked', 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-radio-group", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-destructive",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">Edit</div>
|
||||
<div role="menuitem">Duplicate</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" data-variant="destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Delete
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-destructive", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Dropdown menus support document direction. Set <code>dir="rtl"</code> on the dropdown root or a parent element. Use logical alignment data attributes where possible.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-rtl",
|
||||
trigger="فتح",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56", "data-align": "end"}
|
||||
) %}
|
||||
<div role="heading" id="dropdown-rtl-label">الحساب</div>
|
||||
<div role="menuitem">الملف الشخصي <span data-shortcut>⌘P</span></div>
|
||||
<div role="menuitem">الإعدادات <span data-shortcut>⌘S</span></div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" data-variant="destructive">حذف</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-rtl", code | prettyHtml) }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.dropdown-menu</code></dt>
|
||||
<dd>Root component class. Must wrap the trigger and the <code>[data-popover]</code> content.</dd>
|
||||
<dt><code class="highlight language-html">[data-popover]</code></dt>
|
||||
<dd>Inline menu popover. Supports <code>data-side</code> and <code>data-align</code> through the shared popover positioning rules.</dd>
|
||||
<dt><code class="highlight language-html">[role="menu"]</code></dt>
|
||||
<dd>Menu container referenced by the trigger's <code>aria-controls</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitem"]</code></dt>
|
||||
<dd>Action item. Supports <code>aria-disabled="true"</code>, <code>data-inset</code>, and <code>data-variant="destructive"</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitemcheckbox"]</code></dt>
|
||||
<dd>Checkbox item. Use <code>aria-checked="true|false"</code> and an optional <code>[data-indicator]</code> child.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitemradio"]</code></dt>
|
||||
<dd>Radio item. Use <code>aria-checked="true|false"</code> and an optional <code>[data-indicator]</code> child.</dd>
|
||||
<dt><code class="highlight language-html">[role="heading"]</code></dt>
|
||||
<dd>Group label. Supports <code>data-inset</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="separator"]</code></dt>
|
||||
<dd>Visual separator between groups or actions.</dd>
|
||||
<dt><code class="highlight language-html">[data-shortcut]</code></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of a menu item.</dd>
|
||||
<dt><code class="highlight language-html">[data-indicator]</code></dt>
|
||||
<dd>Indicator slot for checkbox and radio menu items. It becomes visible when the item has <code>aria-checked="true"</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
@@ -0,0 +1,352 @@
|
||||
# Dropdown Menu
|
||||
|
||||
<Preview>
|
||||
<div id="demo-dropdown-menu" class="dropdown-menu">
|
||||
<button type="button" id="demo-dropdown-menu-trigger" aria-haspopup="menu" aria-controls="demo-dropdown-menu-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="demo-dropdown-menu-menu" aria-labelledby="demo-dropdown-menu-trigger">
|
||||
<div role="group" aria-labelledby="demo-dropdown-account">
|
||||
<div role="heading" id="demo-dropdown-account">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span data-shortcut>⇧⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<div role="menuitem">Keyboard shortcuts</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">Log out</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dropdown_menu" }}>
|
||||
This component ships a `dropdown_menu()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
Basecoat dropdown menus are inline-positioned relative to the `.dropdown-menu` wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and matches Basecoat's current popover/select positioning model.
|
||||
|
||||
<Steps>
|
||||
<Step title="Include the JavaScript files">
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0-beta.2/dist/js/dropdown-menu.min.js" defer></script>
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Add your dropdown menu HTML">
|
||||
|
||||
```html
|
||||
<div id="demo-dropdown-menu" class="dropdown-menu">
|
||||
<button type="button" id="demo-dropdown-menu-trigger" aria-haspopup="menu" aria-controls="demo-dropdown-menu-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="demo-dropdown-menu-menu" aria-labelledby="demo-dropdown-menu-trigger">
|
||||
<div role="group" aria-labelledby="demo-dropdown-account">
|
||||
<div role="heading" id="demo-dropdown-account">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span data-shortcut>⇧⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<div role="menuitem">Keyboard shortcuts</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">Log out</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
#### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="dropdown-menu"></code></dt>
|
||||
<dd>
|
||||
Relative wrapper for the trigger and inline menu content.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button aria-haspopup="menu" aria-expanded="false"></code></dt>
|
||||
<dd>Trigger button. The script toggles <code>aria-expanded</code> and manages keyboard navigation.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover aria-hidden="true"></code></dt>
|
||||
<dd>
|
||||
Menu content popover. Set <code>data-side="top|right|bottom|left"</code> and <code>data-align="start|center|end"</code> to control placement.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="menu"></code></dt>
|
||||
<dd>
|
||||
Container for menu items, groups, labels, and separators.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related menu items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading/label.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code></dt>
|
||||
<dd>Standard action item. Use <code>aria-disabled="true"</code> for disabled items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemcheckbox" aria-checked="true"></code></dt>
|
||||
<dd>Checkbox-style item. Add a child with <code>data-indicator</code> for the checked icon.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemradio" aria-checked="true"></code></dt>
|
||||
<dd>Radio-style item. Add a child with <code>data-indicator</code> for the selected icon.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups or options.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
### JavaScript API
|
||||
|
||||
| API | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `dropdown.open()` | Method | Opens the menu. |
|
||||
| `dropdown.close()` | Method | Closes the menu. |
|
||||
| `dropdown.toggle()` | Method | Toggles the menu. |
|
||||
| `dropdown.refresh()` | Method | Rescans menu items after children change inside the existing `role="menu"` element. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-basic" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-basic-trigger" aria-haspopup="menu" aria-controls="dropdown-basic-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-basic-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-basic-menu" aria-labelledby="dropdown-basic-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-basic-account">
|
||||
<div role="heading" id="dropdown-basic-account">My Account</div>
|
||||
<div role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Shortcuts
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-shortcuts" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-shortcuts-trigger" aria-haspopup="menu" aria-controls="dropdown-shortcuts-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-shortcuts-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-shortcuts-menu" aria-labelledby="dropdown-shortcuts-trigger">
|
||||
<div role="menuitem">
|
||||
New Tab
|
||||
<span data-shortcut>⌘T</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
New Window
|
||||
<span data-shortcut>⌘N</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
New Private Window
|
||||
<span data-shortcut>⇧⌘N</span>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">
|
||||
Print
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Icons
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-icons" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-icons-trigger" aria-haspopup="menu" aria-controls="dropdown-icons-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-icons-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-icons-menu" aria-labelledby="dropdown-icons-trigger">
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-user" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
||||
Profile
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-credit-card" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
Billing
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-settings" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
Settings
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">
|
||||
<svg class="lucide lucide-log-out" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 17 5-5-5-5" /><path d="M21 12H9" /><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /></svg>
|
||||
Log out
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Checkboxes
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-checkboxes" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-checkboxes-trigger" aria-haspopup="menu" aria-controls="dropdown-checkboxes-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-checkboxes-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-checkboxes-menu" aria-labelledby="dropdown-checkboxes-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-checkboxes-label">
|
||||
<div role="heading" id="dropdown-checkboxes-label">Appearance</div>
|
||||
<div role="menuitemcheckbox" aria-checked="true">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Status Bar
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
<div role="menuitemcheckbox" aria-checked="false">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Activity Bar
|
||||
<span data-shortcut>⌘A</span>
|
||||
</div>
|
||||
<div role="menuitemcheckbox" aria-checked="false" aria-disabled="true">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Panel
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector("#dropdown-checkboxes");
|
||||
const checkboxes = dropdownMenu.querySelectorAll('[role="menuitemcheckbox"]');
|
||||
checkboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener("click", () => {
|
||||
if (checkbox.getAttribute("aria-disabled") === "true") return;
|
||||
checkbox.setAttribute("aria-checked", checkbox.getAttribute("aria-checked") !== "true");
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
### Radio Group
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-radio-group" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-radio-group-trigger" aria-haspopup="menu" aria-controls="dropdown-radio-group-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-radio-group-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-radio-group-menu" aria-labelledby="dropdown-radio-group-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-radio-label">
|
||||
<div role="heading" id="dropdown-radio-label">Panel Position</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Top
|
||||
</div>
|
||||
<div role="menuitemradio" aria-checked="true">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Bottom
|
||||
</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>
|
||||
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
Right
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const dropdownMenu = document.querySelector("#dropdown-radio-group");
|
||||
const radios = dropdownMenu.querySelectorAll('[role="menuitemradio"]');
|
||||
radios.forEach((radio) => {
|
||||
radio.addEventListener("click", () => {
|
||||
radios.forEach((item) => item.setAttribute("aria-checked", "false"));
|
||||
radio.setAttribute("aria-checked", "true");
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-destructive" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-destructive-trigger" aria-haspopup="menu" aria-controls="dropdown-destructive-menu" aria-expanded="false" class="btn-outline">Open</button>
|
||||
<div id="dropdown-destructive-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-destructive-menu" aria-labelledby="dropdown-destructive-trigger">
|
||||
<div role="menuitem">Edit</div>
|
||||
<div role="menuitem">Duplicate</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" data-variant="destructive">
|
||||
<svg class="lucide lucide-trash-2" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /><path d="M14 11v6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M3 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></svg>
|
||||
Delete
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Dropdown menus support document direction. Set `dir="rtl"` on the dropdown root or a parent element. Use logical alignment data attributes where possible.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<div id="dropdown-rtl" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-rtl-trigger" aria-haspopup="menu" aria-controls="dropdown-rtl-menu" aria-expanded="false" class="btn-outline">فتح</button>
|
||||
<div id="dropdown-rtl-popover" data-popover aria-hidden="true" class="min-w-56" data-align="end">
|
||||
<div role="menu" id="dropdown-rtl-menu" aria-labelledby="dropdown-rtl-trigger">
|
||||
<div role="heading" id="dropdown-rtl-label">الحساب</div>
|
||||
<div role="menuitem">
|
||||
الملف الشخصي
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
الإعدادات
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem" data-variant="destructive">حذف</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,194 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Empty
|
||||
description: Display empty states with icons, titles, descriptions, and actions.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML structure
|
||||
id: usage-html-structure
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: Avatar
|
||||
id: example-avatar
|
||||
- label: Avatar Group
|
||||
id: example-avatar-group
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_default %}
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "folder-code" %}
|
||||
</figure>
|
||||
<h3>No Projects Yet</h3>
|
||||
<p>You haven't created any projects yet. Get started by creating your first project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn">Create Project</button>
|
||||
<button class="btn-outline">Import Project</button>
|
||||
</div>
|
||||
<a href="#" class="btn-link text-muted-foreground">
|
||||
Learn More
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-default", code_default) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>.empty</code> on a semantic container. The component styles common children like <code><header></code>, <code><figure></code>, headings, descriptions, and <code><footer></code>.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_default) }}
|
||||
|
||||
<h3 id="usage-html-structure"><a href="#usage-html-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><section class="empty"></code></dt>
|
||||
<dd>The empty-state container.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Groups the media, title, and description.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><figure></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Icon, avatar, image, or avatar group. An icon-only figure is styled automatically with <code>:has(> svg:only-child)</code>.</dd>
|
||||
<dt><code class="highlight language-html"><h2></code>, <code class="highlight language-html"><h3></code>, or <code class="highlight language-html"><h4></code></dt>
|
||||
<dd>The empty-state title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The empty-state description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> or <code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Actions and supporting content.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code_outline %}
|
||||
<section class="empty border">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "cloud" %}
|
||||
</figure>
|
||||
<h3>Cloud Storage Empty</h3>
|
||||
<p>Upload files to your cloud storage to access them anywhere.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm-outline">Upload Files</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-outline", code_outline) }}
|
||||
|
||||
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
|
||||
|
||||
{% set code_avatar %}
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="size-12 rounded-full">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
</figure>
|
||||
<h3>User Offline</h3>
|
||||
<p>This user is currently offline. You can leave a message or try again later.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">Leave Message</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-avatar", code_avatar) }}
|
||||
|
||||
<h3 id="example-avatar-group"><a href="#example-avatar-group">Avatar Group</a></h3>
|
||||
|
||||
{% set code_avatar_group %}
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="-space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-12 [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</figure>
|
||||
<h3>No Team Members</h3>
|
||||
<p>Invite your team to collaborate on this project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">
|
||||
{% lucide "plus" %}
|
||||
Invite Members
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-avatar-group", code_avatar_group) }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
{% set code_input_group %}
|
||||
<section class="empty">
|
||||
<header>
|
||||
<h3>404 - Not Found</h3>
|
||||
<p>The page you're looking for doesn't exist. Try searching for what you need below.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="input-group w-full">
|
||||
{% lucide "search" %}
|
||||
<input type="search" placeholder="Try searching for pages...">
|
||||
<kbd class="kbd">/</kbd>
|
||||
</div>
|
||||
<p>Need help? <a href="#">Contact support</a></p>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-input-group", code_input_group) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code_rtl %}
|
||||
<section class="empty" dir="rtl">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "folder-code" %}
|
||||
</figure>
|
||||
<h3>لا توجد مشاريع بعد</h3>
|
||||
<p>ابدأ بإنشاء مشروعك الأول.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">إنشاء مشروع</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-rtl", code_rtl) }}
|
||||
@@ -0,0 +1,168 @@
|
||||
# Empty
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure>
|
||||
<svg class="lucide lucide-folder-code" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg>
|
||||
</figure>
|
||||
<h3>No Projects Yet</h3>
|
||||
<p>You haven't created any projects yet. Get started by creating your first project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn">Create Project</button>
|
||||
<button class="btn-outline">Import Project</button>
|
||||
</div>
|
||||
<a href="#" class="btn-link text-muted-foreground">
|
||||
Learn More
|
||||
<svg class="lucide lucide-arrow-up-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
Use `.empty` on a semantic container. The component styles common children like `header`, `figure`, headings, descriptions, and `footer`.
|
||||
|
||||
```html
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure>
|
||||
<svg class="lucide lucide-folder-code" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg>
|
||||
</figure>
|
||||
<h3>No Projects Yet</h3>
|
||||
<p>You haven't created any projects yet. Get started by creating your first project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn">Create Project</button>
|
||||
<button class="btn-outline">Import Project</button>
|
||||
</div>
|
||||
<a href="#" class="btn-link text-muted-foreground">
|
||||
Learn More
|
||||
<svg class="lucide lucide-arrow-up-right" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M7 7h10v10" /><path d="M7 17 17 7" /></svg>
|
||||
</a>
|
||||
</footer>
|
||||
</section>
|
||||
```
|
||||
|
||||
#### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><section class="empty"></code></dt>
|
||||
<dd>
|
||||
The empty-state container.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>
|
||||
Groups the media, title, and description.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><figure></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Icon, avatar, image, or avatar group. An icon-only figure is styled automatically with <code>:has(> svg:only-child)</code>.</dd>
|
||||
<dt><code class="highlight language-html"><h2></code>, <code class="highlight language-html"><h3></code>, or <code class="highlight language-html"><h4></code></dt>
|
||||
<dd>The empty-state title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The empty-state description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> or <code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Actions and supporting content.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Outline
|
||||
|
||||
<Preview>
|
||||
<section class="empty border">
|
||||
<header>
|
||||
<figure>
|
||||
<svg class="lucide lucide-cloud" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></svg>
|
||||
</figure>
|
||||
<h3>Cloud Storage Empty</h3>
|
||||
<p>Upload files to your cloud storage to access them anywhere.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm-outline">Upload Files</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Avatar
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="size-12 rounded-full">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
</figure>
|
||||
<h3>User Offline</h3>
|
||||
<p>This user is currently offline. You can leave a message or try again later.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">Leave Message</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Avatar Group
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="-space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-12 [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" />
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png" />
|
||||
</figure>
|
||||
<h3>No Team Members</h3>
|
||||
<p>Invite your team to collaborate on this project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">
|
||||
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
Invite Members
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Input Group
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<h3>404 - Not Found</h3>
|
||||
<p>The page you're looking for doesn't exist. Try searching for what you need below.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<div class="input-group w-full">
|
||||
<svg class="lucide lucide-search" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
<input type="search" placeholder="Try searching for pages..." />
|
||||
<kbd class="kbd">/</kbd>
|
||||
</div>
|
||||
<p>Need help? <a href="#">Contact support</a></p>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<section class="empty" dir="rtl">
|
||||
<header>
|
||||
<figure>
|
||||
<svg class="lucide lucide-folder-code" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M10 10.5 8 13l2 2.5" /><path d="m14 10.5 2 2.5-2 2.5" /><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z" /></svg>
|
||||
</figure>
|
||||
<h3>لا توجد مشاريع بعد</h3>
|
||||
<p>ابدأ بإنشاء مشروعك الأول.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">إنشاء مشروع</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
@@ -1,60 +1,19 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Field
|
||||
description: Combine labels, controls, and help text to compose accessible form fields.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Input
|
||||
id: example-input
|
||||
- label: Textarea
|
||||
id: example-textarea
|
||||
- label: Select
|
||||
id: example-select
|
||||
- label: Slider
|
||||
id: example-slider
|
||||
- label: Fieldset
|
||||
id: example-fieldset
|
||||
- label: Checkbox
|
||||
id: example-checkbox
|
||||
- label: Radio Group
|
||||
id: example-radio
|
||||
- label: Switch
|
||||
id: example-switch
|
||||
- label: Choice Card
|
||||
id: example-choice-card
|
||||
- label: Field Group
|
||||
id: example-field-group
|
||||
- label: Responsive Layout
|
||||
id: example-responsive
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
# Field
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_demo %}
|
||||
<Preview>
|
||||
<form class="w-full max-w-md space-y-6">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Payment Method</legend>
|
||||
<p>All transactions are secure and encrypted</p>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="card-name">Name on Card</label>
|
||||
<input id="card-name" type="text" placeholder="Evil Rabbit" required>
|
||||
<input id="card-name" type="text" placeholder="Evil Rabbit" required />
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="card-number">Card Number</label>
|
||||
<input id="card-number" type="text" placeholder="1234 5678 9012 3456" aria-describedby="card-number-desc" required>
|
||||
<input id="card-number" type="text" placeholder="1234 5678 9012 3456" aria-describedby="card-number-desc" required />
|
||||
<p id="card-number-desc">Enter your 16-digit card number</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="exp-month">Month</label>
|
||||
@@ -88,121 +47,113 @@ toc:
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="cvv">CVV</label>
|
||||
<input id="cvv" type="text" placeholder="123" required>
|
||||
<input id="cvv" type="text" placeholder="123" required />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<hr role="separator" class="my-6 border-border">
|
||||
|
||||
<hr role="separator" class="my-6 border-border" />
|
||||
<fieldset class="fieldset">
|
||||
<legend>Billing Address</legend>
|
||||
<p>The billing address associated with your payment method</p>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="same-as-shipping" class="gap-3">
|
||||
<input type="checkbox" id="same-as-shipping" checked>
|
||||
<input type="checkbox" id="same-as-shipping" checked />
|
||||
Same as shipping address
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
<div role="group" class="field">
|
||||
<label for="comments">Comments</label>
|
||||
<textarea id="comments" placeholder="Add any additional comments" rows="3"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
<button type="button" class="btn-outline">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-demo", code_demo) }}
|
||||
## Usage
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
Field uses semantic HTML with minimal classes. Two main patterns can be used independently or composed together:
|
||||
|
||||
<section class="prose">
|
||||
<p>Field uses semantic HTML with minimal classes. Two main patterns can be used independently or composed together:</p>
|
||||
<ul>
|
||||
<li><code><div role="group" class="field"></code> for individual field containers (label, input, helper text, errors)</li>
|
||||
<li><code><fieldset class="fieldset"></code> for grouping multiple related fields together</li>
|
||||
</ul>
|
||||
<p>Elements inside these containers are styled automatically based on their purpose. Use <code>aria-describedby</code> to connect helper text and errors to inputs for accessibility.</p>
|
||||
<p><strong>Fields</strong> have the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" class="field"></code></dt>
|
||||
<dd>Field container. Add <code>data-orientation="horizontal"</code> to align controls horizontally. For responsive layouts, use standard Tailwind responsive classes.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><label></code></dt>
|
||||
<dd>Label for the input.</dd>
|
||||
<dt><code class="highlight language-html"><input></code>, <code><select></code> or <code><textarea></code></dt>
|
||||
<dd>Form input.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Helper text. Use <code>aria-describedby</code> on the input to reference this element's <code>id</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p role="alert"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Error message. Use <code>aria-describedby</code> on the input and <code>aria-invalid="true"</code> to reference this element's <code>id</code>.</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Wraps label and description when the label sits beside the input (e.g. <code>data-orientation="horizontal"</code>). Not required if you have no description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
<p><strong>Fieldsets</strong> have the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><fieldset class="fieldset"></code></dt>
|
||||
<dd>Container for grouping related fields.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><legend></code></dt>
|
||||
<dd>Heading for the fieldset.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Description text. When placed directly after <code><legend></code>, automatically styled as muted text.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group" class="field"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Individual field containers (see structure above). You can include multiple <code>.field</code> elements, or place form controls directly in the fieldset.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
- `<div role="group" class="field">` for individual field containers.
|
||||
- `<fieldset class="fieldset">` for grouping multiple related fields together.
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
Elements inside these containers are styled automatically based on their purpose. Use `aria-describedby` to connect helper text and errors to inputs.
|
||||
|
||||
<h3 id="example-input"><a href="#example-input">Input</a></h3>
|
||||
**Fields** have the following HTML structure:
|
||||
|
||||
{% set code_input %}
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" class="field"></code></dt>
|
||||
<dd>
|
||||
Field container. Add <code>data-orientation="horizontal"</code> to align controls horizontally. For responsive layouts, use standard Tailwind responsive classes.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><label></code></dt>
|
||||
<dd>Label for the input.</dd>
|
||||
<dt><code class="highlight language-html"><input></code>, <code><select></code> or <code><textarea></code></dt>
|
||||
<dd>Form input.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Helper text. Use <code>aria-describedby</code> on the input to reference this element's <code>id</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p role="alert"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Error message. Use <code>aria-describedby</code> on the input and <code>aria-invalid="true"</code> to reference this element's <code>id</code>.</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Wraps label and description when the label sits beside the input (e.g. <code>data-orientation="horizontal"</code>). Not required if you have no description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
**Fieldsets** have the following HTML structure:
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><fieldset class="fieldset"></code></dt>
|
||||
<dd>
|
||||
Container for grouping related fields.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><legend></code></dt>
|
||||
<dd>Heading for the fieldset.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Description text. When placed directly after <code><legend></code>, automatically styled as muted text.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group" class="field"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Individual field containers (see structure above). You can include multiple <code>.field</code> elements, or place form controls directly in the fieldset.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Input
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="grid gap-6">
|
||||
<div role="group" class="field">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" type="text" placeholder="evilrabbit" aria-describedby="username-desc">
|
||||
<input id="username" type="text" placeholder="evilrabbit" aria-describedby="username-desc" />
|
||||
<p id="username-desc">Choose a unique username for your account.</p>
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="password">Password</label>
|
||||
<p id="password-desc">Must be at least 8 characters long.</p>
|
||||
<input id="password" type="password" placeholder="••••••••" aria-describedby="password-desc">
|
||||
<input id="password" type="password" placeholder="••••••••" aria-describedby="password-desc" />
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-input", code_input, class="w-full max-w-md") }}
|
||||
### Textarea
|
||||
|
||||
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
|
||||
|
||||
{% set code_textarea %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<div role="group" class="field">
|
||||
<label for="bio">Feedback</label>
|
||||
<textarea id="bio" rows="4" placeholder="Your feedback helps us improve..." aria-describedby="bio-desc"></textarea>
|
||||
<p id="bio-desc">Share your thoughts about our service.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-textarea", code_textarea, class="w-full max-w-md") }}
|
||||
### Select
|
||||
|
||||
<h3 id="example-select"><a href="#example-select">Select</a></h3>
|
||||
|
||||
{% set code_select %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<div role="group" class="field">
|
||||
<label for="country">Department</label>
|
||||
<select id="country" class="select w-full" aria-describedby="country-desc">
|
||||
@@ -218,17 +169,15 @@ toc:
|
||||
</select>
|
||||
<p id="country-desc">Select your department or area of work.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-select", code_select, class="w-full max-w-md") }}
|
||||
### Slider
|
||||
|
||||
<h3 id="example-slider"><a href="#example-slider">Slider</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" class="field">
|
||||
<label for="price-range">Price</label>
|
||||
<p id="price-range-desc">Set your budget: $<span id="price-range-value">150</span></p>
|
||||
<input id="price-range" type="range" min="0" max="500" value="150">
|
||||
<input id="price-range" type="range" min="0" max="500" value="150" />
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
@@ -240,7 +189,7 @@ toc:
|
||||
const max = parseFloat(el.max || 100);
|
||||
const value = parseFloat(el.value);
|
||||
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
|
||||
el.style.setProperty('--slider-value', `${percent}%`);
|
||||
el.style.setProperty('--slider-value', \`\${percent}%\`);
|
||||
};
|
||||
|
||||
sliders.forEach(slider => {
|
||||
@@ -254,159 +203,135 @@ toc:
|
||||
priceSlider.addEventListener('input', (event) => priceValue.innerHTML = event.target.value);
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("slider", code, "w-full max-w-sm") }}
|
||||
</Preview>
|
||||
|
||||
<h3 id="example-fieldset"><a href="#example-fieldset">Fieldset</a></h3>
|
||||
### Fieldset
|
||||
|
||||
<section class="prose">
|
||||
<p>Group related fields with <code><fieldset class="fieldset"></code> and <code><legend></code>.</p>
|
||||
</section>
|
||||
Group related fields with `<fieldset class="fieldset">` and `<legend>`.
|
||||
|
||||
{% set code_fieldset %}
|
||||
<Preview>
|
||||
<form class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Profile</legend>
|
||||
<p>This information will be displayed on your profile</p>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="full-name">Full name</label>
|
||||
<input id="full-name" type="text" placeholder="Evil Rabbit" aria-describedby="name-desc">
|
||||
<input id="full-name" type="text" placeholder="Evil Rabbit" aria-describedby="name-desc" />
|
||||
<p id="name-desc">Your first and last name</p>
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="display-name">Username</label>
|
||||
<input id="display-name" type="text" placeholder="@evilrabbit" aria-invalid="true" aria-describedby="username-error">
|
||||
<input id="display-name" type="text" placeholder="@evilrabbit" aria-invalid="true" aria-describedby="username-error" />
|
||||
<p id="username-error" role="alert">Username is already taken</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" id="newsletter">
|
||||
<input type="checkbox" id="newsletter" />
|
||||
<label for="newsletter" class="font-normal">Subscribe to the newsletter</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" class="btn mt-4">Save changes</button>
|
||||
</form>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-fieldset", code_fieldset) }}
|
||||
### Checkbox
|
||||
|
||||
<h3 id="example-checkbox"><a href="#example-checkbox">Checkbox</a></h3>
|
||||
|
||||
{% set code_checkbox %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Show these items on the desktop</legend>
|
||||
<p>Select the items you want to show on the desktop.</p>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
<input type="checkbox" />
|
||||
Hard disks
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
<input type="checkbox" />
|
||||
External disks
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
<input type="checkbox" />
|
||||
CDs, DVDs and iPods
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
<input type="checkbox" />
|
||||
Connected servers
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
{# <div class="flex w-full items-center gap-3"> #}
|
||||
<input id="sync-desktop-documents" type="checkbox" checked class="input self-start">
|
||||
<section>
|
||||
<label for="sync-desktop-documents">Sync Desktop & Documents folders</label>
|
||||
<p>Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.</p>
|
||||
</section>
|
||||
{# </div> #}
|
||||
<hr role="separator" />
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input id="sync-desktop-documents" type="checkbox" checked class="input self-start" />
|
||||
<section>
|
||||
<label for="sync-desktop-documents">Sync Desktop & Documents folders</label>
|
||||
<p>Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.</p>
|
||||
</section>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-checkbox", code_checkbox, class="w-full max-w-md") }}
|
||||
### Radio Group
|
||||
|
||||
<h3 id="example-radio"><a href="#example-radio">Radio Group</a></h3>
|
||||
|
||||
{% set code_radio %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Subscription plan</legend>
|
||||
<p>Yearly and lifetime plans offer significant savings.</p>
|
||||
|
||||
<div role="radiogroup" class="grid gap-3">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan" checked>
|
||||
<input type="radio" name="subscription-plan" checked />
|
||||
Monthly ($9.99/month)
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan">
|
||||
<input type="radio" name="subscription-plan" />
|
||||
Yearly ($99.99/year)
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan">
|
||||
<input type="radio" name="subscription-plan" />
|
||||
Lifetime ($299.99)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-radio", code_radio, class="w-full max-w-md") }}
|
||||
### Switch
|
||||
|
||||
<h3 id="example-switch"><a href="#example-switch">Switch</a></h3>
|
||||
Set `data-orientation="horizontal"` on `.field` to align labels and controls side-by-side.
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>data-orientation="horizontal"</code> on <code>.field</code> to align labels and controls side-by-side.</p>
|
||||
</section>
|
||||
|
||||
{% set code_switch %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<section>
|
||||
<label for="multi-factor-authentication">Multi-factor authentication</label>
|
||||
<p>Enable multi-factor authentication. If you do not have a two-factor device, you can use a one-time code sent to your email.</p>
|
||||
</section>
|
||||
<input id="multi-factor-authentication" type="checkbox" role="switch">
|
||||
<input id="multi-factor-authentication" type="checkbox" role="switch" />
|
||||
</div>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-switch", code_switch, class="w-full max-w-md") }}
|
||||
### Choice Card
|
||||
|
||||
<h3 id="example-choice-card"><a href="#example-choice-card">Choice Card</a></h3>
|
||||
|
||||
{% set code_choice_card %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend class="text-sm">Compute Environment</legend>
|
||||
<p>Select the compute environment for your cluster.</p>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label class="flex gap-3 border rounded-md p-4 has-[:checked]:border-primary has-[:checked]:bg-primary/5 dark:has-[:checked]:bg-primary/10">
|
||||
<section>
|
||||
<h3>Kubernetes</h3>
|
||||
<p>Run GPU workloads on a K8s configured cluster.</p>
|
||||
</section>
|
||||
<input id="choice-card" type="radio" name="choice-card" class="self-start" checked>
|
||||
<input id="choice-card" type="radio" name="choice-card" class="self-start" checked />
|
||||
</label>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
@@ -415,66 +340,53 @@ toc:
|
||||
<h3>Virtual Machine</h3>
|
||||
<p>Access a VM configured cluster to run GPU workloads.</p>
|
||||
</section>
|
||||
<input id="choice-card" type="radio" name="choice-card" class="self-start">
|
||||
<input id="choice-card" type="radio" name="choice-card" class="self-start" />
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-choice-card", code_choice_card, class="w-full max-w-md") }}
|
||||
### Field Group
|
||||
|
||||
<h3 id="example-field-group"><a href="#example-field-group">Field Group</a></h3>
|
||||
|
||||
{% set code_field_group %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Account</legend>
|
||||
<p>Use a field group to stack related fields with consistent spacing.</p>
|
||||
|
||||
<div role="group" aria-label="Field group">
|
||||
<div role="group" class="field">
|
||||
<label for="field-group-name">Name</label>
|
||||
<input id="field-group-name" type="text" placeholder="Evil Rabbit">
|
||||
<input id="field-group-name" type="text" placeholder="Evil Rabbit" />
|
||||
</div>
|
||||
|
||||
<div class="field-separator">
|
||||
<hr role="separator">
|
||||
<hr role="separator" />
|
||||
<span>Optional</span>
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="field-group-bio">Bio</label>
|
||||
<textarea id="field-group-bio" rows="3" placeholder="Tell us about yourself"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-group", code_field_group, class="w-full max-w-md") }}
|
||||
### Responsive Layout
|
||||
|
||||
<h3 id="example-responsive"><a href="#example-responsive">Responsive Layout</a></h3>
|
||||
For responsive layouts, use standard Tailwind responsive classes directly on `.field`. This example stacks vertically on mobile and switches to horizontal at the `md` breakpoint.
|
||||
|
||||
<section class="prose">
|
||||
<p>For responsive layouts, use standard Tailwind responsive classes directly on <code>.field</code>. This example stacks vertically on mobile and switches to horizontal at the <code>md</code> breakpoint.</p>
|
||||
</section>
|
||||
|
||||
{% set code_responsive %}
|
||||
<Preview class="w-full max-w-3xl">
|
||||
<fieldset class="fieldset">
|
||||
<legend>Profile</legend>
|
||||
<p>Keep your profile details up to date.</p>
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<hr role="separator" />
|
||||
<div class="flex flex-col gap-7">
|
||||
<div role="group" class="field flex-col md:flex-row md:items-center">
|
||||
<section class="md:flex-auto">
|
||||
<label for="profile-name">Name</label>
|
||||
<p id="profile-name-desc">Provide your full name for identification.</p>
|
||||
</section>
|
||||
<input id="profile-name" type="text" placeholder="Evil Rabbit" aria-describedby="profile-name-desc" class="md:w-auto md:min-w-80">
|
||||
<input id="profile-name" type="text" placeholder="Evil Rabbit" aria-describedby="profile-name-desc" class="md:w-auto md:min-w-80" />
|
||||
</div>
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<hr role="separator" />
|
||||
<div role="group" class="field flex-col md:flex-row md:items-start">
|
||||
<section class="md:flex-auto">
|
||||
<label for="profile-bio">Message</label>
|
||||
@@ -482,38 +394,29 @@ toc:
|
||||
</section>
|
||||
<textarea id="profile-bio" rows="3" placeholder="Hello, world!" aria-describedby="profile-bio-desc" class="md:w-auto md:min-w-80"></textarea>
|
||||
</div>
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<hr role="separator" />
|
||||
<div role="group" class="field flex-col md:flex-row md:items-center">
|
||||
<button type="submit" class="btn md:w-auto">Submit</button>
|
||||
<button type="button" class="btn-outline md:w-auto">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
</Preview>
|
||||
|
||||
{{ code_preview("field-responsive", code_responsive, class="w-full max-w-3xl") }}
|
||||
### RTL
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
Fields support right-to-left layouts through native form direction and logical spacing. Add `dir="rtl"` to the field, fieldset, or an ancestor.
|
||||
|
||||
<section class="prose">
|
||||
<p>Fields support right-to-left layouts through native form direction and logical spacing. Add <code>dir="rtl"</code> to the field, fieldset, or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<Preview class="w-full max-w-md">
|
||||
<fieldset class="fieldset" dir="rtl">
|
||||
<legend>الملف الشخصي</legend>
|
||||
<p>تظهر هذه المعلومات في حسابك.</p>
|
||||
|
||||
<div role="group" aria-label="Field group">
|
||||
<div role="group" class="field">
|
||||
<label for="field-rtl-name">الاسم الكامل</label>
|
||||
<input id="field-rtl-name" type="text" placeholder="أرنب شرير">
|
||||
<input id="field-rtl-name" type="text" placeholder="أرنب شرير" />
|
||||
<p>اكتب اسمك الأول والأخير.</p>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("field-rtl", code_rtl, class="w-full max-w-md") }}
|
||||
</Preview>
|
||||
@@ -1,500 +0,0 @@
|
||||
---
|
||||
templateEngineOverride: njk
|
||||
layout: layouts/page.njk
|
||||
title: Input Group
|
||||
description: Add icons, text, buttons, and helper content to inputs and textareas.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Align
|
||||
id: example-align
|
||||
children:
|
||||
- label: inline-start
|
||||
id: align-inline-start
|
||||
- label: inline-end
|
||||
id: align-inline-end
|
||||
- label: block-start
|
||||
id: align-block-start
|
||||
- label: block-end
|
||||
id: align-block-end
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: Text
|
||||
id: example-text
|
||||
- label: Button
|
||||
id: example-button
|
||||
- label: Kbd
|
||||
id: example-kbd
|
||||
- label: Dropdown
|
||||
id: example-dropdown
|
||||
- label: Spinner
|
||||
id: example-spinner
|
||||
- label: Textarea
|
||||
id: example-textarea
|
||||
- label: Custom Input
|
||||
id: example-custom-input
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
{% set spinner_icon %}{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}{% endset %}
|
||||
|
||||
{% set code_default %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com">
|
||||
<span data-align="start">https://</span>
|
||||
<button type="button" class="btn-xs-icon-ghost rounded-full" data-align="end" aria-label="Info">
|
||||
{% lucide "info" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="input-group min-h-32" data-orientation="vertical">
|
||||
<textarea placeholder="Ask, Search or Chat..."></textarea>
|
||||
<footer data-align="end" role="group" aria-label="Message actions">
|
||||
<button type="button" class="btn-xs-icon-outline rounded-full" aria-label="Add attachment">{% lucide "plus" %}</button>
|
||||
{% set trigger %}Auto{% endset %}
|
||||
{{ dropdown_menu(
|
||||
main_attrs={"class": "min-w-0"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-xs-ghost"},
|
||||
popover_attrs={"data-side": "top", "data-align": "start", "class": "min-w-28 [--radius:0.95rem]"},
|
||||
items=[
|
||||
{ type: "item", label: "Auto" },
|
||||
{ type: "item", label: "Agent" },
|
||||
{ type: "item", label: "Manual" }
|
||||
]
|
||||
) }}
|
||||
<span class="ms-auto">52% used</span>
|
||||
<hr role="separator" class="h-4">
|
||||
<button type="button" class="btn-xs-icon rounded-full" disabled aria-label="Send">{% lucide "arrow-up" %}</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="@shadcn">
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<span class="flex size-4 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||
{% lucide "check", { "class": "size-3" } %}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-default", code_default | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
{% set code_usage %}
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<!-- icon -->
|
||||
</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_block(code_usage | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>.input-group</code> on the root. The group owns the visible shell. Native controls stay semantic and become borderless children inside that shell.</p>
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="input-group"></code></dt>
|
||||
<dd>The root shell. Add <code>role="group"</code> and an accessible label only when the group itself needs to be named.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input></code>, <code class="highlight language-html"><textarea></code>, <code class="highlight language-html"><select></code></dt>
|
||||
<dd>The native control. Use <code>aria-invalid="true"</code> for invalid state and <code>disabled</code> for disabled state.</dd>
|
||||
<dt><code class="highlight language-html"><span></code>, <code class="highlight language-html"><svg></code>, <code class="highlight language-html"><kbd></code></dt>
|
||||
<dd>Decorative or helper content. Use <code>aria-hidden="true"</code> for decorative icons.</dd>
|
||||
<dt><code class="highlight language-html"><button></code>, <code class="highlight language-html"><div role="group"></code></dt>
|
||||
<dd>Interactive actions. Use <code>role="group"</code> with an accessible label when multiple related actions share one side of the input.</dd>
|
||||
<dt><code class="highlight language-html"><header></code>, <code class="highlight language-html"><footer></code></dt>
|
||||
<dd>Header or footer addon rows for vertically oriented input groups.</dd>
|
||||
<dt><code class="highlight language-html">data-align="start|end"</code></dt>
|
||||
<dd>Optional visual placement. Values use logical direction, so start/end automatically flip in RTL. Use <code>data-orientation="vertical"</code> on the root for header/footer layouts.</dd>
|
||||
<dt><code class="highlight language-html">[data-control]</code></dt>
|
||||
<dd>Escape hatch for custom controls that are not native <code>input</code>, <code>textarea</code>, or <code>select</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-align"><a href="#example-align">Align</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>data-align="start|end"</code> on addon wrappers to position them inline. Use <code>data-orientation="vertical"</code> with <code>header</code> or <code>footer</code> for block placement.</p>
|
||||
</section>
|
||||
|
||||
<h4 id="align-inline-start"><a href="#align-inline-start">inline-start</a></h4>
|
||||
|
||||
{% set code_align_inline_start %}
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-start-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-start-input" type="text" placeholder="Search...">
|
||||
<span role="group" data-align="start" aria-hidden="true">
|
||||
{% lucide "search", { "class": "text-muted-foreground" } %}
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the start.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-inline-start", code_align_inline_start | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-inline-end"><a href="#align-inline-end">inline-end</a></h4>
|
||||
|
||||
{% set code_align_inline_end %}
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-end-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-end-input" type="password" placeholder="Enter password">
|
||||
<span role="group" data-align="end" aria-hidden="true">
|
||||
{% lucide "eye-off" %}
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the end.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-inline-end", code_align_inline_end | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-block-start"><a href="#align-block-start">block-start</a></h4>
|
||||
|
||||
{% set code_align_block_start %}
|
||||
<div role="group" aria-label="Block start input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-start-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-start-input" type="text" placeholder="Enter your name">
|
||||
<header data-align="start">
|
||||
<span>Full Name</span>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the input.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="block-start-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-start-textarea" class="font-mono text-sm" placeholder="console.log('Hello, world!');"></textarea>
|
||||
<header data-align="start">
|
||||
{% lucide "file-code", { "class": "text-muted-foreground" } %}
|
||||
<span class="font-mono">script.js</span>
|
||||
<button type="button" class="btn-xs-icon-ghost ml-auto" aria-label="Copy">
|
||||
{% lucide "copy" %}
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-block-start", code_align_block_start | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-block-end"><a href="#align-block-end">block-end</a></h4>
|
||||
|
||||
{% set code_align_block_end %}
|
||||
<div role="group" aria-label="Block end input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-end-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-end-input" type="text" placeholder="Enter amount">
|
||||
<footer data-align="end">
|
||||
<span>USD</span>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the input.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="block-end-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-end-textarea" placeholder="Write a comment..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>0/280</span>
|
||||
<button type="button" class="btn-sm ml-auto">Post</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-block-end", code_align_block_end | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code_icon %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="email" placeholder="Enter your email">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "mail" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "credit-card" %}</span>
|
||||
<span data-align="end" aria-hidden="true">{% lucide "check" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number">
|
||||
<span data-align="end" aria-hidden="true">
|
||||
{% lucide "star" %}
|
||||
{% lucide "info" %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-icon", code_icon | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-text"><a href="#example-text">Text</a></h3>
|
||||
|
||||
{% set code_text %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="0.00">
|
||||
<span data-align="start">$</span>
|
||||
<span data-align="end">USD</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com">
|
||||
<span data-align="start">https://</span>
|
||||
<span data-align="end">.com</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter your username">
|
||||
<span data-align="end">@company.com</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea placeholder="Enter your message"></textarea>
|
||||
<footer data-align="end">
|
||||
<span class="text-xs text-muted-foreground">120 characters left</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-text", code_text | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-button"><a href="#example-button">Button</a></h3>
|
||||
|
||||
{% set code_button %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" readonly placeholder="https://x.com/shadcn">
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Copy" title="Copy">
|
||||
{% lucide "copy" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="input-group [--radius:9999px]">
|
||||
<button type="button" class="btn-xs-icon-secondary" data-align="start" aria-label="Connection information">
|
||||
{% lucide "info" %}
|
||||
</button>
|
||||
<span data-align="start" class="text-muted-foreground">https://</span>
|
||||
<input type="text" id="input-secure-19">
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Favorite">
|
||||
{% lucide "star" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Type to search...">
|
||||
<button type="button" class="btn-secondary" data-align="end">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-button", code_button | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-kbd"><a href="#example-kbd">Kbd</a></h3>
|
||||
|
||||
{% set code_kbd %}
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<kbd data-align="end">⌘K</kbd>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-kbd", code_kbd | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-dropdown"><a href="#example-dropdown">Dropdown</a></h3>
|
||||
|
||||
{% set code_dropdown %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter file name">
|
||||
{% set trigger %}{% lucide "ellipsis" %}{% endset %}
|
||||
{{ dropdown_menu(
|
||||
main_attrs={"data-align": "end"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-xs-icon-ghost", "aria-label": "Open file actions"},
|
||||
popover_attrs={"data-align": "end", "class": "min-w-32"},
|
||||
items=[
|
||||
{ type: "item", label: "Settings" },
|
||||
{ type: "item", label: "Copy path" },
|
||||
{ type: "item", label: "Open location" }
|
||||
]
|
||||
) }}
|
||||
</div>
|
||||
|
||||
<div class="input-group [--radius:1rem]">
|
||||
<input type="text" placeholder="Enter search query">
|
||||
{% set trigger %}Search In... {% lucide "chevron-down", { "class": "size-3" } %}{% endset %}
|
||||
{{ dropdown_menu(
|
||||
main_attrs={"data-align": "end"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-xs-ghost pr-1.5! text-xs"},
|
||||
popover_attrs={"data-align": "end", "class": "min-w-32 [--radius:0.95rem]"},
|
||||
items=[
|
||||
{ type: "item", label: "Documentation" },
|
||||
{ type: "item", label: "Blog Posts" },
|
||||
{ type: "item", label: "Changelog" }
|
||||
]
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-dropdown", code_dropdown | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
|
||||
|
||||
{% set code_spinner %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Searching...">
|
||||
<span data-align="end">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Processing...">
|
||||
<span data-align="start">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Saving changes...">
|
||||
<span data-align="end">
|
||||
Saving...
|
||||
{{ spinner_icon | safe }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Refreshing data...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "loader", { "class": "animate-spin" } %}</span>
|
||||
<span data-align="end" class="text-muted-foreground">Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-spinner", code_spinner | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
|
||||
|
||||
{% set code_textarea %}
|
||||
<div class="input-group min-h-[200px]" data-orientation="vertical">
|
||||
<textarea placeholder="console.log('Hello, world!');"></textarea>
|
||||
<footer data-align="end" class="border-t">
|
||||
<span>Line 1, Column 1</span>
|
||||
<button type="button" class="btn-sm ml-auto">Run {% lucide "corner-down-left" %}</button>
|
||||
</footer>
|
||||
<header data-align="start" class="border-b">
|
||||
{% lucide "file-code", { "class": "text-muted-foreground" } %}
|
||||
<span>script.js</span>
|
||||
<div role="group" aria-label="File actions" class="ml-auto flex gap-1">
|
||||
<button type="button" class="btn-xs-icon-ghost">{% lucide "refresh-ccw" %}</button>
|
||||
<button type="button" class="btn-xs-icon-ghost">{% lucide "copy" %}</button>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-textarea", code_textarea | prettyHtml, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-custom-input"><a href="#example-custom-input">Custom Input</a></h3>
|
||||
|
||||
{% set code_custom %}
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea
|
||||
data-control
|
||||
placeholder="Autoresize textarea..."
|
||||
class="field-sizing-content min-h-16 resize-none"
|
||||
></textarea>
|
||||
<footer data-align="end">
|
||||
<button type="button" class="btn-sm ml-auto">Submit</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-custom", code_custom | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl" class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="بحث...">
|
||||
<span role="group" data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<span role="group" data-align="end">١٢ نتيجة</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري البحث...">
|
||||
<span role="group" data-align="end">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري حفظ التغييرات...">
|
||||
<span role="group" data-align="end">
|
||||
جاري الحفظ...
|
||||
{{ spinner_icon | safe }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="rtl-textarea">منطقة النص</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="rtl-textarea" placeholder="اكتب تعليقًا..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>٠/٢٨٠</span>
|
||||
<button type="button" class="btn-sm ms-auto">نشر</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>تذييل موضع أسفل منطقة النص.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-rtl", code_rtl | prettyHtml, class="w-full max-w-sm") }}
|
||||
@@ -0,0 +1,428 @@
|
||||
# Input Group
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-search" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com" />
|
||||
<span data-align="start">https://</span>
|
||||
<button type="button" class="btn-xs-icon-ghost rounded-full" data-align="end" aria-label="Info">
|
||||
<svg class="lucide lucide-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group min-h-32" data-orientation="vertical">
|
||||
<textarea placeholder="Ask, Search or Chat..."></textarea>
|
||||
<footer data-align="end" role="group" aria-label="Message actions">
|
||||
<button type="button" class="btn-xs-icon-outline rounded-full" aria-label="Add attachment">
|
||||
<svg class="lucide lucide-plus" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-670915" class="dropdown-menu min-w-0">
|
||||
<button type="button" id="dropdown-menu-670915-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-670915-menu" aria-expanded="false" class="btn-xs-ghost">Auto</button>
|
||||
<div id="dropdown-menu-670915-popover" data-popover aria-hidden="true" data-side="top" data-align="start" class="min-w-28 [--radius:0.95rem]">
|
||||
<div role="menu" id="dropdown-menu-670915-menu" aria-labelledby="dropdown-menu-670915-trigger">
|
||||
<div id="dropdown-menu-670915-items-1" role="menuitem">Auto</div>
|
||||
<div id="dropdown-menu-670915-items-2" role="menuitem">Agent</div>
|
||||
<div id="dropdown-menu-670915-items-3" role="menuitem">Manual</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="ms-auto">52% used</span>
|
||||
<hr role="separator" class="h-4" />
|
||||
<button type="button" class="btn-xs-icon rounded-full" disabled aria-label="Send">
|
||||
<svg class="lucide lucide-arrow-up" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="@shadcn" />
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<span class="flex size-4 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||
<svg class="size-3 lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<!-- icon -->
|
||||
</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
Use `.input-group` on the root. Native controls stay semantic and become borderless children inside that shell.
|
||||
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="input-group"></code></dt>
|
||||
<dd>
|
||||
The root shell. Add <code>role="group"</code> and an accessible label only when the group itself needs to be named.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input></code>, <code class="highlight language-html"><textarea></code>, <code class="highlight language-html"><select></code></dt>
|
||||
<dd>The native control. Use <code>aria-invalid="true"</code> for invalid state and <code>disabled</code> for disabled state.</dd>
|
||||
<dt><code class="highlight language-html"><span></code>, <code class="highlight language-html"><svg></code>, <code class="highlight language-html"><kbd></code></dt>
|
||||
<dd>Decorative or helper content. Use <code>aria-hidden="true"</code> for decorative icons.</dd>
|
||||
<dt><code class="highlight language-html"><button></code>, <code class="highlight language-html"><div role="group"></code></dt>
|
||||
<dd>Interactive actions. Use <code>role="group"</code> with an accessible label when multiple related actions share one side of the input.</dd>
|
||||
<dt><code class="highlight language-html"><header></code>, <code class="highlight language-html"><footer></code></dt>
|
||||
<dd>Header or footer addon rows for vertically oriented input groups.</dd>
|
||||
<dt><code class="highlight language-html">data-align="start|end"</code></dt>
|
||||
<dd>Optional visual placement. Values use logical direction, so start/end automatically flip in RTL. Use <code>data-orientation="vertical"</code> on the root for header/footer layouts.</dd>
|
||||
<dt><code class="highlight language-html">[data-control]</code></dt>
|
||||
<dd>Escape hatch for custom controls that are not native <code>input</code>, <code>textarea</code>, or <code>select</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
Use `data-align="start|end"` on addon wrappers to position them inline. Use `data-orientation="vertical"` with `header` or `footer` for block placement.
|
||||
|
||||
### inline-start
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-start-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-start-input" type="text" placeholder="Search..." />
|
||||
<span role="group" data-align="start" aria-hidden="true">
|
||||
<svg class="text-muted-foreground lucide lucide-search" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the start.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### inline-end
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-end-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-end-input" type="password" placeholder="Enter password" />
|
||||
<span role="group" data-align="end" aria-hidden="true">
|
||||
<svg class="lucide lucide-eye-off" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" /><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" /><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" /><path d="m2 2 20 20" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the end.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### block-start
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" aria-label="Block start input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-start-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-start-input" type="text" placeholder="Enter your name" />
|
||||
<header data-align="start">
|
||||
<span>Full Name</span>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the input.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="block-start-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-start-textarea" class="font-mono text-sm" placeholder="console.log('Hello, world!');"></textarea>
|
||||
<header data-align="start">
|
||||
<svg class="text-muted-foreground lucide lucide-file-code" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /><path d="M10 12.5 8 15l2 2.5" /><path d="m14 12.5 2 2.5-2 2.5" /></svg>
|
||||
<span class="font-mono">script.js</span>
|
||||
<button type="button" class="btn-xs-icon-ghost ml-auto" aria-label="Copy">
|
||||
<svg class="lucide lucide-copy" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### block-end
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div role="group" aria-label="Block end input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-end-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-end-input" type="text" placeholder="Enter amount" />
|
||||
<footer data-align="end">
|
||||
<span>USD</span>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the input.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="block-end-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-end-textarea" placeholder="Write a comment..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>0/280</span>
|
||||
<button type="button" class="btn-sm ml-auto">Post</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Icon
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-search" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="email" placeholder="Enter your email" />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-mail" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /><rect x="2" y="4" width="20" height="16" rx="2" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number" />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-credit-card" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="5" rx="2" /><line x1="2" x2="22" y1="10" y2="10" /></svg>
|
||||
</span>
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<svg class="lucide lucide-check" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number" />
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<svg class="lucide lucide-star" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg>
|
||||
<svg class="lucide lucide-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Text
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="0.00" />
|
||||
<span data-align="start">$</span>
|
||||
<span data-align="end">USD</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com" />
|
||||
<span data-align="start">https://</span>
|
||||
<span data-align="end">.com</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter your username" />
|
||||
<span data-align="end">@company.com</span>
|
||||
</div>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea placeholder="Enter your message"></textarea>
|
||||
<footer data-align="end">
|
||||
<span class="text-xs text-muted-foreground">120 characters left</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Button
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" readonly placeholder="https://x.com/shadcn" />
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Copy" title="Copy">
|
||||
<svg class="lucide lucide-copy" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group [--radius:9999px]">
|
||||
<button type="button" class="btn-xs-icon-secondary" data-align="start" aria-label="Connection information">
|
||||
<svg class="lucide lucide-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
|
||||
</button>
|
||||
<span data-align="start" class="text-muted-foreground">https://</span>
|
||||
<input type="text" id="input-secure-19" />
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Favorite">
|
||||
<svg class="lucide lucide-star" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Type to search..." />
|
||||
<button type="button" class="btn-secondary" data-align="end">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Kbd
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-search" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
<span data-align="end"><kbd class="kbd">⌘K</kbd></span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Dropdown
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter file name" />
|
||||
<div id="dropdown-menu-601971" class="dropdown-menu" data-align="end">
|
||||
<button type="button" id="dropdown-menu-601971-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-601971-menu" aria-expanded="false" class="btn-xs-icon-ghost" aria-label="Open file actions">
|
||||
<svg class="lucide lucide-ellipsis" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1" /><circle cx="19" cy="12" r="1" /><circle cx="5" cy="12" r="1" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-601971-popover" data-popover aria-hidden="true" data-align="end" class="min-w-32">
|
||||
<div role="menu" id="dropdown-menu-601971-menu" aria-labelledby="dropdown-menu-601971-trigger">
|
||||
<div id="dropdown-menu-601971-items-1" role="menuitem">Settings</div>
|
||||
<div id="dropdown-menu-601971-items-2" role="menuitem">Copy path</div>
|
||||
<div id="dropdown-menu-601971-items-3" role="menuitem">Open location</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group [--radius:1rem]">
|
||||
<input type="text" placeholder="Enter search query" />
|
||||
<div id="dropdown-menu-689868" class="dropdown-menu" data-align="end">
|
||||
<button type="button" id="dropdown-menu-689868-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-689868-menu" aria-expanded="false" class="btn-xs-ghost pr-1.5! text-xs">
|
||||
Search In...
|
||||
<svg class="size-3 lucide lucide-chevron-down" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="dropdown-menu-689868-popover" data-popover aria-hidden="true" data-align="end" class="min-w-32 [--radius:0.95rem]">
|
||||
<div role="menu" id="dropdown-menu-689868-menu" aria-labelledby="dropdown-menu-689868-trigger">
|
||||
<div id="dropdown-menu-689868-items-1" role="menuitem">Documentation</div>
|
||||
<div id="dropdown-menu-689868-items-2" role="menuitem">Blog Posts</div>
|
||||
<div id="dropdown-menu-689868-items-3" role="menuitem">Changelog</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Spinner
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Searching..." />
|
||||
<span data-align="end">
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Processing..." />
|
||||
<span data-align="start">
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Saving changes..." />
|
||||
<span data-align="end">
|
||||
Saving...
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Refreshing data..." />
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<svg class="animate-spin lucide lucide-loader" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4" /><path d="m16.2 7.8 2.9-2.9" /><path d="M18 12h4" /><path d="m16.2 16.2 2.9 2.9" /><path d="M12 18v4" /><path d="m4.9 19.1 2.9-2.9" /><path d="M2 12h4" /><path d="m4.9 4.9 2.9 2.9" /></svg>
|
||||
</span>
|
||||
<span data-align="end" class="text-muted-foreground">Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Textarea
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="input-group min-h-[200px]" data-orientation="vertical">
|
||||
<textarea placeholder="console.log('Hello, world!');"></textarea>
|
||||
<footer data-align="end" class="border-t">
|
||||
<span>Line 1, Column 1</span>
|
||||
<button type="button" class="btn-sm ml-auto">
|
||||
Run
|
||||
<svg class="lucide lucide-corner-down-left" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 4v7a4 4 0 0 1-4 4H4" /><path d="m9 10-5 5 5 5" /></svg>
|
||||
</button>
|
||||
</footer>
|
||||
<header data-align="start" class="border-b">
|
||||
<svg class="text-muted-foreground lucide lucide-file-code" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /><path d="M14 2v5a1 1 0 0 0 1 1h5" /><path d="M10 12.5 8 15l2 2.5" /><path d="m14 12.5 2 2.5-2 2.5" /></svg>
|
||||
<span>script.js</span>
|
||||
<div role="group" aria-label="File actions" class="ml-auto flex gap-1">
|
||||
<button type="button" class="btn-xs-icon-ghost">
|
||||
<svg class="lucide lucide-refresh-ccw" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" /><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" /><path d="M16 16h5v5" /></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-xs-icon-ghost">
|
||||
<svg class="lucide lucide-copy" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Custom Input
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea data-control placeholder="Autoresize textarea..." class="field-sizing-content min-h-16 resize-none"></textarea>
|
||||
<footer data-align="end">
|
||||
<button type="button" class="btn-sm ml-auto">Submit</button>
|
||||
</footer>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div dir="rtl" class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="بحث..." />
|
||||
<span role="group" data-align="start" aria-hidden="true">
|
||||
<svg class="lucide lucide-search" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /><circle cx="11" cy="11" r="8" /></svg>
|
||||
</span>
|
||||
<span role="group" data-align="end">١٢ نتيجة</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري البحث..." />
|
||||
<span role="group" data-align="end">
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري حفظ التغييرات..." />
|
||||
<span role="group" data-align="end">
|
||||
جاري الحفظ...
|
||||
<svg aria-label="Loading" role="status" class="animate-spin lucide lucide-loader-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="rtl-textarea">منطقة النص</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="rtl-textarea" placeholder="اكتب تعليقًا..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>٠/٢٨٠</span>
|
||||
<button type="button" class="btn-sm ms-auto">نشر</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>تذييل موضع أسفل منطقة النص.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user