mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 12:48:19 +00:00
Compare commits
102
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3c5d16e28 | ||
|
|
a8c066306b | ||
|
|
bbdfe1d272 | ||
|
|
a17eaa20d3 | ||
|
|
fbf3f47b75 | ||
|
|
cb72d80558 | ||
|
|
9806d6af63 | ||
|
|
971b2e171b | ||
|
|
0ae97ae627 | ||
|
|
1d33827162 | ||
|
|
815e3ffffe | ||
|
|
b971bce153 | ||
|
|
47efe50c5a | ||
|
|
349dac9cf1 | ||
|
|
4973389fbe | ||
|
|
4ccacc98a5 | ||
|
|
2e8daaef03 | ||
|
|
1509cdadef | ||
|
|
89b3942965 | ||
|
|
0829ebe0bf | ||
|
|
04ad999ce4 | ||
|
|
1e5011344f | ||
|
|
1268f5a146 | ||
|
|
c1cf01da45 | ||
|
|
7e7d509e90 | ||
|
|
317c490e0c | ||
|
|
a54d5fb4eb | ||
|
|
8445f6d150 | ||
|
|
713c163fb8 | ||
|
|
a7f686e71b | ||
|
|
128f79c2e4 | ||
|
|
ee63071f87 | ||
|
|
def943d74e | ||
|
|
f43c20948b | ||
|
|
3ec06205a0 | ||
|
|
06e7c1ec84 | ||
|
|
09f868d474 | ||
|
|
b9274702d3 | ||
|
|
83c09c1197 | ||
|
|
36bca0c6a7 | ||
|
|
167b98e573 | ||
|
|
4035870578 | ||
|
|
dea0602b03 | ||
|
|
19941561d3 | ||
|
|
f708994df9 | ||
|
|
6fcbb31e30 | ||
|
|
0dfc5e061c | ||
|
|
c771c8256b | ||
|
|
2adb4c4667 | ||
|
|
cbb1119699 | ||
|
|
88db2e84ce | ||
|
|
a7f6f717d1 | ||
|
|
91c3932fe7 | ||
|
|
f977492bf6 | ||
|
|
21ba8ecb9e | ||
|
|
c0bf373890 | ||
|
|
77680f9e2b | ||
|
|
2afa2ae02e | ||
|
|
23585e3161 | ||
|
|
7e3324edf2 | ||
|
|
cda08f3c95 | ||
|
|
0b46e05a29 | ||
|
|
7322220788 | ||
|
|
4c86ce9bfc | ||
|
|
bff89833d4 | ||
|
|
770b12f82f | ||
|
|
b8ac469755 | ||
|
|
14abbdc013 | ||
|
|
f367ebac2a | ||
|
|
b8d8b1741c | ||
|
|
f82b1d79d4 | ||
|
|
eed1938d2f | ||
|
|
e5aed697c8 | ||
|
|
0c16664e4b | ||
|
|
ab44d6d48a | ||
|
|
15473865b4 | ||
|
|
6148716f68 | ||
|
|
045d13cd49 | ||
|
|
2962e980eb | ||
|
|
530224c587 | ||
|
|
382b8f6fa2 | ||
|
|
d644cb9efd | ||
|
|
1c1a413114 | ||
|
|
1e1682df21 | ||
|
|
d280e21aac | ||
|
|
a03e225bd7 | ||
|
|
bd23745393 | ||
|
|
29600bd3ac | ||
|
|
97a53f1a67 | ||
|
|
ea4fd16b00 | ||
|
|
c5db76d677 | ||
|
|
246aa65860 | ||
|
|
150c20fd67 | ||
|
|
81acdd18cc | ||
|
|
16219ccae6 | ||
|
|
a2e9775a2c | ||
|
|
2a43d10da9 | ||
|
|
92ebe490b3 | ||
|
|
4a44656fd9 | ||
|
|
608e3b2197 | ||
|
|
a1a74c220f | ||
|
|
55e3c78131 |
@@ -1,46 +0,0 @@
|
||||
import eleventyLucideicons from "@grimlink/eleventy-plugin-lucide-icons";
|
||||
import prettier from "prettier";
|
||||
import Nunjucks from "nunjucks";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
export default async function(eleventyConfig) {
|
||||
eleventyConfig.addWatchTarget("src/nunjucks/");
|
||||
eleventyConfig.setInputDirectory("./docs/src");
|
||||
eleventyConfig.addPassthroughCopy({"docs/src/assets": "assets"});
|
||||
eleventyConfig.addPassthroughCopy({ "src/js": "assets/js" });
|
||||
eleventyConfig.addPlugin(eleventyLucideicons);
|
||||
eleventyConfig.addShortcode("fetchCode", function(filePath) {
|
||||
const absolutePath = path.resolve(process.cwd(), filePath);
|
||||
try {
|
||||
return fs.readFileSync(absolutePath, 'utf8');
|
||||
} catch (error) {
|
||||
console.error(`[Eleventy FetchCode Error] Failed to read: ${absolutePath}`, error);
|
||||
return `<!-- Error fetching code for ${filePath} -->`;
|
||||
}
|
||||
});
|
||||
|
||||
eleventyConfig.addNunjucksAsyncFilter("prettyHtml", async (code, callback) => {
|
||||
try {
|
||||
const formattedCode = await prettier.format(code, {
|
||||
parser: "html",
|
||||
printWidth: 1000,
|
||||
singleAttributePerLine: false,
|
||||
bracketSameLine: true,
|
||||
htmlWhitespaceSensitivity: "ignore"
|
||||
});
|
||||
callback(null, formattedCode);
|
||||
} catch (err) {
|
||||
console.error("Error formatting HTML with Prettier:", err);
|
||||
callback(err, code);
|
||||
}
|
||||
});
|
||||
let nunjucksEnvironment = new Nunjucks.Environment(
|
||||
new Nunjucks.FileSystemLoader([
|
||||
"src/nunjucks",
|
||||
"docs/src/_includes"
|
||||
]),
|
||||
{ autoescape: true }
|
||||
);
|
||||
eleventyConfig.setLibrary("njk", nunjucksEnvironment);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
docs/src/assets/styles.css merge=theirs
|
||||
@@ -1 +0,0 @@
|
||||
github: [hunvreus]
|
||||
+8
-3
@@ -1,11 +1,16 @@
|
||||
.*
|
||||
!.gitignore
|
||||
!.eleventy.js
|
||||
|
||||
!.gitattributes
|
||||
_site/
|
||||
|
||||
node_modules/
|
||||
packages/cli/dist/
|
||||
packages/css/dist/
|
||||
|
||||
# Logs
|
||||
npm-debug.log*
|
||||
npm-debug.log*
|
||||
dist/
|
||||
docs/generated/
|
||||
public/assets/
|
||||
|
||||
public/fragments/
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Agent Rules
|
||||
|
||||
## Communication
|
||||
- Keep answers concise, technical, and to the point.
|
||||
- Do not use filler or glazing openers.
|
||||
- Keep explanations MECE: separate facts, decisions, risks, and open questions.
|
||||
- If scope is partial, state exactly what was not included.
|
||||
- Challenge incorrect assumptions with evidence from code, docs, or upstream sources.
|
||||
|
||||
## Project Intent
|
||||
- Basecoat is a Tailwind CSS, vanilla HTML, CSS, and JavaScript implementation of shadcn/ui's visual language for non-React stacks.
|
||||
- Basecoat maps upstream shadcn/ui concepts onto simpler semantic markup; it is not a React, Radix, Base UI, `cn-*`, or `data-slot` DOM port.
|
||||
- Prefer the browser platform: native elements, semantic HTML, CSS state selectors, and small vanilla JS only when needed.
|
||||
|
||||
## Source Boundaries
|
||||
- Do not hand-edit build outputs:
|
||||
- `packages/*/dist/**`
|
||||
- `dist/**`
|
||||
- `docs/generated/**`
|
||||
- `public/assets/**`
|
||||
- Generated source entrypoints such as `src/css/basecoat-vega.css` are committed, but must be regenerated through `scripts/generate-css-entrypoints.js` or build scripts, not manually maintained.
|
||||
|
||||
## Public API and Markup
|
||||
- Keep public component APIs small: usually one root class, semantic child elements, documented ARIA/data attributes only when needed, and minimal JS for behavior.
|
||||
- Avoid required child classes when meaningful markup can identify children: `input`, `textarea`, `select`, `button`, `a`, `label`, `fieldset`, `legend`, `header`, `section`, `footer`, `svg`, `kbd`, `hr`.
|
||||
- Add new child classes, wrappers, `data-*` attributes, macro arguments, or public selectors only as intentional API.
|
||||
- Do not add roles for styling; ARIA must describe behavior or accessibility state.
|
||||
- Prefer intentional divergence over awkward imitation when upstream markup does not map cleanly to Basecoat.
|
||||
|
||||
## Accessibility
|
||||
- Start with the native element that matches the control.
|
||||
- Use `<fieldset>` and `<legend>` for grouped form controls when appropriate.
|
||||
- Use native `<dialog>` where possible.
|
||||
- Keep composite-widget keyboard behavior predictable: skip disabled items, respect orientation, and handle Escape/Enter/Space according to role.
|
||||
- Disabled custom items must be skipped by pointer and keyboard interactions.
|
||||
- Use logical CSS utilities/properties for RTL; flip only icons whose meaning is directional.
|
||||
|
||||
## CSS
|
||||
- Component CSS in `src/css/components/*.css` owns structure, layout primitives, accessibility selectors, and behavior hooks.
|
||||
- Style-pack CSS in `src/css/styles/*.css` owns visual choices: color, radius, shadow/ring, typography, spacing, variants, and state visuals.
|
||||
- Each style bundle must stand alone. Do not load Vega/default and then undo it in another style.
|
||||
- Avoid defensive resets that only compensate for another style pack.
|
||||
- Prefer Tailwind logical utilities (`ps`, `pe`, `ms`, `me`, `start`, `end`, `rounded-s`, `rounded-e`) over physical left/right utilities.
|
||||
- Prefer `border` for lines and separators.
|
||||
- Avoid `!important`; if unavoidable, keep the selector narrow and document why.
|
||||
|
||||
## JavaScript
|
||||
- JavaScript is behavior only; do not use JS to paper over CSS architecture issues.
|
||||
- Register JS components with `window.basecoat.register()` and make initialization idempotent with `data-*-initialized` flags.
|
||||
- Keep generated DOM minimal and aligned with documented HTML.
|
||||
- Prefer native browser APIs and events.
|
||||
- Keep scroll management scoped to component containers. Do not use `scrollIntoView()` when it can scroll the page unexpectedly.
|
||||
- Emit custom events only for meaningful integration points.
|
||||
|
||||
## Upstream Migration
|
||||
- Always check the upstream shadcn/ui repo before changing a migrated component: `https://github.com/shadcn-ui/ui`.
|
||||
- Use a local copy of the upstream repo for migration work.
|
||||
- Review these upstream repo paths:
|
||||
- components: `apps/v4/registry/bases/base/ui/`
|
||||
- docs: `apps/v4/content/docs/components/`
|
||||
- examples: `apps/v4/registry/bases/base/examples/`
|
||||
- styles: `apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
- Review Basecoat files in the same pass:
|
||||
- component CSS: `src/css/components/<component>.css`
|
||||
- style packs: `src/css/styles/{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
- JS: `src/js/<component>.js` when behavior changes
|
||||
- templates: `src/nunjucks/*.njk` and `src/jinja/*.html.jinja` when markup changes
|
||||
- docs: `docs/src/components/<component>.md`
|
||||
- Map upstream concepts, not implementation details. Do not port `cn-*` selectors directly.
|
||||
- Classify differences as `drift fixed`, `intentional`, or `deferred`.
|
||||
|
||||
## Docs
|
||||
- Docs are Basecoat API docs, not React docs.
|
||||
- Update docs when supported markup, variants, states, JS behavior, templates, or macros change.
|
||||
- Prefer concrete HTML examples over abstract composition trees.
|
||||
- Do not include React-only APIs such as `asChild`.
|
||||
- Do not add "Composition" sections unless they describe stable Basecoat markup.
|
||||
- Do not add API Reference sections for native/CSS-only components unless there is a real Basecoat API.
|
||||
- Structure component docs as: title and preview, `## Usage`, optional `### HTML structure` for stable markup requirements, optional `### JavaScript API` for components with Basecoat JS methods/events/options, then `## Examples`.
|
||||
- Keep `Usage` focused on the minimal working snippet and important Basecoat/upstream differences; put variants, states, RTL, and richer patterns under `Examples`.
|
||||
- Keep snippets minimal and aligned with current behavior.
|
||||
- In HTML snippets, keep text-only elements on one line, such as `<p>Text</p>`; use multiline formatting when the element contains child elements or when line length would hurt readability.
|
||||
- In HTML examples, keep icons as single-line `<svg ...>...</svg>` elements, keep simple text-only or inline-formatting elements on one line, and avoid blank separator lines between adjacent markup parts.
|
||||
- Document intentional differences from upstream near the relevant example.
|
||||
|
||||
## Validation
|
||||
- Run `npm run build` after source/package changes.
|
||||
- Run `npm run docs:build` after docs or docs asset changes.
|
||||
- Manually verify changed components in docs across light/dark mode, style packs, variants, RTL when supported, and relevant interaction states.
|
||||
|
||||
## Change Management
|
||||
- Ask before removing intentional functionality or public API.
|
||||
- Preserve backward compatibility when the repo already depends on it, especially package exports and default `basecoat.css` behavior.
|
||||
- Keep generated outputs and docs consistent with source changes.
|
||||
- Remove stale comments/docs during refactors.
|
||||
- Keep commits focused and imperative.
|
||||
- Keep release-visible changes in `CHANGELOG.md` under `## [Unreleased]`; put backlog or incomplete work in `TODO.md` or the migration tracker.
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [1.0.0-beta.6] - 2026-06-23
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated docs dependency to the published `reallysimpledocs@1.0.0-beta.5` package.
|
||||
- Refined component documentation examples across the docs site, including Select, Table, Tabs, Switch, and Theme Switcher.
|
||||
- Clarified CDN and npm installation guidance for default and named style bundles.
|
||||
- Updated Table examples to avoid inline overlay menus inside scrollable table containers and documented the overflow limitation.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed dark-mode unchecked Switch thumb colors across all style packs to match upstream shadcn/ui behavior.
|
||||
|
||||
## [1.0.0-beta.5] - 2026-06-22
|
||||
|
||||
### Added
|
||||
|
||||
- Added a dedicated Accordion component with native `<details>` markup, single-item JavaScript behavior, `data-multiple`, disabled item handling, style-pack spacing, and docs.
|
||||
- Added a dedicated Breadcrumb component with semantic `nav`/`ol` markup, style-pack visuals, collapsed and dropdown examples, and docs.
|
||||
- Added documented Card action support with `.card-action`.
|
||||
- Added Combobox clear button, popup trigger, and input-group support, with docs aligned to upstream examples.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Combobox single selection reopening immediately after selection and filtering the reopened list to the selected value.
|
||||
- Improved Combobox multiple chips and remove buttons to match style-pack button sizing more closely.
|
||||
|
||||
## [1.0.0-beta.4] - 2026-06-20
|
||||
|
||||
### Changed
|
||||
|
||||
- Renamed the compact scrollbar utility from `scrollbar-thin` to `scrollbar-sm`; `scrollbar-thin` remains available through the compatibility stylesheet.
|
||||
|
||||
## [1.0.0-beta.3] - 2026-06-20
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Changed Button, Badge, Card, Avatar, and Alert visual APIs to use a canonical root class plus data attributes instead of composed visual classes. For example, use `class="btn" data-variant="outline"`, `class="badge" data-variant="secondary"`, `class="card" data-size="sm"`, `class="avatar" data-size="lg"`, and `class="alert" data-variant="destructive"`. Legacy aliases are available only through the optional compatibility stylesheet.
|
||||
- Changed icon-only Button sizing from `data-icon="only"` plus optional `data-size` to upstream-aligned `data-size="icon|icon-xs|icon-sm|icon-lg"`.
|
||||
|
||||
### Added
|
||||
|
||||
- Added an opt-in `basecoat-css/compat` stylesheet for pre-1.0 default Basecoat class aliases.
|
||||
- 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.
|
||||
- Added dedicated Progress component styles and docs with label, controlled, and RTL examples.
|
||||
|
||||
## [1.0.0-beta.2] - 2026-06-14
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Removed the `.form` convenience selector for Basecoat 1.0. Use explicit component classes (`label`, `input`, `textarea`, `select`) or compose fields with `.field` / `.fieldset`.
|
||||
- Changed Combobox markup and behavior to an input-first structure. The visible input now filters options, the hidden input stores the submitted value, single select stores the selected value, and multiple select stores a JSON array.
|
||||
- Removed Combobox `data-multiple`; use `aria-multiselectable="true"` on the Combobox listbox, matching Select.
|
||||
- Changed Command markup to the migrated Basecoat structure: `.command-dialog` wraps `.command`, the search input lives in the command header, and items use role-based menu markup with `role="menuitem"`.
|
||||
- Removed Combobox-specific search-header behavior from Select. Use the dedicated Combobox component for editable/filterable selection.
|
||||
- Removed built-in document command events for Toast, Sidebar, and Theme. Use element methods instead: `toaster.toast(config)`, `sidebar.open()`, `sidebar.close()`, `sidebar.toggle()`, and `window.basecoat.theme.*`.
|
||||
- Reworked style loading for style packs. Non-default styles are standalone bundles and should not be loaded on top of the default/Vega bundle.
|
||||
|
||||
### Added
|
||||
|
||||
- Added standalone style packs: Vega, Nova, Maia, Lyra, Mira, Luma, Sera, and Rhea.
|
||||
- Added dedicated Empty component styles and docs.
|
||||
- Added dedicated Item component styles and docs.
|
||||
- Added dedicated Input Group component styles and docs.
|
||||
- Added dedicated Spinner docs and examples using `animate-spin` and `size-4` patterns.
|
||||
- Added style-specific package entrypoints such as `basecoat-css/nova` and styleless base entrypoints for custom themes.
|
||||
- Added `window.basecoat.refresh(element)` as a generic dispatcher for components that expose `refresh()`.
|
||||
- Added `refresh()` methods to Command, Select, Combobox, Dropdown Menu, and Tabs for dynamic child lists.
|
||||
- Added method APIs for Sidebar, Toast, and Theme.
|
||||
- Added `data-format="object"` to Select and Combobox for opt-in hidden input serialization as `{ value, label }` objects.
|
||||
- Added `selected` details to Select and Combobox change events and JavaScript properties.
|
||||
- Added `data-filter="manual"` to Command and Combobox for app-owned remote or local-search result filtering.
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed `window.basecoat.init(name)` and `window.basecoat.initAll()` to initialize uninitialized components only, instead of forcing global reinitialization.
|
||||
- Added internal destroy hooks for JavaScript components so removed component roots clean up event listeners and runtime state.
|
||||
- Reworked component CSS so shared component files own structure and behavior hooks while style-pack files own visual treatment.
|
||||
- Updated Button, Button Group, Input, Textarea, Select, Combobox, Command, Dialog, Dropdown Menu, Popover, Field, Tabs, Table, Card, Alert, Badge, Kbd, Label, Skeleton, Radio, Switch, Empty, Item, and Input Group toward current shadcn/ui styles.
|
||||
- Split Native Select documentation from custom Select documentation.
|
||||
- Updated docs to use Basecoat-specific HTML usage instead of React/shadcn composition APIs.
|
||||
- Updated docs site styling, navigation, fonts, and style switcher for the 1.0 style system.
|
||||
- Migrated the docs build to ReallySimpleDocs/Astro while keeping Basecoat's Nunjucks source examples as a pre-render step.
|
||||
- Changed docs build scripts so local Basecoat package assets are generated before ReallySimpleDocs resolves `basecoat-css`.
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed the old Form component page. Form layout should now be composed with Field, Fieldset, Input, Textarea, Select, Native Select, Checkbox, Radio, and Switch.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed destructive Alert descriptions/content using muted text instead of destructive text across all style packs.
|
||||
|
||||
### Migration Notes
|
||||
|
||||
To keep the previous `.form` wrapper behavior, define it in your own Tailwind CSS:
|
||||
|
||||
```css
|
||||
.form label { @apply label; }
|
||||
.form input { @apply input; }
|
||||
.form textarea { @apply textarea; }
|
||||
.form select { @apply select; }
|
||||
```
|
||||
|
||||
To keep the previous document-event command APIs, add bridge listeners in your app:
|
||||
|
||||
```js
|
||||
document.addEventListener('basecoat:toast', (event) => {
|
||||
document.getElementById('toaster')?.toast(event.detail?.config || {});
|
||||
});
|
||||
|
||||
document.addEventListener('basecoat:sidebar', (event) => {
|
||||
const sidebar = document.getElementById(event.detail?.id || 'sidebar');
|
||||
const action = event.detail?.action || 'toggle';
|
||||
if (['open', 'close', 'toggle'].includes(action)) sidebar?.[action]();
|
||||
});
|
||||
|
||||
document.addEventListener('basecoat:theme', (event) => {
|
||||
const mode = event.detail?.mode;
|
||||
mode ? window.basecoat.theme.set(mode) : window.basecoat.theme.toggle();
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
# Contributing to Basecoat
|
||||
|
||||
- Support development by sponsoring the author: https://github.com/sponsors/hunvreus
|
||||
- Submit pull requests (PRs) against the `development` branch, not `main`.
|
||||
- For branches, we have:
|
||||
- `main` is our production and default branch. This is what we deploy to https://basecoatui.com and publish to https://www.npmjs.com/package/basecoat-css and https://www.npmjs.com/package/basecoat-cli.
|
||||
- `development` is for staging. We will deploy it soon at https://dev.basecoatui.com.
|
||||
- New features are worked in `feature/name-of-the-feature` branches.
|
||||
- Issues are addressed in `issue/123-main-isse` branches.
|
||||
- When ready, we PR against `development`, test it and then finally merge to `main`.
|
||||
- Keep changes focused: one feature or fix per PR.
|
||||
- Test locally before submitting.
|
||||
- Follow existing code style.
|
||||
|
||||
## Cloudflare Workers deploys
|
||||
|
||||
Docs are built with 11ty and deployed as Workers Static Assets from `_site`.
|
||||
|
||||
- Local preview: `npm run workers:dev`
|
||||
- Production deploy: `npm run workers:deploy`
|
||||
- Preview version upload: `npm run workers:preview`
|
||||
|
||||
Workers Builds settings:
|
||||
|
||||
- Build command: `npm run docs:build`
|
||||
- Deploy command: `npx wrangler deploy`
|
||||
- Non-production branch deploy command: `npx wrangler versions upload`
|
||||
- Production branch: `main`
|
||||
- Enable builds for non-production branches for branch preview URLs.
|
||||
|
||||
Thanks for helping!
|
||||
@@ -1,29 +1,136 @@
|
||||
# Basecoat
|
||||
|
||||
A collection of modern UI components built with Tailwind CSS that works with any web stack. Basecoat brings the magic of [shadcn/ui](https://ui.shadcn.com) to traditional web applications, no React required.
|
||||
Basecoat is a Tailwind CSS, vanilla HTML/CSS/JavaScript implementation of the shadcn/ui design system. It provides shadcn-style components for any web stack without React, Radix, or framework runtime dependencies.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- **Lightweight**: no runtime JS, just CSS and a tiny bit of vanilla JavaScript for the more interactive components.
|
||||
- **Easy to use**: add classes like `btn` or `input` and you're done.
|
||||
- **Framework-agnostic**: works with any backend or frontend stack.
|
||||
- **Accessible**: components follow accessibility best practices.
|
||||
- **Dark mode ready**: respects your Tailwind config.
|
||||
- **Extendable**: tweak styles with Tailwind or CSS variables.
|
||||
- **Themable**: fully compatible with shadcn/ui themes.
|
||||
- **Readable**: no class soup, just clean markup.
|
||||
- **Free and open source**: MIT licensed.
|
||||
- Semantic HTML-first components.
|
||||
- Tailwind CSS v4 source files and generated CSS bundles.
|
||||
- Small vanilla JavaScript for components that need behavior.
|
||||
- Nunjucks and Jinja template macros.
|
||||
- Standalone style packs: Vega, Nova, Maia, Lyra, Mira, Luma, Sera, and Rhea.
|
||||
- Dark mode and CSS variable theming.
|
||||
- CDN, npm, and CLI usage paths.
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit [basecoatui.com](https://basecoatui.com).
|
||||
- Website: [basecoatui.com](https://basecoatui.com)
|
||||
- Installation: [basecoatui.com/installation](https://basecoatui.com/installation)
|
||||
- Customization: [basecoatui.com/customization](https://basecoatui.com/customization)
|
||||
|
||||
## Installation
|
||||
## Packages
|
||||
|
||||
Visit [basecoatui.com/installation](https://basecoatui.com/installation)
|
||||
This repository publishes two workspace packages:
|
||||
|
||||
- `basecoat-css`: CSS, JavaScript, Nunjucks macros, and Jinja macros.
|
||||
- `basecoat-cli`: CLI for adding Basecoat assets to a project.
|
||||
|
||||
Package details live in:
|
||||
|
||||
- `packages/css/README.md`
|
||||
- `packages/cli/README.md`
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install basecoat-css
|
||||
```
|
||||
|
||||
Use the default bundle:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
```
|
||||
|
||||
Use a specific style bundle:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/nova";
|
||||
```
|
||||
|
||||
Use the styleless base plus a custom style file:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base";
|
||||
@import "./style-acme.css";
|
||||
```
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```text
|
||||
.
|
||||
├── docs/
|
||||
│ ├── generated/ Generated RSD/Astro docs input
|
||||
│ └── src/ Documentation source pages, examples, and fragments
|
||||
├── packages/
|
||||
│ ├── cli/ Published CLI package
|
||||
│ └── css/ Published CSS package
|
||||
├── public/ Generated docs assets for Astro
|
||||
├── scripts/ Build and generation scripts
|
||||
└── src/
|
||||
├── components/ Astro overrides for ReallySimpleDocs
|
||||
├── css/
|
||||
│ ├── base/ Shared tokens, base layer, and semantic utilities
|
||||
│ ├── components/ Component structure and behavior hooks
|
||||
│ └── styles/ Style-pack visual rules
|
||||
├── docs.css Documentation-site CSS extension
|
||||
├── jinja/ Jinja component macros
|
||||
├── js/ Vanilla JS components and registry
|
||||
└── nunjucks/ Nunjucks component macros
|
||||
```
|
||||
|
||||
## CSS Architecture
|
||||
|
||||
Basecoat separates structure from style:
|
||||
|
||||
- `src/css/base/base.css`: shared tokens and semantic utilities.
|
||||
- `src/css/components/*.css`: component layout, structure, accessibility selectors, and behavior hooks.
|
||||
- `src/css/styles/*.css`: style-pack visuals such as color, radius, shadow, typography, spacing, variants, and state styles.
|
||||
|
||||
Generated source entrypoints are committed for transparency and package imports:
|
||||
|
||||
- `src/css/basecoat.css`: default backward-compatible Vega bundle.
|
||||
- `src/css/basecoat-base.css`: base plus components, no style pack.
|
||||
- `src/css/basecoat-components.css`: component imports only.
|
||||
- `src/css/basecoat-<style>.css`: base plus one style pack.
|
||||
- `src/css/basecoat-<style>.cdn.css`: CDN-compatible wrapper.
|
||||
|
||||
These entrypoints are generated by `scripts/generate-css-entrypoints.js` and by the build scripts.
|
||||
|
||||
## JavaScript Lifecycle
|
||||
|
||||
- `window.basecoat.init(name)` initializes uninitialized components for one registered component.
|
||||
- `window.basecoat.initAll()` initializes all uninitialized registered components.
|
||||
- `window.basecoat.refresh(element)` asks an initialized component to rescan dynamic children when it supports refresh.
|
||||
- Components use internal destroy hooks to clean up listeners when initialized roots are removed from the DOM.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies.
|
||||
npm i
|
||||
|
||||
# Generate docs input and run the ReallySimpleDocs/Astro site.
|
||||
npm run docs:dev
|
||||
|
||||
# Build package assets.
|
||||
npm run build
|
||||
|
||||
# Build the static docs site.
|
||||
npm run docs:build
|
||||
|
||||
# Run the Workers docs site locally.
|
||||
npm run workers:dev
|
||||
|
||||
# Deploy the Workers docs site.
|
||||
npm run workers:deploy
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](/LICENSE.md)
|
||||
[MIT](LICENSE.md)
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
## Now
|
||||
|
||||
- [ ] Add Drawer component
|
||||
- [ ] Make utility components (e.g., Item) into proper components (i.e., .item)
|
||||
- [ ] Split CSS into components
|
||||
|
||||
## Next
|
||||
|
||||
- [ ] Make Command load dynamic items (e.g. search results from remote endpoint)
|
||||
- [ ] Make Combobox load dynamic items (e.g. search results from remote endpoint)
|
||||
- [ ] Add Toggle component
|
||||
- [ ] Add Carousel component
|
||||
- [ ] Add Sheet component
|
||||
- [ ] Add Separator component
|
||||
- [ ] Adding Menubar component
|
||||
|
||||
## Later
|
||||
|
||||
- [ ] Add Calendar component (https://flatpickr.js.org/)
|
||||
- [ ] Finish Chart component (https://www.chartjs.org/)
|
||||
@@ -0,0 +1,53 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import reallySimpleDocs from 'reallysimpledocs/astro';
|
||||
|
||||
export default defineConfig({
|
||||
site: process.env.SITE_URL || 'https://basecoatui.com',
|
||||
vite: {
|
||||
build: {
|
||||
assetsInlineLimit: 0,
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ['basecoat-css'],
|
||||
},
|
||||
},
|
||||
integrations: [
|
||||
reallySimpleDocs({
|
||||
docsDir: './docs/src',
|
||||
routeBase: '/',
|
||||
style: 'vega',
|
||||
css: false,
|
||||
bodyAttrs: {
|
||||
'hx-boost': 'true',
|
||||
'hx-target': '#content',
|
||||
'hx-select': '#content',
|
||||
'hx-swap': 'outerHTML',
|
||||
'hx-push-url': 'true',
|
||||
},
|
||||
components: {
|
||||
Head: './docs/src/site/StyleHead.astro',
|
||||
SidebarHeader: './docs/src/site/SidebarHeader.astro',
|
||||
ContentHeader: './docs/src/site/ContentHeader.astro',
|
||||
},
|
||||
site: {
|
||||
title: 'Basecoat',
|
||||
subtitle: 'v1.0.0-beta.4',
|
||||
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'],
|
||||
author: {
|
||||
name: 'Ronan Berder',
|
||||
x: '@hunvreus',
|
||||
},
|
||||
assets: {
|
||||
favicon: 'favicon.svg',
|
||||
appleTouchIcon: 'apple-touch-icon.png',
|
||||
socialImage: 'social.png',
|
||||
},
|
||||
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>',
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -1,92 +0,0 @@
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.content {
|
||||
> 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 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;
|
||||
}
|
||||
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,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,8 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.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,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,105 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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 (_) {}
|
||||
|
||||
const apply = dark => {
|
||||
document.documentElement.classList.toggle('dark', dark);
|
||||
try { localStorage.setItem('themeMode', dark ? 'dark' : 'light'); } catch (_) {}
|
||||
};
|
||||
|
||||
document.addEventListener('basecoat:theme', (event) => {
|
||||
const mode = event.detail?.mode;
|
||||
apply(mode === 'dark' ? true
|
||||
: mode === 'light' ? false
|
||||
: !document.documentElement.classList.contains('dark'));
|
||||
});
|
||||
})();
|
||||
</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 %}
|
||||
{# Favicon #}
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png">
|
||||
{# Open Graph / Facebook #}
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
|
||||
<meta property="og:title" content="{{ meta_title }}">
|
||||
<meta property="og:description" content="{{ meta_description }}">
|
||||
<meta property="og:image" content="{{ site.url }}/assets/social-screenshot.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="{{ site.url }}{{ page.url }}">
|
||||
<meta name="twitter:title" content="{{ title if title else site.title }}">
|
||||
<meta name="twitter:description" content="{{ meta_description }}">
|
||||
<meta name="twitter:image" content="{{ site.url }}/assets/social-screenshot.png">
|
||||
<meta name="twitter:creator" content="{{ site.author.x }}">
|
||||
{# CSS #}
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
{# JS #}
|
||||
<script src="/assets/js/dropdown-menu.js" defer></script>
|
||||
<script src="/assets/js/popover.js" defer></script>
|
||||
<script src="/assets/js/select.js" defer></script>
|
||||
<script src="/assets/js/sidebar.js" defer></script>
|
||||
<script src="/assets/js/tabs.js" defer></script>
|
||||
<script src="/assets/js/toast.js" defer></script>
|
||||
{# HTMX #}
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></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>
|
||||
{% if head %}{{ head | safe }}{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
{{ 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,21 +0,0 @@
|
||||
---
|
||||
layout: layouts/layout.njk
|
||||
---
|
||||
|
||||
<main class="mx-auto relative flex w-full max-w-screen-lg gap-10">
|
||||
<div class="mx-auto w-full flex-1 max-w-screen-md">
|
||||
<header class="space-y-2">
|
||||
<h1 class="text-2xl font-semibold tracking-tight sm:text-3xl xl:text-4xl">{{ title }}</h1>
|
||||
<p class="text-muted-foreground text-[1.05rem] sm:text-base">{{ description }}</p>
|
||||
</header>
|
||||
<article class="pb-12 mt-8 content">
|
||||
{{ content | safe }}
|
||||
</article>
|
||||
</div>
|
||||
{% if toc %}
|
||||
<div class="hidden text-sm xl:block w-full max-w-[300px]">
|
||||
{% from "macros/toc.njk" import toc_nav %}
|
||||
{{ toc_nav(toc) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
@@ -1,23 +0,0 @@
|
||||
{% macro code_block(code, language="html", class="my-4") %}
|
||||
<div class="relative {{ class }}">
|
||||
<pre class="grid text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-muted !p-3.5">{{- 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 "clipboard", {"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,64 +0,0 @@
|
||||
<div class="flex h-14 w-full items-center gap-2 px-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar'))"
|
||||
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>
|
||||
<select
|
||||
class="select h-8 leading-none"
|
||||
id="theme-select"
|
||||
>
|
||||
<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>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle dark mode"
|
||||
data-tooltip="Toggle dark mode"
|
||||
data-side="bottom"
|
||||
onclick="document.dispatchEvent(new CustomEvent('basecoat:theme'))"
|
||||
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="form grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-card-form-email">Email</label>
|
||||
<input type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<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 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="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-username">Username</label>
|
||||
<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="form grid w-full max-w-sm gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-text">Username</label>
|
||||
<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 for="demo-form-select">Email</label>
|
||||
<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 for="demo-form-text">Bio</label>
|
||||
<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 for="demo-form-radio">Notify me about...</label>
|
||||
<fieldset id="demo-form-radio" class="grid gap-3">
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="1">All new messages</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="2">Direct messages and mentions</label>
|
||||
<label class="font-normal"><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 type="checkbox" id="demo-form-checkbox">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="demo-form-checkbox" class="leading-snug">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="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="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="1" checked>
|
||||
Recents
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="2" checked>
|
||||
Home
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="3">
|
||||
Applications
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="4">
|
||||
Desktop
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="5">
|
||||
Download
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="6">
|
||||
Documents
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-date">Date of birth</label>
|
||||
<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="leading-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<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="leading-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<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="form grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-width">Width</label>
|
||||
<input type="text" id="demo-popover-width" value="100%" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-width">Max. width</label>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="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="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<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="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<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,25 +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">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/success" hx-target="#toaster" hx-swap="beforeend">
|
||||
Success
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/error" hx-target="#toaster" hx-swap="beforeend">
|
||||
Error
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/info" hx-target="#toaster" hx-swap="beforeend">
|
||||
Info
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" 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,122 +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">
|
||||
<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">Ronan</a> and I made this (and <a class="underline underline-offset-4" href="https://pagescms.org" target="_target">other things</a>). 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">Sponsor me on GitHub</a>
|
||||
<a href="https://x.com/hunvreus" class="btn-sm-outline" target="_blank">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">
|
||||
<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>
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Basecoat</span>
|
||||
<span class="truncate text-xs">v{{ pkg.version }}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{# <div class="flex flex-col gap-2 px-2 text-sm">
|
||||
<form class="form relative">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
|
||||
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
|
||||
</form>
|
||||
</div> #}
|
||||
{% endset %}
|
||||
|
||||
{% set link_attrs = {
|
||||
"hx-boost": "true",
|
||||
"hx-select": "#content",
|
||||
"hx-target": "#content",
|
||||
"hx-swap": "outerHTML"
|
||||
} %}
|
||||
|
||||
{% set icon_home %}{% lucide "house" %}{% endset %}
|
||||
{% set icon_info %}{% lucide "info" %}{% endset %}
|
||||
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
|
||||
{% set icon_layout_dashboard %}{% lucide "layout-dashboard" %}{% endset %}
|
||||
{% set icon_github %}<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><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>{% endset %}
|
||||
{% set icon_discord %}<svg fill="currentColor" 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>{% endset %}
|
||||
{% set icon_github %}<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><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>{% endset %}
|
||||
{% set icon_discord %}<svg fill="currentColor" 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>{% endset %}
|
||||
|
||||
{% set menu = [
|
||||
{ type: "group", label: "Getting started", items: [
|
||||
{ label: "Introduction", url: "/introduction", icon: icon_info, attrs: link_attrs },
|
||||
{ label: "Installation", url: "/installation", icon: icon_square_terminal, attrs: link_attrs },
|
||||
{ label: "Kitchen sink", url: "/kitchen-sink", icon: icon_layout_dashboard, attrs: link_attrs },
|
||||
{ label: "GitHub", url: "https://github.com/hunvreus/basecoat", icon: icon_github, attrs: { "target": "_blank" } },
|
||||
{ label: "Discord", url: "https://basecoatui.com/chat", icon: icon_discord, attrs: { "target": "_blank" } }
|
||||
]},
|
||||
{ type: 'group', label: 'Components', items: [
|
||||
{ label: "Accordion", url: "/components/accordion", attrs: link_attrs },
|
||||
{ label: "Alert", url: "/components/alert", attrs: link_attrs },
|
||||
{ label: "Alert Dialog", url: "/components/alert-dialog", attrs: link_attrs },
|
||||
{ label: "Avatar", url: "/components/avatar", attrs: link_attrs },
|
||||
{ label: "Badge", url: "/components/badge", attrs: link_attrs },
|
||||
{ label: "Breadcrumb", url: "/components/breadcrumb", attrs: link_attrs },
|
||||
{ label: "Button", url: "/components/button", attrs: link_attrs },
|
||||
{ label: "Card", url: "/components/card", attrs: link_attrs },
|
||||
{ label: "Checkbox", url: "/components/checkbox", attrs: link_attrs },
|
||||
{ label: "Combobox", url: "/components/combobox", attrs: link_attrs },
|
||||
{ label: "Dialog", url: "/components/dialog", attrs: link_attrs },
|
||||
{ label: "Dropdown Menu", url: "/components/dropdown-menu", attrs: link_attrs },
|
||||
{ label: "Form", url: "/components/form", attrs: link_attrs },
|
||||
{ label: "Input", url: "/components/input", attrs: link_attrs },
|
||||
{ label: "Label", url: "/components/label", attrs: link_attrs },
|
||||
{ label: "Pagination", url: "/components/pagination", attrs: link_attrs },
|
||||
{ label: "Popover", url: "/components/popover", attrs: link_attrs },
|
||||
{ label: "Radio Group", url: "/components/radio-group", attrs: link_attrs },
|
||||
{ label: "Select", url: "/components/select", attrs: link_attrs },
|
||||
{ label: "Skeleton", url: "/components/skeleton", attrs: link_attrs },
|
||||
{ label: "Sidebar", url: "/components/sidebar", attrs: link_attrs },
|
||||
{ label: "Slider", url: "/components/slider", attrs: link_attrs },
|
||||
{ label: "Switch", url: "/components/switch", attrs: link_attrs },
|
||||
{ label: "Table", url: "/components/table", attrs: link_attrs },
|
||||
{ label: "Tabs", url: "/components/tabs", attrs: link_attrs },
|
||||
{ label: "Textarea", url: "/components/textarea", attrs: link_attrs },
|
||||
{ label: "Theme Switcher", url: "/components/theme-switcher", attrs: link_attrs },
|
||||
{ label: "Toast", url: "/components/toast", attrs: link_attrs },
|
||||
{ label: "Tooltip", url: "/components/tooltip", attrs: link_attrs }
|
||||
]}
|
||||
] %}
|
||||
|
||||
|
||||
{{ sidebar(
|
||||
id="sidebar",
|
||||
label="Sidebar navigation",
|
||||
header=header,
|
||||
footer=footer,
|
||||
content_attrs={ "class": "scrollbar" },
|
||||
menu=menu
|
||||
) }}
|
||||
@@ -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 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-luma.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/luma";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-lyra.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/lyra";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-maia.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/maia";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-mira.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/mira";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-nova.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/nova";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-rhea.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/rhea";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-sera.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/sera";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-vega.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/vega";
|
||||
@@ -0,0 +1,221 @@
|
||||
# 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>
|
||||
```bash title="npm"
|
||||
npx basecoat-cli add dialog
|
||||
```
|
||||
|
||||
```bash title="pnpm"
|
||||
pnpm dlx basecoat-cli add dialog
|
||||
```
|
||||
|
||||
```bash title="bun"
|
||||
bunx basecoat-cli add dialog
|
||||
```
|
||||
|
||||
```bash title="yarn"
|
||||
yarn dlx basecoat-cli add dialog
|
||||
```
|
||||
</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>
|
||||
```jinja title="Nunjucks"
|
||||
{% from "select.njk" import select %}
|
||||
{{ select(
|
||||
name="fruit",
|
||||
items=[
|
||||
{ value: "apple", label: "Apple" },
|
||||
{ value: "banana", label: "Banana" },
|
||||
{ value: "blueberry", label: "Blueberry" }
|
||||
]
|
||||
) }}
|
||||
```
|
||||
|
||||
```jinja title="Jinja"
|
||||
{% from "select.html.jinja" import select %}
|
||||
{{ select(
|
||||
name="fruit",
|
||||
items=[
|
||||
{ "value": "apple", "label": "Apple" },
|
||||
{ "value": "banana", "label": "Banana" },
|
||||
{ "value": "blueberry", "label": "Blueberry" }
|
||||
]
|
||||
) }}
|
||||
```
|
||||
</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. |
|
||||
@@ -0,0 +1,309 @@
|
||||
# Accordion
|
||||
|
||||
A vertically stacked set of interactive headings that each reveal a section of content.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<section class="accordion">
|
||||
<details open>
|
||||
<summary>
|
||||
What are your shipping 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>
|
||||
</summary>
|
||||
<section>We offer standard (5-7 days), express (2-3 days), and overnight shipping. Free shipping on international orders.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
What is your return policy?
|
||||
<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>
|
||||
</summary>
|
||||
<section>You can return items within 30 days of delivery. Items must be unused and in their original packaging.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
How can I contact customer support?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Email support@example.com or use live chat during business hours.</section>
|
||||
</details>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Accordion component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/accordion.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Include JavaScript">
|
||||
|
||||
Copy or serve the full Basecoat JavaScript bundle.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/all.min.js" defer></script>
|
||||
```
|
||||
|
||||
Or copy or serve the Basecoat runtime and Accordion script.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/basecoat.min.js" defer></script>
|
||||
<script src="/assets/js/accordion.min.js" defer></script>
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your accordion HTML">
|
||||
|
||||
```html
|
||||
<section class="accordion">
|
||||
<details open>
|
||||
<summary>
|
||||
Is it accessible?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Yes. It uses native disclosure semantics.</section>
|
||||
</details>
|
||||
</section>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Basecoat uses native `<details>` and `<summary>` elements for disclosure semantics. The accordion JavaScript only enforces single-item behavior by default and prevents disabled items from toggling. Add `data-multiple` to the root when more than one item can be open at a time.
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><section class="accordion"></code></dt>
|
||||
<dd>
|
||||
Root accordion container. Add <code>data-multiple</code> to allow multiple open items.
|
||||
<dl>
|
||||
<dt><code><details open></code></dt>
|
||||
<dd>
|
||||
Accordion item. Use the native <code>open</code> attribute for the default expanded item. Add <code>aria-disabled="true"</code> when an item should not toggle.
|
||||
<dl>
|
||||
<dt><code><summary></code></dt>
|
||||
<dd>Accordion trigger. Keep the trigger text visible and add an optional trailing icon.</dd>
|
||||
<dt><code><section></code></dt>
|
||||
<dd>Accordion content. Use any semantic element that fits your content.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
### JavaScript API
|
||||
|
||||
Accordion does not expose custom methods or events. Programmatically open or close an item with the native `details.open` boolean.
|
||||
|
||||
```js
|
||||
document.querySelector("#billing").open = true
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
A basic accordion that shows one item at a time. The first item is open by default.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<section class="accordion">
|
||||
<details open>
|
||||
<summary>
|
||||
How do I reset my password?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Click on 'Forgot Password' on the login page, enter your email address, and we'll send you a link to reset your password. The link will expire in 24 hours.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
Can I change my subscription plan?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Yes. You can upgrade or downgrade your plan from the billing settings page.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
What payment methods do you accept?
|
||||
<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>
|
||||
</summary>
|
||||
<section>We accept all major credit cards, PayPal, and bank transfers for annual plans.</section>
|
||||
</details>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Multiple
|
||||
|
||||
Use `data-multiple` to allow multiple items to be open at the same time.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<section class="accordion" data-multiple>
|
||||
<details open>
|
||||
<summary>
|
||||
Notification Settings
|
||||
<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>
|
||||
</summary>
|
||||
<section>Manage how you receive notifications. You can enable email alerts for updates or push notifications for mobile devices.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
Privacy & Security
|
||||
<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>
|
||||
</summary>
|
||||
<section>Control account security, session history, and privacy preferences.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
Billing & Subscription
|
||||
<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>
|
||||
</summary>
|
||||
<section>Review invoices, change plans, and update payment methods.</section>
|
||||
</details>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
Use `aria-disabled="true"` on an item to disable it.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<section class="accordion">
|
||||
<details open>
|
||||
<summary>
|
||||
Can I access my account history?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Yes. Your account history is available from the activity page.</section>
|
||||
</details>
|
||||
<details aria-disabled="true">
|
||||
<summary>
|
||||
Premium feature information
|
||||
<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>
|
||||
</summary>
|
||||
<section>This item is disabled.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
How do I update my email address?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Go to account settings, update your email, then confirm the change from your inbox.</section>
|
||||
</details>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Borders
|
||||
|
||||
Add horizontal padding to the rows when using a bordered root.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<section class="accordion rounded-md border [&>details>summary]:px-4 [&>details>:not(summary)]:px-4">
|
||||
<details id="billing" open>
|
||||
<summary>
|
||||
How does billing work?
|
||||
<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>
|
||||
</summary>
|
||||
<section>We offer monthly and annual subscription plans. Billing is charged at the beginning of each cycle, and you can cancel anytime. All plans include automatic backups, 24/7 support, and unlimited team members.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
Is my data secure?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Data is encrypted in transit and at rest.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
What integrations do you support?
|
||||
<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>
|
||||
</summary>
|
||||
<section>We support Slack, GitHub, Google Workspace, and webhooks.</section>
|
||||
</details>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Card
|
||||
|
||||
Wrap the accordion in a card for grouped support or billing content.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Subscription & Billing</h2>
|
||||
<p>Common questions about your account, plans, payments and cancellations.</p>
|
||||
</header>
|
||||
<section>
|
||||
<section class="accordion">
|
||||
<details open>
|
||||
<summary>
|
||||
What subscription plans do you offer?
|
||||
<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>
|
||||
</summary>
|
||||
<section>We offer three subscription tiers: Starter ($9/month), Professional ($29/month), and Enterprise ($99/month). Each plan includes increasing storage limits, API access, priority support, and team collaboration features.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
How does billing work?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Billing is charged at the beginning of each cycle.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
How do I cancel my subscription?
|
||||
<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>
|
||||
</summary>
|
||||
<section>Cancel from billing settings. Your plan remains active until the end of the current cycle.</section>
|
||||
</details>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Accordion spacing uses logical properties. Set `dir="rtl"` on the accordion or a parent element.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<section class="accordion" dir="rtl">
|
||||
<details open>
|
||||
<summary>
|
||||
كيف يمكنني إعادة تعيين كلمة المرور؟
|
||||
<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>
|
||||
</summary>
|
||||
<section>انقر على 'نسيت كلمة المرور' في صفحة تسجيل الدخول، أدخل عنوان بريدك الإلكتروني، وسنرسل لك رابطا لإعادة تعيين كلمة المرور. سينتهي صلاحية الرابط خلال 24 ساعة.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
هل يمكنني تغيير خطة الاشتراك الخاصة بي؟
|
||||
<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>
|
||||
</summary>
|
||||
<section>نعم، يمكنك تغيير الخطة من إعدادات الفوترة.</section>
|
||||
</details>
|
||||
<details>
|
||||
<summary>
|
||||
ما هي طرق الدفع التي تقبلونها؟
|
||||
<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>
|
||||
</summary>
|
||||
<section>نقبل بطاقات الائتمان الرئيسية والتحويلات البنكية.</section>
|
||||
</details>
|
||||
</section>
|
||||
</Preview>
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
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 animates <code><details></code> elements by default. The example above uses Alpine.js to handle the open/close state along with some Tailwind CSS classes.</p>
|
||||
@@ -0,0 +1,187 @@
|
||||
# Alert Dialog
|
||||
|
||||
A modal dialog that interrupts the user with important content and expects a response.
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('demo-alert-dialog').showModal()" class="btn" data-variant="outline">Show Dialog</button>
|
||||
<dialog id="demo-alert-dialog" class="alert-dialog" aria-labelledby="demo-alert-dialog-title" aria-describedby="demo-alert-dialog-description">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="demo-alert-dialog-title">Are you absolutely sure?</h2>
|
||||
<p id="demo-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" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Alert Dialog component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/dialog.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Alert Dialog uses the Dialog component CSS.
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your alert dialog HTML">
|
||||
|
||||
Alert Dialog uses a native `<dialog class="alert-dialog">` element. Omit the close button and backdrop-click handler when the user must choose an explicit action.
|
||||
|
||||
```html
|
||||
<button type="button" onclick="document.getElementById('demo-alert-dialog').showModal()" class="btn" data-variant="outline">Show Dialog</button>
|
||||
<dialog id="demo-alert-dialog" class="alert-dialog" aria-labelledby="demo-alert-dialog-title" aria-describedby="demo-alert-dialog-description">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="demo-alert-dialog-title">Are you absolutely sure?</h2>
|
||||
<p id="demo-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" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><button type="button" onclick="dialog.showModal()"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Trigger button. Basecoat uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code><dialog class="alert-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><div></code></dt>
|
||||
<dd>
|
||||
Dialog content surface.
|
||||
<dl>
|
||||
<dt><code><header></code></dt>
|
||||
<dd>
|
||||
Alert dialog header.
|
||||
<dl>
|
||||
<dt><code><figure></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Media area for an icon or image.</dd>
|
||||
<dt><code><h2 id="{ TITLE_ID }"></code></dt>
|
||||
<dd>Alert dialog title. Reference it from <code>aria-labelledby</code>.</dd>
|
||||
<dt><code><p id="{ DESCRIPTION_ID }"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Alert dialog description. Reference it from <code>aria-describedby</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code><footer></code></dt>
|
||||
<dd>Action area. Include a cancel action and the primary action.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
A basic alert dialog with a title, description, and cancel and continue buttons.
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('alert-dialog-basic').showModal()" class="btn" data-variant="outline">Show Dialog</button>
|
||||
<dialog id="alert-dialog-basic" class="alert-dialog" aria-labelledby="alert-dialog-basic-title" aria-describedby="alert-dialog-basic-description">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="alert-dialog-basic-title">Are you absolutely sure?</h2>
|
||||
<p id="alert-dialog-basic-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" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Media
|
||||
|
||||
Use a `<figure>` in the header to add a media element such as an icon or image to the alert dialog.
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('alert-dialog-media').showModal()" class="btn" data-variant="outline">Share Project</button>
|
||||
<dialog id="alert-dialog-media" class="alert-dialog" aria-labelledby="alert-dialog-media-title" aria-describedby="alert-dialog-media-description">
|
||||
<div>
|
||||
<header>
|
||||
<figure><svg class="lucide lucide-circle-fading-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 2a10 10 0 0 1 7.38 16.75" /><path d="M12 8v8" /><path d="M16 12H8" /><path d="M2.5 8.88a10 10 0 0 0 0 6.24" /><path d="M4.64 4.64a10 10 0 0 0 0 14.72" /></svg></figure>
|
||||
<h2 id="alert-dialog-media-title">Share this project?</h2>
|
||||
<p id="alert-dialog-media-description">Anyone with the link will be able to view and edit this project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Share</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
Use a destructive action button when the alert dialog confirms a destructive operation.
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('alert-dialog-destructive').showModal()" class="btn" data-variant="destructive">Delete Chat</button>
|
||||
<dialog id="alert-dialog-destructive" class="alert-dialog" aria-labelledby="alert-dialog-destructive-title" aria-describedby="alert-dialog-destructive-description">
|
||||
<div>
|
||||
<header>
|
||||
<figure><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></figure>
|
||||
<h2 id="alert-dialog-destructive-title">Delete this chat?</h2>
|
||||
<p id="alert-dialog-destructive-description">This action cannot be undone. This will permanently delete the chat and remove it from your history.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" data-variant="destructive" onclick="this.closest('dialog').close()">Delete</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Alert Dialog supports document direction. Set `dir="rtl"` on the dialog or a parent element.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<button type="button" onclick="document.getElementById('alert-dialog-rtl').showModal()" class="btn" data-variant="outline">إظهار الحوار</button>
|
||||
<dialog id="alert-dialog-rtl" class="alert-dialog" aria-labelledby="alert-dialog-rtl-title" aria-describedby="alert-dialog-rtl-description">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="alert-dialog-rtl-title">هل أنت متأكد تماماً؟</h2>
|
||||
<p id="alert-dialog-rtl-description">لا يمكن التراجع عن هذا الإجراء. سيتم حذف حسابك نهائياً وإزالة بياناتك من خوادمنا.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">متابعة</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,119 +0,0 @@
|
||||
---
|
||||
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, class="") }}
|
||||
|
||||
<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="/installation/#install-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,140 @@
|
||||
# Alert
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="grid 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>Payment successful</h2>
|
||||
<section>Your payment of $29.99 has been processed. A receipt has been sent to your email address.</section>
|
||||
</div>
|
||||
<div class="alert"><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>New feature available</h2>
|
||||
<section>We've added dark mode support. You can enable it in your account settings.</section>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Alert component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/alert.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your alert HTML">
|
||||
|
||||
Use `alert` for default styling. Add `data-variant="destructive"` for error states. Add a direct child `<footer>` for an inline-end action region.
|
||||
|
||||
```html
|
||||
<div class="alert"><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>Heads up!</h2>
|
||||
<section>You can add components and dependencies to your app using the cli.</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" data-variant="outline">Enable</button>
|
||||
</footer>
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><div class="alert"></code></dt>
|
||||
<dd>
|
||||
Main container. Use <code>data-variant="destructive"</code> for error states.
|
||||
<dl>
|
||||
<dt><code><svg></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>The icon.</dd>
|
||||
<dt><code><h2></code></dt>
|
||||
<dd>The title.</dd>
|
||||
<dt><code><section></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>The description.</dd>
|
||||
<dt><code><footer></code> <span class="badge" data-variant="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
|
||||
|
||||
### Basic
|
||||
|
||||
A basic alert with an icon, title and description.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<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>Account updated successfully</h2>
|
||||
<section>Your profile information has been saved. Changes will be reflected immediately.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
Use `data-variant="destructive"` to create a destructive alert.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="alert" data-variant="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>Payment failed</h2>
|
||||
<section>Your payment could not be processed. Please check your payment method and try again.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Action
|
||||
|
||||
Use `<footer>` to add a button or other action element to the alert.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="alert">
|
||||
<h2>Dark mode is now available</h2>
|
||||
<section>Enable it under your profile settings to get started.</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" data-size="xs">Enable</button>
|
||||
</footer>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Custom colors
|
||||
|
||||
You can customize the alert colors by adding custom classes such as `bg-amber-50 dark:bg-amber-950` to the alert.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="alert border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-50"><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>Your subscription will expire in 3 days.</h2>
|
||||
<section>Renew now to avoid service interruption or upgrade to a paid plan to continue using the service.</section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Add `dir="rtl"` to the alert or an ancestor.
|
||||
|
||||
<Preview class="w-full max-w-md">
|
||||
<div class="grid items-start gap-4" dir="rtl">
|
||||
<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>تم الدفع بنجاح</h2>
|
||||
<section>تمت معالجة دفعتك البالغة 29.99 دولارًا. تم إرسال إيصال إلى عنوان بريدك الإلكتروني.</section>
|
||||
</div>
|
||||
<div class="alert"><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>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<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_preview("alert", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use one of the two classes: <code>alert</code> or <code>alert-dialog</code>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<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>
|
||||
</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) }}
|
||||
@@ -0,0 +1,222 @@
|
||||
# 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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Avatar component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/avatar.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your avatar HTML">
|
||||
|
||||
Add `class="avatar"` to a small inline container. Add `data-size="sm"` or `data-size="lg"` when needed. Put the image first and a fallback `<span>` after it. Add `.avatar-badge` for a status badge or badge icon.
|
||||
|
||||
```html
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><div class="avatar-group"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Optional parent that groups avatars with overlap.
|
||||
<dl>
|
||||
<dt><code><span class="avatar"></code></dt>
|
||||
<dd>
|
||||
Avatar root. Use it standalone or as a direct child of <code>.avatar-group</code>. Add <code>data-size="sm"</code> or <code>data-size="lg"</code> for alternate sizes.
|
||||
<dl>
|
||||
<dt><code><img></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>The avatar image. Provide meaningful <code>alt</code> text.</dd>
|
||||
<dt><code><span></code></dt>
|
||||
<dd>Fallback initials or icon.</dd>
|
||||
<dt><code><span class="avatar-badge"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Status badge positioned at the block-end inline-end corner. It can be empty or contain an icon.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code><span data-count></code> <span class="badge" data-variant="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 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 class="avatar-badge bg-green-600 dark:bg-green-800"></span>
|
||||
</span>
|
||||
</Preview>
|
||||
|
||||
### Badge with icon
|
||||
|
||||
<Preview>
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/pranathip.png" alt="@pranathip" />
|
||||
<span>PP</span>
|
||||
<span class="avatar-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">
|
||||
<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">
|
||||
<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">
|
||||
<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="grid gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="avatar" data-size="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" data-size="lg">
|
||||
<img src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<span>CN</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="avatar" data-size="sm"><span>CN</span></span>
|
||||
<span class="avatar"><span>CN</span></span>
|
||||
<span class="avatar" data-size="lg"><span>CN</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Dropdown
|
||||
|
||||
<Preview>
|
||||
<div class="dropdown-menu">
|
||||
<button type="button" id="avatar-dropdown-trigger" aria-label="Open user menu" aria-haspopup="menu" aria-controls="avatar-dropdown-menu" aria-expanded="false" class="btn rounded-full" data-variant="ghost" data-size="icon">
|
||||
<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-12" dir="rtl">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
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 %}<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">{% endset %}
|
||||
{{ code_preview("avatar", code, "max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Avatars are fairly simple to implement with Tailwind. The example above shows how to implement one with Tailwind and an <code><img></code> element. More examples can be found in the <a href="/kitchen-sink#avatar">kitchen sink</a>.</p>
|
||||
@@ -0,0 +1,140 @@
|
||||
# Badge
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="badge">Badge</span>
|
||||
<span class="badge" data-variant="secondary">Secondary</span>
|
||||
<span class="badge" data-variant="destructive">Destructive</span>
|
||||
<span class="badge" data-variant="outline">Outline</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Badge component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/badge.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your badge HTML">
|
||||
|
||||
Add `class="badge"` to an inline element. Add `data-variant` when the badge is not the default primary variant.
|
||||
|
||||
Inline SVG icons are spaced automatically by the badge gap.
|
||||
|
||||
```html
|
||||
<span class="badge">Default</span>
|
||||
<span class="badge" data-variant="secondary">Secondary</span>
|
||||
<span class="badge" data-variant="destructive">Destructive</span>
|
||||
<span class="badge" data-variant="outline">Outline</span>
|
||||
<span class="badge" data-variant="ghost">Ghost</span>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Badges support right-to-left layouts through native text flow. Flip directional icons explicitly.
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
Use `data-variant` to change the badge variant.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="badge">Default</span>
|
||||
<span class="badge" data-variant="secondary">Secondary</span>
|
||||
<span class="badge" data-variant="destructive">Destructive</span>
|
||||
<span class="badge" data-variant="outline">Outline</span>
|
||||
<span class="badge" data-variant="ghost">Ghost</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### With icon
|
||||
|
||||
Use `data-icon="inline-start"` or `data-icon="inline-end"` on the icon for icon-aware spacing.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="badge"><svg data-icon="inline-start" 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" data-variant="secondary">
|
||||
Bookmark
|
||||
<svg data-icon="inline-end" class="lucide lucide-bookmark" 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 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### With spinner
|
||||
|
||||
Use an inline loading icon with `data-icon="inline-start"` or `data-icon="inline-end"`.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="badge" data-variant="destructive"><svg aria-label="Loading" role="status" data-icon="inline-start" 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>
|
||||
Deleting
|
||||
</span>
|
||||
<span class="badge" data-variant="secondary">
|
||||
Generating
|
||||
<svg aria-label="Loading" role="status" data-icon="inline-end" 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>
|
||||
</Preview>
|
||||
|
||||
### Link
|
||||
|
||||
<Preview>
|
||||
<a href="#" class="badge">
|
||||
Open Link
|
||||
<svg data-icon="inline-end" 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>
|
||||
</Preview>
|
||||
|
||||
### Custom colors
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="badge bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300">Blue</span>
|
||||
<span class="badge bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">Green</span>
|
||||
<span class="badge bg-sky-50 text-sky-700 dark:bg-sky-950 dark:text-sky-300">Sky</span>
|
||||
<span class="badge bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300">Purple</span>
|
||||
<span class="badge bg-red-50 text-red-700 dark:bg-red-950 dark:text-red-300">Red</span>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="flex flex-wrap gap-2">
|
||||
<span class="badge">شارة</span>
|
||||
<span class="badge" data-variant="secondary">ثانوي</span>
|
||||
<span class="badge" data-variant="destructive">مدمر</span>
|
||||
<span class="badge" data-variant="outline">مخطط</span>
|
||||
<span class="badge"><svg data-icon="inline-start" 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>
|
||||
متحقق
|
||||
</span>
|
||||
<span class="badge" data-variant="secondary">
|
||||
إشارة مرجعية
|
||||
<svg data-icon="inline-end" class="lucide lucide-bookmark" 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 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" /></svg>
|
||||
</span>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<span class="badge">Badge</span>{% 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> or <code>badge-outline</code>.
|
||||
</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" %}
|
||||
</a>{% endset %}
|
||||
{{ code_preview("badge-link", code) }}
|
||||
@@ -0,0 +1,154 @@
|
||||
# Breadcrumb
|
||||
|
||||
<Preview>
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||||
<ol>
|
||||
<li><a href="#">Home</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><a href="#">Components</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><span aria-current="page">Breadcrumb</span></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Breadcrumb component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/breadcrumb.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your breadcrumb HTML">
|
||||
|
||||
Use a navigation landmark with an ordered list. Mark the current page with `aria-current="page"`.
|
||||
|
||||
```html
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||||
<ol>
|
||||
<li><a href="#">Home</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip class="lucide lucide-chevron-right" ...></svg></li>
|
||||
<li><span aria-current="page">Breadcrumb</span></li>
|
||||
</ol>
|
||||
</nav>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## HTML structure
|
||||
|
||||
- `nav.breadcrumb` is the root landmark and should include `aria-label="Breadcrumb"`.
|
||||
- `ol` contains the breadcrumb items in hierarchy order.
|
||||
- `li > a` represents navigable ancestors.
|
||||
- `li[aria-hidden="true"]` represents a visual separator.
|
||||
- `[aria-current="page"]` represents the current page.
|
||||
- `li > span[aria-hidden="true"]` represents a collapsed ellipsis.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
<Preview>
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||||
<ol>
|
||||
<li><a href="#">Home</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><a href="#">Components</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><span aria-current="page">Breadcrumb</span></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</Preview>
|
||||
|
||||
### Custom separator
|
||||
|
||||
<Preview>
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||||
<ol>
|
||||
<li><a href="#">Home</a></li>
|
||||
<li aria-hidden="true"><svg class="lucide lucide-dot" 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.1" cy="12.1" r="1" /></svg></li>
|
||||
<li><a href="#">Components</a></li>
|
||||
<li aria-hidden="true"><svg class="lucide lucide-dot" 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.1" cy="12.1" r="1" /></svg></li>
|
||||
<li><span aria-current="page">Breadcrumb</span></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</Preview>
|
||||
|
||||
### Dropdown
|
||||
|
||||
<Preview>
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||||
<ol>
|
||||
<li><a href="#">Home</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li>
|
||||
<div id="breadcrumb-dropdown" class="dropdown-menu">
|
||||
<button type="button" id="breadcrumb-dropdown-trigger" class="btn" data-variant="ghost" data-size="icon-sm" aria-haspopup="menu" aria-controls="breadcrumb-dropdown-menu" aria-expanded="false"><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>
|
||||
<span class="sr-only">Toggle menu</span>
|
||||
</button>
|
||||
<div id="breadcrumb-dropdown-popover" data-popover aria-hidden="true">
|
||||
<div role="menu" id="breadcrumb-dropdown-menu" aria-labelledby="breadcrumb-dropdown-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 aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><a href="#">Components</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><span aria-current="page">Breadcrumb</span></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</Preview>
|
||||
|
||||
### Collapsed
|
||||
|
||||
<Preview>
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||||
<ol>
|
||||
<li><a href="#">Home</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li> <span aria-hidden="true"><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></span></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><a href="#">Components</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><span aria-current="page">Breadcrumb</span></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<nav class="breadcrumb" aria-label="Breadcrumb" dir="rtl">
|
||||
<ol>
|
||||
<li><a href="#">الرئيسية</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><a href="#">المكونات</a></li>
|
||||
<li aria-hidden="true"><svg data-rtl-flip 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></li>
|
||||
<li><span aria-current="page">مسار التنقل</span></li>
|
||||
</ol>
|
||||
</nav>
|
||||
</Preview>
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
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>This example uses some simple Tailwind CSS styles along with the <a href="/components/dropdown-menu">Dropdown Menu</a> component (which may require JavaScript).</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, class="") }}
|
||||
@@ -0,0 +1,307 @@
|
||||
# Button Group
|
||||
|
||||
<Preview>
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" 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" data-variant="outline">Archive</button>
|
||||
<button type="button" class="btn" data-variant="outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline">Snooze</button>
|
||||
<div id="dropdown-menu-277805" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-menu-277805-trigger" aria-label="More archive options" aria-haspopup="menu" aria-controls="dropdown-menu-277805-menu" aria-expanded="false" class="btn" data-variant="outline" data-size="icon"><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
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, the component CSS files it composes, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/button-group.css";
|
||||
@import "basecoat-css/components/button.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Button Group composes Button styles for the child controls.
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your button group HTML">
|
||||
|
||||
Wrap related controls in a `button-group`.
|
||||
|
||||
```html
|
||||
<div role="group" aria-label="Message actions" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline">Archive</button>
|
||||
<button type="button" class="btn" data-variant="outline">Report</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
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" aria-label="Media controls" class="button-group h-fit" data-orientation="vertical">
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" 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" data-variant="outline" data-size="icon" 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" data-variant="outline" data-size="sm">Small</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm">Button</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm">Group</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon-sm" aria-label="Add small item"><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" data-variant="outline">Default</button>
|
||||
<button type="button" class="btn" data-variant="outline">Button</button>
|
||||
<button type="button" class="btn" data-variant="outline">Group</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Add item"><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" data-variant="outline" data-size="lg">Large</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="lg">Button</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="lg">Group</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon-lg" aria-label="Add large item"><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
|
||||
|
||||
Nest `button-group` components to keep controls joined within each group while adding spacing between groups.
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Pagination controls" class="button-group">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm">1</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm">2</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm">3</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm">4</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm">5</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon-sm" aria-label="Previous page"><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>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon-sm" aria-label="Next page"><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>
|
||||
</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" data-variant="secondary" data-size="sm">Copy</button>
|
||||
<hr role="separator" />
|
||||
<button type="button" class="btn" data-variant="secondary" data-size="sm">Paste</button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Split
|
||||
|
||||
Use a separator to create a split action.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="secondary">Button</button>
|
||||
<hr role="separator" />
|
||||
<button type="button" class="btn" data-variant="secondary" data-size="icon" aria-label="Add"><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>
|
||||
</Preview>
|
||||
|
||||
### Input
|
||||
|
||||
<Preview>
|
||||
<div role="group" aria-label="Search" class="button-group">
|
||||
<input type="text" class="input" placeholder="Search..." />
|
||||
<button type="button" class="btn" data-variant="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" class="button-group">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" 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>
|
||||
<div role="group" class="button-group">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Send a message..." />
|
||||
<div role="group" data-align="inline-end">
|
||||
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" aria-label="Voice mode" aria-pressed="false"><svg class="lucide lucide-audio-lines" 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 10v3" /><path d="M6 6v11" /><path d="M10 3v18" /><path d="M14 8v7" /><path d="M18 5v13" /><path d="M22 10v3" /></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Dropdown menu
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline">Follow</button>
|
||||
<div id="dropdown-menu-609880" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-menu-609880-trigger" aria-label="More follow options" aria-haspopup="menu" aria-controls="dropdown-menu-609880-menu" aria-expanded="false" class="btn" data-variant="outline" data-size="icon"><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 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" data-variant="outline" data-size="icon" aria-label="Submit amount"><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" data-variant="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-label="Open Popover" aria-expanded="false" aria-controls="popover-788465-popover" class="btn" data-variant="outline" data-size="icon"><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 text-sm" data-align="end">
|
||||
<div>
|
||||
<p class="font-medium">Start a new task with Copilot</p>
|
||||
<p class="text-muted-foreground">Describe your task in natural language.</p>
|
||||
</div>
|
||||
<textarea class="textarea min-h-16 w-full resize-none" placeholder="I need to..."></textarea>
|
||||
<p class="text-muted-foreground text-xs">Copilot will open a pull request for review.</p>
|
||||
</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">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="رجوع"><svg class="rtl:rotate-180 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>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline">أرشفة</button>
|
||||
<button type="button" class="btn" data-variant="outline">تقرير</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline">تأجيل</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="المزيد من الخيارات"><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>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -0,0 +1,243 @@
|
||||
# Button
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn" data-variant="outline">Button</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Submit"><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>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Button component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/button.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your button HTML">
|
||||
|
||||
```html
|
||||
<button type="button" class="btn">Primary</button>
|
||||
<button type="button" class="btn" data-variant="outline">Outline</button>
|
||||
<button type="button" class="btn" data-variant="secondary" data-size="sm">Small secondary</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" 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>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Add `class="btn"` to a `<button>` or `<a>`. Add `data-variant` and `data-size` when needed. Add `data-icon="inline-start"` or `data-icon="inline-end"` to inline icons when a text button needs icon-aware spacing.
|
||||
|
||||
#### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><button class="btn"></code></dt>
|
||||
<dd>Button root. Use a native <code><button></code> for actions and <code><a></code> for navigation.</dd>
|
||||
<dt><code>data-variant="primary|secondary|outline|ghost|link|destructive"</code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Visual variant. Omit it for the primary variant.</dd>
|
||||
<dt><code>data-size="xs|sm|default|lg|icon|icon-xs|icon-sm|icon-lg"</code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Button size. Omit it for the default size.</dd>
|
||||
</dl>
|
||||
|
||||
### Variants
|
||||
|
||||
| Variant | Markup |
|
||||
| --- | --- |
|
||||
| Primary | `<button class="btn">` |
|
||||
| Secondary | `<button class="btn" data-variant="secondary">` |
|
||||
| Outline | `<button class="btn" data-variant="outline">` |
|
||||
| Ghost | `<button class="btn" data-variant="ghost">` |
|
||||
| Link | `<button class="btn" data-variant="link">` |
|
||||
| Destructive | `<button class="btn" data-variant="destructive">` |
|
||||
|
||||
### Sizes
|
||||
|
||||
| Size | Markup |
|
||||
| --- | --- |
|
||||
| Extra small | `<button class="btn" data-size="xs">` |
|
||||
| Small | `<button class="btn" data-size="sm">` |
|
||||
| Default | `<button class="btn">` |
|
||||
| Large | `<button class="btn" data-size="lg">` |
|
||||
| Icon extra small | `<button class="btn" data-size="icon-xs">` |
|
||||
| Icon small | `<button class="btn" data-size="icon-sm">` |
|
||||
| Icon | `<button class="btn" data-size="icon">` |
|
||||
| Icon large | `<button class="btn" data-size="icon-lg">` |
|
||||
|
||||
Use `data-size="icon"` or one of the icon size variants for square buttons. Icon-only buttons need an accessible name, usually with `aria-label`.
|
||||
|
||||
Use `data-icon="inline-start"` or `data-icon="inline-end"` on inline icons when a text button needs icon-aware spacing. Use icon size variants only for icon-only buttons.
|
||||
|
||||
To group related buttons, use the [Button Group](/components/button-group/) component.
|
||||
|
||||
## Examples
|
||||
|
||||
### Size
|
||||
|
||||
Use `data-size` to change the size of the button.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn" data-variant="outline" data-size="xs">Extra Small</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon-xs" aria-label="Submit"><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></button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm">Small</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon-sm" aria-label="Submit"><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></button>
|
||||
<button type="button" class="btn" data-variant="outline">Default</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Submit"><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></button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="lg">Large</button>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon-lg" aria-label="Submit"><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></button>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Default
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn">Button</button>
|
||||
</Preview>
|
||||
|
||||
### Outline
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline">Outline</button>
|
||||
</Preview>
|
||||
|
||||
### Secondary
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="secondary">Secondary</button>
|
||||
</Preview>
|
||||
|
||||
### Ghost
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="ghost">Ghost</button>
|
||||
</Preview>
|
||||
|
||||
### Destructive
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="destructive">Destructive</button>
|
||||
</Preview>
|
||||
|
||||
### Link
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="link">Link</button>
|
||||
</Preview>
|
||||
|
||||
### Icon
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" aria-label="Open"><svg class="lucide lucide-circle-fading-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="M12 2a10 10 0 0 1 7.38 16.75" /><path d="m16 12-4-4-4 4" /><path d="M12 16V8" /><path d="M2.5 8.875a10 10 0 0 0-.5 3" /><path d="M2.83 16a10 10 0 0 0 2.43 3.4" /><path d="M4.636 5.235a10 10 0 0 1 .891-.857" /><path d="M8.644 21.42a10 10 0 0 0 7.631-.38" /></svg></button>
|
||||
</Preview>
|
||||
|
||||
### With icon
|
||||
|
||||
Use `data-icon="inline-start"` or `data-icon="inline-end"` on inline icons to match upstream icon-aware spacing.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" data-size="sm"><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 the `rounded-full` class to make the button rounded.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon" aria-label="Scroll up"><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>
|
||||
</Preview>
|
||||
|
||||
### Spinner
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn" disabled><svg data-icon="inline-start" 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>
|
||||
Generating
|
||||
</button>
|
||||
<button type="button" class="btn" data-variant="secondary" disabled>
|
||||
Downloading
|
||||
<svg data-icon="inline-end" 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>
|
||||
</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">
|
||||
<button type="button" class="btn" data-variant="outline" data-size="icon" 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" data-variant="outline">Archive</button>
|
||||
<button type="button" class="btn" data-variant="outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn" data-variant="outline">Snooze</button>
|
||||
<div id="button-docs-actions" class="dropdown-menu">
|
||||
<button type="button" id="button-docs-actions-trigger" aria-label="More options" aria-haspopup="menu" aria-controls="button-docs-actions-menu" aria-expanded="false" class="btn" data-variant="outline" data-size="icon"><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="button-docs-actions-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="menu" id="button-docs-actions-menu" aria-labelledby="button-docs-actions-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>
|
||||
|
||||
### 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" data-variant="outline">زر</button>
|
||||
<button type="button" class="btn" data-variant="destructive">حذف</button>
|
||||
<button type="button" class="btn" data-variant="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" data-variant="outline" data-size="icon" aria-label="Add"><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" data-variant="secondary" disabled><svg data-icon="inline-start" 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>
|
||||
جاري التحميل
|
||||
</button>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,141 +0,0 @@
|
||||
---
|
||||
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: Primary
|
||||
id: example-primary
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: Ghost
|
||||
id: example-ghost
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: With Icon
|
||||
id: example-with-icon
|
||||
- label: Loading
|
||||
id: example-loading
|
||||
- label: Large
|
||||
id: example-large
|
||||
- label: Small
|
||||
id: example-small
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<button class="btn">Button</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use one of the <code>btn</code> classes to style any element as a button:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variants</b>:
|
||||
<ul>
|
||||
<li><code>btn</code> or <code>btn-primary</code> for 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>
|
||||
<li><code>btn-icon</code> for icon buttons. This can be combined with other variants, for example <code>btn-icon-destructive</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Sizes</b>:
|
||||
<ul>
|
||||
<li><code>btn-sm</code> for small buttons.</li>
|
||||
<li><code>btn-lg</code> for large buttons.</li>
|
||||
<li>You can combine sizes with any variant, for example <code>btn-lg-destructive</code> or <code>btn-sm-icon-outline</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</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 %}<button class="btn">Primary</button>{% endset %}
|
||||
{{ code_preview("button-primary", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-secondary">Button</button>{% endset %}
|
||||
{{ code_preview("button-secondary", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-destructive">Destructive</button>{% endset %}
|
||||
{{ code_preview("button-destructive", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline">Outline</button>{% endset %}
|
||||
{{ code_preview("button-outline", code) }}
|
||||
|
||||
<h3 id="example-ghost"><a href="#example-ghost">Ghost</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-ghost">Ghost</button>{% endset %}
|
||||
{{ code_preview("button-ghost", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<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 class="btn-icon-outline">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-icon", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With Icon</a></h3>
|
||||
|
||||
{% set code %}<button class="btn">
|
||||
{% lucide "send" %}
|
||||
Send email
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-with-icon", code) }}
|
||||
|
||||
<h3 id="example-loading"><a href="#example-loading">Loading</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-loading", code) }}
|
||||
|
||||
<h3 id="example-large"><a href="#example-large">Large</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-lg">
|
||||
{% lucide "shopping-cart" %}
|
||||
Buy
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-large", code) }}
|
||||
|
||||
<h3 id="example-small"><a href="#example-small">Small</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-sm-icon-destructive">
|
||||
{% lucide "trash" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-small", code) }}
|
||||
@@ -0,0 +1,180 @@
|
||||
# Card
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Login to your account</h2>
|
||||
<p>Enter your email below to login to your account</p>
|
||||
<div class="card-action">
|
||||
<button type="button" class="btn" data-variant="link">Sign Up</button>
|
||||
</div>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-card-email">Email</label>
|
||||
<input class="input" type="email" id="demo-card-email" placeholder="m@example.com" required />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center">
|
||||
<label class="label" for="demo-card-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-password" required />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer class="flex-col gap-2">
|
||||
<button type="button" class="btn w-full">Login</button>
|
||||
<button type="button" class="btn w-full" data-variant="outline">Login with Google</button>
|
||||
</footer>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Card component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/card.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your card HTML">
|
||||
|
||||
```html
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Card Title</h2>
|
||||
<p>Card Description</p>
|
||||
<div class="card-action">Card Action</div>
|
||||
</header>
|
||||
<section> <p>Card Content</p></section>
|
||||
<footer>
|
||||
<p>Card Footer</p>
|
||||
</footer>
|
||||
</div>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><div class="card"></code></dt>
|
||||
<dd>
|
||||
The card container. Use <code>data-size="sm"</code> for a denser layout.
|
||||
<dl>
|
||||
<dt><code><header></code></dt>
|
||||
<dd>
|
||||
The card header.
|
||||
<dl>
|
||||
<dt><code><h2></code> or <code><h3></code></dt>
|
||||
<dd>The card title.</dd>
|
||||
<dt><code><p></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>The card description.</dd>
|
||||
<dt><code><div class="card-action"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Action or metadata placed in the top-right of the header. Use <code><menu class="card-action"></code> when the content is a list of commands.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code><section></code></dt>
|
||||
<dd>The main card content.</dd>
|
||||
<dt><code><footer></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Footer actions or secondary content at the bottom of the card.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Size
|
||||
|
||||
Use `data-size="sm"` to set the size of the card to small. The small size variant uses smaller spacing.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="card" data-size="sm">
|
||||
<header>
|
||||
<h2>Small Card</h2>
|
||||
<p>This card uses the small size variant.</p>
|
||||
<div class="card-action">
|
||||
<button type="button" class="btn" data-size="sm" data-variant="outline">Action</button>
|
||||
</div>
|
||||
</header>
|
||||
<section> <p>The card component supports a size attribute that can be set to <code>sm</code> for a more compact appearance.</p></section>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Image
|
||||
|
||||
Add an image before the card header to create a card with an image.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="card">
|
||||
<img
|
||||
alt="Event cover"
|
||||
class="aspect-video w-full object-cover"
|
||||
src="https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=1200&auto=format&fit=crop&q=80"
|
||||
/>
|
||||
<header>
|
||||
<h2>Design systems meetup</h2>
|
||||
<p>A practical talk on component APIs, accessibility, and shipping faster.</p>
|
||||
<div class="card-action">
|
||||
<span class="badge" data-variant="secondary">Featured</span>
|
||||
</div>
|
||||
</header>
|
||||
<footer>
|
||||
<button type="button" class="btn w-full">View Event</button>
|
||||
</footer>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## RTL
|
||||
|
||||
Add `dir="rtl"` to the card or an ancestor.
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div class="card" dir="rtl">
|
||||
<header>
|
||||
<h2>تسجيل الدخول إلى حسابك</h2>
|
||||
<p>أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك</p>
|
||||
<div class="card-action">
|
||||
<button type="button" class="btn" data-variant="link">إنشاء حساب</button>
|
||||
</div>
|
||||
</header>
|
||||
<section>
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label class="label" for="demo-card-rtl-email">البريد الإلكتروني</label>
|
||||
<input class="input" type="email" id="demo-card-rtl-email" placeholder="m@example.com" required />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center">
|
||||
<label class="label" for="demo-card-rtl-password">كلمة المرور</label>
|
||||
<a href="#" class="ms-auto inline-block text-sm underline-offset-4 hover:underline">نسيت كلمة المرور؟</a>
|
||||
</div>
|
||||
<input class="input" type="password" id="demo-card-rtl-password" required />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer class="flex-col gap-2">
|
||||
<button type="button" class="btn w-full">تسجيل الدخول</button>
|
||||
<button type="button" class="btn w-full" data-variant="outline">تسجيل الدخول باستخدام Google</button>
|
||||
</footer>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,156 +0,0 @@
|
||||
---
|
||||
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: With content and footer
|
||||
id: example-with-content-and-footer
|
||||
- label: With image
|
||||
id: example-with-image
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div class="card w-full">
|
||||
<header>
|
||||
<h2>Login to your account</h2>
|
||||
<p>Enter your details below to login to your account</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-card-form-email">Email</label>
|
||||
<input type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<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 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>{% endset %}
|
||||
{{ code_preview("card", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<header>
|
||||
<h2>Card Title</h2>
|
||||
<p>Card Description</p>
|
||||
</header>
|
||||
<section>
|
||||
<p>Card Content</p>
|
||||
</section>
|
||||
<footer>
|
||||
<p>Card Footer</p>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<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>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-with-content-and-footer"><a href="#example-with-content-and-footer">With content and footer</a></h3>
|
||||
|
||||
{% set code %}<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-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">
|
||||
</div>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-with-content-and-footer", code) }}
|
||||
|
||||
<h3 id="example-with-image"><a href="#example-with-image">With image</a></h3>
|
||||
|
||||
{% set code %}<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>{% endset %}
|
||||
{{ code_preview("card-with-image", code) }}
|
||||
@@ -0,0 +1,268 @@
|
||||
import basecoatRuntimeSource from "../../../src/js/basecoat.js?raw"
|
||||
import chartRuntimeSource from "../../../src/js/chart.js?raw"
|
||||
import chartExamplesSource from "../site/chart-examples.js?raw"
|
||||
|
||||
# Chart
|
||||
|
||||
<Callout type="warning" title="Alpha API" icon="triangle-alert">
|
||||
Chart support is alpha. The JavaScript API, generated markup, and option names may change before the stable 1.0 release.
|
||||
</Callout>
|
||||
|
||||
Use Chart.js with Basecoat defaults for themed colors, external tooltips, and optional generated legends.
|
||||
|
||||
<Preview class="w-full">
|
||||
<canvas id="chart-example-overview" aria-label="Visitors by device"></canvas>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Chart component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/chart.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Include JavaScript">
|
||||
|
||||
Copy or serve Chart.js, the Basecoat runtime, and Chart script.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/chart.umd.min.js"></script>
|
||||
<script src="/assets/js/basecoat.min.js" defer></script>
|
||||
<script src="/assets/js/chart.min.js" defer></script>
|
||||
```
|
||||
|
||||
Chart is not included in the full Basecoat JavaScript bundle.
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your chart HTML">
|
||||
|
||||
```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>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
`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.
|
||||
|
||||
## 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)",
|
||||
surface: "gradient",
|
||||
dataset: {
|
||||
fill: true,
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
### Bar chart - multiple
|
||||
|
||||
<Preview class="w-full">
|
||||
<canvas id="chart-example-visitors" aria-label="Monthly visitors by device"></canvas>
|
||||
</Preview>
|
||||
|
||||
### Line chart - linear
|
||||
|
||||
<Preview class="w-full">
|
||||
<canvas id="chart-example-line" aria-label="Revenue line chart"></canvas>
|
||||
</Preview>
|
||||
|
||||
### Line chart - step
|
||||
|
||||
<Preview class="w-full">
|
||||
<canvas id="chart-example-step" aria-label="Support queue step chart"></canvas>
|
||||
</Preview>
|
||||
|
||||
### Bar chart - stacked
|
||||
|
||||
<Preview class="w-full">
|
||||
<canvas id="chart-example-stacked" aria-label="Stacked pipeline bar chart"></canvas>
|
||||
</Preview>
|
||||
|
||||
### Pie chart - donut
|
||||
|
||||
<Preview class="w-full">
|
||||
<canvas id="chart-example-donut" aria-label="Traffic source donut chart"></canvas>
|
||||
</Preview>
|
||||
|
||||
### Radar chart
|
||||
|
||||
<Preview class="w-full">
|
||||
<canvas id="chart-example-radar" aria-label="Channel performance radar chart"></canvas>
|
||||
</Preview>
|
||||
|
||||
<script is:inline src="https://cdn.jsdelivr.net/npm/chart.js/dist/chart.umd.min.js"></script>
|
||||
<script is:inline>{`
|
||||
(() => {
|
||||
const initChartPage = () => {
|
||||
if (!window.Chart) {
|
||||
requestAnimationFrame(initChartPage)
|
||||
return
|
||||
}
|
||||
|
||||
if (!window.basecoat) {
|
||||
${basecoatRuntimeSource}
|
||||
}
|
||||
|
||||
if (!window.basecoat.chart) {
|
||||
${chartRuntimeSource}
|
||||
}
|
||||
|
||||
${chartExamplesSource}
|
||||
}
|
||||
|
||||
initChartPage()
|
||||
})()
|
||||
`}</script>
|
||||
|
||||
## 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. |
|
||||
|
||||
### Series options
|
||||
|
||||
| Option | Description |
|
||||
| --- | --- |
|
||||
| `label` | Display label for legends and tooltips. |
|
||||
| `color` | Base color for the series stroke, fill, legend marker, and tooltip marker. |
|
||||
| `surface` | Derives a fill from `color`. Use `true` for a translucent fill, `"gradient"` for an upstream-style vertical fade, or `{ from, to }` for custom opacity stops. |
|
||||
| `dataset` | Raw Chart.js dataset options. |
|
||||
@@ -1,814 +0,0 @@
|
||||
---
|
||||
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,227 @@
|
||||
# Checkbox
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="fieldset">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="terms-checkbox" name="terms-checkbox" class="input" />
|
||||
<label for="terms-checkbox">Accept terms and conditions</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="terms-checkbox-2" name="terms-checkbox-2" class="input" checked />
|
||||
<section>
|
||||
<label for="terms-checkbox-2">Accept terms and conditions</label>
|
||||
<p>By clicking this checkbox, you agree to the terms.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal" data-disabled>
|
||||
<input type="checkbox" id="toggle-checkbox" name="toggle-checkbox" class="input" disabled />
|
||||
<label for="toggle-checkbox">Enable notifications</label>
|
||||
</div>
|
||||
<label>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="toggle-checkbox-2" name="toggle-checkbox-2" class="input" />
|
||||
<section>
|
||||
<h3>Enable notifications</h3>
|
||||
<p>You can enable or disable notifications at any time.</p>
|
||||
</section>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Checkbox component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/checkbox.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your checkbox HTML">
|
||||
|
||||
Add `class="input"` to checkbox inputs. Pair it with [Field](/components/field) and a native `<label>` for layout and labeling.
|
||||
|
||||
```html
|
||||
<input type="checkbox" class="input" />
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
Pair the checkbox with `field` and a native `<label>` for proper layout and labeling.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="fieldset">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="terms-checkbox-basic" name="terms-checkbox-basic" class="input" />
|
||||
<label for="terms-checkbox-basic">Accept terms and conditions</label>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Description
|
||||
|
||||
Use a `section` and paragraph for helper text.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="fieldset">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="terms-checkbox-desc" name="terms-checkbox-desc" class="input" checked aria-describedby="terms-checkbox-desc-description" />
|
||||
<section>
|
||||
<label for="terms-checkbox-desc">Accept terms and conditions</label>
|
||||
<p id="terms-checkbox-desc-description">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
Use the `disabled` attribute to prevent interaction and add `data-disabled` to the `.field` component for disabled styles.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="fieldset">
|
||||
<div role="group" class="field" data-orientation="horizontal" data-disabled>
|
||||
<input type="checkbox" id="toggle-checkbox-disabled" name="toggle-checkbox-disabled" class="input" disabled />
|
||||
<label for="toggle-checkbox-disabled">Enable notifications</label>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Invalid
|
||||
|
||||
Set `aria-invalid="true"` on the checkbox and `data-invalid` on the field wrapper to show invalid styles.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="fieldset">
|
||||
<div role="group" class="field" data-orientation="horizontal" data-invalid>
|
||||
<input type="checkbox" id="terms-checkbox-invalid" name="terms-checkbox-invalid" class="input" aria-invalid="true" />
|
||||
<label for="terms-checkbox-invalid">Accept terms and conditions</label>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Group
|
||||
|
||||
Use multiple fields to create a checkbox list.
|
||||
|
||||
<Preview>
|
||||
<fieldset class="fieldset">
|
||||
<legend data-variant="label">Show these items on the desktop:</legend>
|
||||
<p>Select the items you want to show on the desktop.</p>
|
||||
<div role="group" aria-label="Desktop items" data-slot="checkbox-group">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="finder-pref-9k2-hard-disks-ljj-checkbox" name="finder-pref-9k2-hard-disks-ljj-checkbox" checked />
|
||||
<label for="finder-pref-9k2-hard-disks-ljj-checkbox" class="font-normal">Hard disks</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="finder-pref-9k2-external-disks-1yg-checkbox" name="finder-pref-9k2-external-disks-1yg-checkbox" checked />
|
||||
<label for="finder-pref-9k2-external-disks-1yg-checkbox" class="font-normal">External disks</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="finder-pref-9k2-cds-dvds-fzt-checkbox" name="finder-pref-9k2-cds-dvds-fzt-checkbox" />
|
||||
<label for="finder-pref-9k2-cds-dvds-fzt-checkbox" class="font-normal">CDs, DVDs, and iPods</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input class="input" type="checkbox" id="finder-pref-9k2-connected-servers-6l2-checkbox" name="finder-pref-9k2-connected-servers-6l2-checkbox" />
|
||||
<label for="finder-pref-9k2-connected-servers-6l2-checkbox" class="font-normal">Connected servers</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</Preview>
|
||||
|
||||
### Table
|
||||
|
||||
<Preview class="w-full">
|
||||
<div class="table-container">
|
||||
<table class="table" data-checkbox-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-8"><input type="checkbox" id="select-all-checkbox" name="select-all-checkbox" class="input" aria-label="Select all" /></th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr data-state="selected">
|
||||
<td><input type="checkbox" id="row-1-checkbox" name="row-1-checkbox" class="input" aria-label="Select Sarah Chen" checked /></td>
|
||||
<td class="font-medium">Sarah Chen</td>
|
||||
<td>sarah.chen@example.com</td>
|
||||
<td>Admin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" id="row-2-checkbox" name="row-2-checkbox" class="input" aria-label="Select Marcus Rodriguez" /></td>
|
||||
<td class="font-medium">Marcus Rodriguez</td>
|
||||
<td>marcus.rodriguez@example.com</td>
|
||||
<td>User</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" id="row-3-checkbox" name="row-3-checkbox" class="input" aria-label="Select Priya Patel" /></td>
|
||||
<td class="font-medium">Priya Patel</td>
|
||||
<td>priya.patel@example.com</td>
|
||||
<td>User</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" id="row-4-checkbox" name="row-4-checkbox" class="input" aria-label="Select David Kim" /></td>
|
||||
<td class="font-medium">David Kim</td>
|
||||
<td>david.kim@example.com</td>
|
||||
<td>Editor</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
## RTL
|
||||
|
||||
Add `dir="rtl"` to the field group or an ancestor.
|
||||
|
||||
<Preview>
|
||||
<div role="group" class="fieldset" dir="rtl">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="terms-checkbox-rtl" name="terms-checkbox" class="input" />
|
||||
<label for="terms-checkbox-rtl">قبول الشروط والأحكام</label>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="terms-checkbox-2-rtl" name="terms-checkbox-2" class="input" checked aria-describedby="terms-checkbox-2-rtl-description" />
|
||||
<section>
|
||||
<label for="terms-checkbox-2-rtl">قبول الشروط والأحكام</label>
|
||||
<p id="terms-checkbox-2-rtl-description">بالنقر على هذا المربع، فإنك توافق على الشروط.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div role="group" class="field" data-orientation="horizontal" data-disabled>
|
||||
<input type="checkbox" id="toggle-checkbox-rtl" name="toggle-checkbox" class="input" disabled />
|
||||
<label for="toggle-checkbox-rtl">تفعيل الإشعارات</label>
|
||||
</div>
|
||||
<label>
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
<input type="checkbox" id="toggle-checkbox-2-rtl" name="toggle-checkbox-2" class="input" />
|
||||
<section>
|
||||
<h3>تفعيل الإشعارات</h3>
|
||||
<p>يمكنك تفعيل أو إلغاء تفعيل الإشعارات في أي وقت.</p>
|
||||
</section>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,101 +0,0 @@
|
||||
---
|
||||
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: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: With text
|
||||
id: example-with-text
|
||||
- label: Form
|
||||
id: example-form
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input">
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{{ code_preview("checkbox", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>input</code> class to your <code><input type="radio"></code> elements or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). You can also set the <code>aria-invalid</code> attribute to <code>true</code> to make the input invalid.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input" disabled>
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{{ code_preview("checkbox-disabled", code) }}
|
||||
|
||||
<h3 id="example-with-text"><a href="#example-with-text">With text</a></h3>
|
||||
|
||||
{% set code %}<div class="flex items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-with-text" class="input">
|
||||
<div class="grid gap-2">
|
||||
<label for="checkbox-with-text" 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>{% endset %}
|
||||
{{ code_preview("checkbox-with-text", code) }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
|
||||
<input type="checkbox" id="checkbox-form-1">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="checkbox-form-1" class="leading-snug">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>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code) }}
|
||||
|
||||
{% set code %}<form class="form flex flex-col gap-4">
|
||||
<header>
|
||||
<label for="demo-form-checkboxes" class="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="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="1" checked>
|
||||
Recents
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="2" checked>
|
||||
Home
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="3">
|
||||
Applications
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="4">
|
||||
Desktop
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="5">
|
||||
Download
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="6">
|
||||
Documents
|
||||
</label>
|
||||
</fieldset>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code) }}
|
||||
@@ -0,0 +1,353 @@
|
||||
# Combobox
|
||||
|
||||
<Preview>
|
||||
<div id="framework-combobox" class="combobox">
|
||||
<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 items 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 CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Combobox component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/combobox.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Include JavaScript">
|
||||
|
||||
Copy or serve the full Basecoat JavaScript bundle.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/all.min.js" defer></script>
|
||||
```
|
||||
|
||||
Or copy or serve the Basecoat runtime and Combobox script.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/basecoat.min.js" defer></script>
|
||||
<script src="/assets/js/combobox.min.js" defer></script>
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</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 items 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><div class="combobox"></code></dt>
|
||||
<dd>
|
||||
Root element. Add <code>data-auto-highlight="true"</code> to make the first visible option active when the list opens or filters. Add <code>data-format="object"</code> to serialize selected values as objects. Add <code>data-filter="manual"</code> when your app owns filtering.
|
||||
<dl>
|
||||
<dt><code><input type="text" role="combobox"></code></dt>
|
||||
<dd>The editable control. It can be a direct child, inside <code>.input-group</code>, or inside <code>data-popover</code> for popup mode.</dd>
|
||||
<dt><code><button aria-haspopup="listbox"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Opens the list from an input group or popup trigger. In popup mode, include a child with <code>data-value</code> and optional <code>data-placeholder</code> to display the selected value.</dd>
|
||||
<dt><code><button data-clear></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Clears the current selection. Put the clear icon inside the button.</dd>
|
||||
<dt><code><svg class="combobox-trigger-icon"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Decorative trailing icon for input-style comboboxes. Replace the SVG to customize the icon.</dd>
|
||||
<dt><code><div data-popover></code></dt>
|
||||
<dd>The suggestions popup. Use <code>data-side</code> and <code>data-align</code> like Popover.</dd>
|
||||
<dt><code><div role="listbox"></code></dt>
|
||||
<dd>The suggestions list. Add <code>aria-multiselectable="true"</code> in multiple mode. The component uses that attribute to render selected chips around the input.</dd>
|
||||
<dt><code><div role="option" data-value="..."></code></dt>
|
||||
<dd>Selectable option. Options can contain custom HTML; use <code>data-label</code> when the input should display different text than the rendered content.</dd>
|
||||
<dt><code><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>, selected values are stored as 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. |
|
||||
| `combobox.clear()` | Method | Clears the selected value. |
|
||||
| `combobox.refresh()` | Method | Rescans options after children change inside the existing `role="listbox"` element. |
|
||||
|
||||
## Examples
|
||||
|
||||
### Multiple
|
||||
|
||||
<Preview class="w-full max-w-xs">
|
||||
<div id="frameworks-combobox" class="combobox w-full" data-auto-highlight="true">
|
||||
<input type="text" role="combobox" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="frameworks-combobox-listbox" />
|
||||
<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 items 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>
|
||||
|
||||
### Clear button
|
||||
|
||||
<Preview>
|
||||
<div id="clear-combobox" class="combobox">
|
||||
<div class="input-group">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="clear-combobox-listbox" />
|
||||
<span role="group" data-align="inline-end">
|
||||
<button type="button" data-clear aria-label="Clear selection"><svg class="size-4" 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"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
||||
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" aria-label="Open suggestions" aria-haspopup="listbox" aria-expanded="false" aria-controls="clear-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"><path d="m6 9 6 6 6-6" /></svg></button>
|
||||
</span>
|
||||
</div>
|
||||
<div id="clear-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="clear-combobox-listbox" aria-orientation="vertical" data-empty="No items 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-clear" value="Next.js" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Groups
|
||||
|
||||
<Preview>
|
||||
<div id="timezone-combobox" class="combobox">
|
||||
<input type="text" role="combobox" placeholder="Select a 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" class="scrollbar-sm" aria-orientation="vertical" data-empty="No timezones found.">
|
||||
<div role="group" aria-labelledby="timezone-americas">
|
||||
<div role="heading" id="timezone-americas">Americas</div>
|
||||
<div role="option" data-value="(GMT-5) New York">(GMT-5) New York</div>
|
||||
<div role="option" data-value="(GMT-8) Los Angeles">(GMT-8) Los Angeles</div>
|
||||
<div role="option" data-value="(GMT-6) Chicago">(GMT-6) Chicago</div>
|
||||
<div role="option" data-value="(GMT-5) Toronto">(GMT-5) Toronto</div>
|
||||
<div role="option" data-value="(GMT-8) Vancouver">(GMT-8) Vancouver</div>
|
||||
<div role="option" data-value="(GMT-3) São 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="(GMT+0) London">(GMT+0) London</div>
|
||||
<div role="option" data-value="(GMT+1) Paris">(GMT+1) Paris</div>
|
||||
<div role="option" data-value="(GMT+1) Berlin">(GMT+1) Berlin</div>
|
||||
<div role="option" data-value="(GMT+1) Rome">(GMT+1) Rome</div>
|
||||
<div role="option" data-value="(GMT+1) Madrid">(GMT+1) Madrid</div>
|
||||
<div role="option" data-value="(GMT+1) Amsterdam">(GMT+1) Amsterdam</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="timezone-asia-pacific">
|
||||
<div role="heading" id="timezone-asia-pacific">Asia/Pacific</div>
|
||||
<div role="option" data-value="(GMT+9) Tokyo">(GMT+9) Tokyo</div>
|
||||
<div role="option" data-value="(GMT+8) Shanghai">(GMT+8) Shanghai</div>
|
||||
<div role="option" data-value="(GMT+8) Singapore">(GMT+8) Singapore</div>
|
||||
<div role="option" data-value="(GMT+4) Dubai">(GMT+4) Dubai</div>
|
||||
<div role="option" data-value="(GMT+11) Sydney">(GMT+11) Sydney</div>
|
||||
<div role="option" data-value="(GMT+9) Seoul">(GMT+9) Seoul</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Custom items
|
||||
|
||||
<Preview>
|
||||
<div id="country-combobox" class="combobox">
|
||||
<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 items 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</span></span></div>
|
||||
<div role="option" data-value="australia" data-label="Australia" data-filter="Australia Oceania"><span class="flex flex-col"><span>Australia</span><span class="text-muted-foreground text-xs">Oceania</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</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">
|
||||
<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 items 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 role="alert">Select a framework to continue.</p>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Preview>
|
||||
<div id="disabled-combobox" class="combobox">
|
||||
<input type="text" role="combobox" placeholder="Select a framework" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="disabled-combobox-listbox" 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="disabled-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="disabled-combobox-listbox" aria-orientation="vertical">
|
||||
<div role="option" data-value="Next.js">Next.js</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Auto highlight
|
||||
|
||||
Auto highlight makes the first visible option active when the list opens or filters, so pressing Enter can select it without arrowing first.
|
||||
|
||||
<Preview>
|
||||
<div id="auto-highlight-combobox" class="combobox" 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="auto-highlight-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="auto-highlight-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="auto-highlight-combobox-listbox" aria-orientation="vertical" data-empty="No items 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>
|
||||
</Preview>
|
||||
|
||||
### Popup
|
||||
|
||||
<Preview>
|
||||
<div id="popup-combobox" class="combobox">
|
||||
<button type="button" class="btn w-64 justify-between font-normal" data-variant="outline" aria-haspopup="listbox" aria-expanded="false" aria-controls="popup-combobox-listbox">
|
||||
<span data-value data-placeholder="Select country">Select country</span>
|
||||
<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"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</button>
|
||||
<div id="popup-combobox-popover" data-popover aria-hidden="true">
|
||||
<div class="input-group">
|
||||
<input type="text" role="combobox" placeholder="Search" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="popup-combobox-listbox" />
|
||||
</div>
|
||||
<div role="listbox" id="popup-combobox-listbox" aria-orientation="vertical" data-empty="No items found.">
|
||||
<div role="option" data-value="argentina" data-label="Argentina">Argentina</div>
|
||||
<div role="option" data-value="australia" data-label="Australia">Australia</div>
|
||||
<div role="option" data-value="brazil" data-label="Brazil">Brazil</div>
|
||||
<div role="option" data-value="canada" data-label="Canada">Canada</div>
|
||||
<div role="option" data-value="japan" data-label="Japan">Japan</div>
|
||||
<div role="option" data-value="united-states" data-label="United States">United States</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Input group
|
||||
|
||||
<Preview>
|
||||
<div id="input-group-combobox" class="combobox w-60">
|
||||
<div class="input-group">
|
||||
<span data-align="inline-start"><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"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg></span>
|
||||
<input type="text" role="combobox" placeholder="Select a timezone" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="input-group-combobox-listbox" />
|
||||
<button type="button" class="btn" data-variant="ghost" data-size="icon-xs" data-align="inline-end" aria-label="Open suggestions" aria-haspopup="listbox" aria-expanded="false" aria-controls="input-group-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"><path d="m6 9 6 6 6-6" /></svg></button>
|
||||
</div>
|
||||
<div id="input-group-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="input-group-combobox-listbox" aria-orientation="vertical" data-empty="No timezones found.">
|
||||
<div role="group" aria-labelledby="input-group-americas">
|
||||
<div role="heading" id="input-group-americas">Americas</div>
|
||||
<div role="option" data-value="(GMT-5) New York">(GMT-5) New York</div>
|
||||
<div role="option" data-value="(GMT-8) Los Angeles">(GMT-8) Los Angeles</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="input-group-europe">
|
||||
<div role="heading" id="input-group-europe">Europe</div>
|
||||
<div role="option" data-value="(GMT+0) London">(GMT+0) London</div>
|
||||
<div role="option" data-value="(GMT+1) Paris">(GMT+1) Paris</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" />
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl" class="field w-full max-w-xs">
|
||||
<label for="rtl-combobox-input">الفئات</label>
|
||||
<div id="rtl-combobox" class="combobox w-full" data-auto-highlight="true">
|
||||
<input id="rtl-combobox-input" type="text" role="combobox" placeholder="أضف فئات" autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" aria-expanded="false" aria-controls="rtl-combobox-listbox" />
|
||||
<div id="rtl-combobox-popover" data-popover aria-hidden="true">
|
||||
<div role="listbox" id="rtl-combobox-listbox" aria-orientation="vertical" aria-multiselectable="true" data-empty="لم يتم العثور على فئات.">
|
||||
<div role="option" data-value="technology" data-label="التكنولوجيا">التكنولوجيا</div>
|
||||
<div role="option" data-value="design" data-label="التصميم">التصميم</div>
|
||||
<div role="option" data-value="business" data-label="الأعمال">الأعمال</div>
|
||||
<div role="option" data-value="marketing" data-label="التسويق">التسويق</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value='["technology"]' />
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,285 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Combobox
|
||||
description: Autocomplete input and command palette 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: Scrollable
|
||||
id: example-scrollable
|
||||
- label: Top side
|
||||
id: example-top
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% set code_html %}
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search framework..."
|
||||
) %}
|
||||
{% for framework in options_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 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/select.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="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
|
||||
select.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 uses the same markup and JavaScript as the <a href="/components/select#usage-html-js">Select component</a>, the only difference being the search box at the top of the listbox:</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="select"></code></dt>
|
||||
<dd>Wraps around the entire component.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" popovertarget="{ POPOVER_ID }"></code></dt>
|
||||
<dd>
|
||||
<p>The trigger to open the popover, can also have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>id="{BUTTON_ID}"</code>: linked to by the <code>aria-labelledby</code> attribute of the listbox.</li>
|
||||
<li><code>aria-haspopup="listbox"</code>: indicates that the button opens a listbox.</li>
|
||||
<li><code>aria-controls="{ LISTBOX_ID }"</code>: points to the listbox's id.</li>
|
||||
<li><code>aria-expanded="false"</code>: tracks the popover's state.</li>
|
||||
<li><code>aria-activedescendant="{ OPTION_ID }"</code>: points to the active option's id.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div popover class="popover" id="{ POPOVER_ID }"></code></dt>
|
||||
<dd>As with the <a href="/components/popover">Popover</a> component, you can set up the side and alignment of the popover using the <code>data-side</code> and <code>data-align</code> attributes.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the search input to filter the options in the listbox.</dd>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>The listbox containing the options. Should have the following attributes:
|
||||
<ul>
|
||||
<li><code>id="{ LISTBOX_ID }"</code>: refered to by the <code>aria-controls</code> attribute of the trigger.</li>
|
||||
<li><code>aria-labelledby="{ BUTTON_ID }"</code>: linked to by the button's <code>id</code> attribute.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="{ VALUE }"></code></dt>
|
||||
<dd>Option that can be selected.Should have a unique id if you use the <code>aria-activedescendant</code> attribute on the trigger.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/options.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group of options, can have a <code>aria-labelledby</code> attribute to link to a heading.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading"></code></dt>
|
||||
<dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><input type="hidden" name="{ NAME }" value="{ VALUE }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The hidden input that holds the value of the field (if needed).</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>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</dd>
|
||||
<dt><code>change</code></dt>
|
||||
<dd>When the selected value changes, the component dispatches a custom (bubbling) <code>change</code> event on itself, with the selected value in <code>event.detail</code> (e.g. <code>{detail: {value: "something"}}</code>).</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-4">JavaScript methods</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>selectByValue</code></dt>
|
||||
<dd>
|
||||
<p>You can call this method on the component after it is initialized to select an option by value (i.e. the option with the matching <code>data-value</code> attribute):</p>
|
||||
|
||||
{% set code_script %}<script>
|
||||
const comboboxComponent = document.querySelector('#my-combobox');
|
||||
comboboxComponent.addEventListener('basecoat:initialized', () => {
|
||||
comboboxComponent.selectByValue('apple');
|
||||
});
|
||||
</script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
</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">select()</code> Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set <code>is_combobox</code> to <code>True</code> (or <code>true</code> for Nunjucks).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-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/select.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/select.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call select(
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true
|
||||
) %}
|
||||
<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>
|
||||
<div role="option" data-value="remix">Remix</div>
|
||||
<div role="option" data-value="astro">Astro</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% 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" }
|
||||
]
|
||||
}
|
||||
] %}
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
popover_attrs={"class": "w-72"},
|
||||
listbox_attrs={"data-empty": "No timezone found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search timezone..."
|
||||
) %}
|
||||
<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 }}">
|
||||
<div role="heading" id="demo-combobox-timezones-group-{{ loop.index0 }}">{{ group.label }}</div>
|
||||
{% 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 %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-with-groups", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-top"><a href="#example-top">Top side</a></h3>
|
||||
|
||||
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search framework...",
|
||||
popover_attrs={"data-side": "top"}
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}{% endset %}
|
||||
{{ code_preview("combobox", code | prettyHtml) }}
|
||||
@@ -0,0 +1,390 @@
|
||||
# Command
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="demo-command-standalone" class="command rounded-lg 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.">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
## 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 CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Command component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/command.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Include JavaScript">
|
||||
|
||||
Copy or serve the full Basecoat JavaScript bundle.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/all.min.js" defer></script>
|
||||
```
|
||||
|
||||
Or copy or serve the Basecoat runtime and Command script.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/basecoat.min.js" defer></script>
|
||||
<script src="/assets/js/command.min.js" defer></script>
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</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.">
|
||||
<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>
|
||||
<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>
|
||||
```
|
||||
|
||||
Use `<dialog class="command-dialog">` for the command palette variant.
|
||||
|
||||
```html
|
||||
<button class="btn" data-variant="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><dialog class="command-dialog"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Dialog variant. Wraps one direct child <code><div class="command"></code>. Add <code>aria-label</code> or <code>aria-labelledby</code> for an accessible name.
|
||||
<dl>
|
||||
<dt><code><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><header></code></dt>
|
||||
<dd>
|
||||
Search input wrapper. The macro includes a search icon and input.
|
||||
<dl>
|
||||
<dt><code><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><div role="menu" id="{ MENU_ID }"></code></dt>
|
||||
<dd>
|
||||
Command list. Set <code>data-empty</code> to customize the empty state.
|
||||
<dl>
|
||||
<dt><code><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Groups related command items. Hidden automatically when every item in the group is filtered out.</dd>
|
||||
<dt><code><span role="heading" id="{ HEADING_ID }"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Group heading. Use an <code>id</code> when the parent group uses <code>aria-labelledby</code>.</dd>
|
||||
<dt><code><div role="menuitem"></code> or <code><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>, <code>aria-disabled="true"</code>, and <code>data-disabled="true"</code>.</dd>
|
||||
<dt><code><span data-shortcut></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code><span data-indicator></code> <span class="badge" data-variant="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><hr role="separator"></code> <span class="badge" data-variant="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.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-basic').showModal()">Open Menu</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.">
|
||||
<div role="group" aria-labelledby="command-basic-suggestions">
|
||||
<span role="heading" id="command-basic-suggestions">Suggestions</span>
|
||||
<div role="menuitem" data-filter="Calendar">Calendar</div>
|
||||
<div role="menuitem" data-filter="Search Emoji">Search Emoji</div>
|
||||
<div role="menuitem" data-filter="Calculator">Calculator</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Shortcuts
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-shortcuts-dialog').showModal()">Open Menu</button>
|
||||
<dialog id="command-shortcuts-dialog" 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-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.">
|
||||
<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>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Groups
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-groups-dialog').showModal()">Open Menu</button>
|
||||
<dialog id="command-groups-dialog" 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-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.">
|
||||
<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" data-filter="Calculator"><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>
|
||||
<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>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Scrollable
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-scrollable-dialog').showModal()">Open Menu</button>
|
||||
<dialog id="command-scrollable-dialog" 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-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.">
|
||||
<div role="group" aria-labelledby="command-scrollable-navigation">
|
||||
<span role="heading" id="command-scrollable-navigation">Navigation</span>
|
||||
<div role="menuitem" data-filter="Home"><svg class="lucide lucide-home" 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 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" /><path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /></svg><span>Home</span><span data-shortcut>⌘H</span></div>
|
||||
<div role="menuitem" data-filter="Inbox"><svg class="lucide lucide-inbox" 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"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></svg><span>Inbox</span><span data-shortcut>⌘I</span></div>
|
||||
<div role="menuitem" data-filter="Documents"><svg class="lucide lucide-file-text" 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 9H8" /><path d="M16 13H8" /><path d="M16 17H8" /></svg><span>Documents</span><span data-shortcut>⌘D</span></div>
|
||||
<div role="menuitem" data-filter="Folders"><svg class="lucide lucide-folder" 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 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><span>Folders</span><span data-shortcut>⌘F</span></div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-scrollable-actions">
|
||||
<span role="heading" id="command-scrollable-actions">Actions</span>
|
||||
<div role="menuitem" data-filter="New File"><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>New File</span><span data-shortcut>⌘N</span></div>
|
||||
<div role="menuitem" data-filter="New Folder"><svg class="lucide lucide-folder-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 10v6" /><path d="M9 13h6" /><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><span>New Folder</span><span data-shortcut>⇧⌘N</span></div>
|
||||
<div role="menuitem" data-filter="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><span>Copy</span><span data-shortcut>⌘C</span></div>
|
||||
<div role="menuitem" data-filter="Cut"><svg class="lucide lucide-scissors" 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="6" cy="6" r="3" /><path d="M8.12 8.12 12 12" /><path d="M20 4 8.12 15.88" /><circle cx="6" cy="18" r="3" /><path d="M14.8 14.8 20 20" /></svg><span>Cut</span><span data-shortcut>⌘X</span></div>
|
||||
<div role="menuitem" data-filter="Paste"><svg class="lucide lucide-clipboard-paste" 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 14h10" /><path d="M16 4h2a2 2 0 0 1 2 2v1.344" /><path d="m17 18 4-4-4-4" /><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113" /><rect x="8" y="2" width="8" height="4" rx="1" /></svg><span>Paste</span><span data-shortcut>⌘V</span></div>
|
||||
<div role="menuitem" data-filter="Delete"><svg class="lucide lucide-trash" 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 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><span>Delete</span><span data-shortcut>⌫</span></div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-scrollable-view">
|
||||
<span role="heading" id="command-scrollable-view">View</span>
|
||||
<div role="menuitem" data-filter="Grid View"><svg class="lucide lucide-layout-grid" 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="7" height="7" x="3" y="3" rx="1" /><rect width="7" height="7" x="14" y="3" rx="1" /><rect width="7" height="7" x="14" y="14" rx="1" /><rect width="7" height="7" x="3" y="14" rx="1" /></svg><span>Grid View</span></div>
|
||||
<div role="menuitem" data-filter="List View"><svg class="lucide lucide-list" 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 5h.01" /><path d="M3 12h.01" /><path d="M3 19h.01" /><path d="M8 5h13" /><path d="M8 12h13" /><path d="M8 19h13" /></svg><span>List View</span></div>
|
||||
<div role="menuitem" data-filter="Zoom In"><svg class="lucide lucide-zoom-in" 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="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line x1="11" x2="11" y1="8" y2="14" /><line x1="8" x2="14" y1="11" y2="11" /></svg><span>Zoom In</span><span data-shortcut>⌘+</span></div>
|
||||
<div role="menuitem" data-filter="Zoom Out"><svg class="lucide lucide-zoom-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"><circle cx="11" cy="11" r="8" /><line x1="21" x2="16.65" y1="21" y2="16.65" /><line x1="8" x2="14" y1="11" y2="11" /></svg><span>Zoom Out</span><span data-shortcut>⌘-</span></div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-scrollable-account">
|
||||
<span role="heading" id="command-scrollable-account">Account</span>
|
||||
<div role="menuitem" data-filter="Profile"><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"><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"><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 role="menuitem" data-filter="Notifications"><svg class="lucide lucide-bell" 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.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg><span>Notifications</span></div>
|
||||
<div role="menuitem" data-filter="Help & Support"><svg class="lucide lucide-circle-help" 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.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /><path d="M12 17h.01" /></svg><span>Help & Support</span></div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-scrollable-tools">
|
||||
<span role="heading" id="command-scrollable-tools">Tools</span>
|
||||
<div role="menuitem" data-filter="Calculator"><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 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>Calendar</span></div>
|
||||
<div role="menuitem" data-filter="Image Editor"><svg class="lucide lucide-image" 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="18" height="18" x="3" y="3" rx="2" ry="2" /><circle cx="9" cy="9" r="2" /><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" /></svg><span>Image Editor</span></div>
|
||||
<div role="menuitem" data-filter="Code Editor"><svg class="lucide lucide-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="m16 18 6-6-6-6" /><path d="m8 6-6 6 6 6" /></svg><span>Code Editor</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
## RTL
|
||||
|
||||
Add `dir="rtl"` to the command root or an ancestor.
|
||||
|
||||
<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="لم يتم العثور على نتائج.">
|
||||
<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="Search Emoji"><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>البحث عن الرموز التعبيرية</span>
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true" data-filter="Calculator"><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>الآلة الحاسبة</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="command-rtl-settings">
|
||||
<span role="heading" id="command-rtl-settings">الإعدادات</span>
|
||||
<div role="menuitem" data-filter="Profile"><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>الملف الشخصي</span>
|
||||
<span data-shortcut>⌘P</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Billing"><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>
|
||||
<span data-shortcut>⌘B</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>
|
||||
@@ -0,0 +1,258 @@
|
||||
# Dialog
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn" data-variant="outline">Open Dialog</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 class="sm:max-w-sm">
|
||||
<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" data-variant="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" data-variant="ghost" data-size="icon-sm" 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>
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Dialog component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/dialog.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your dialog HTML">
|
||||
|
||||
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" data-variant="outline">Open Dialog</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 class="sm:max-w-sm">
|
||||
<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" data-variant="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" data-variant="ghost" data-size="icon-sm" 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>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><button type="button" onclick="dialog.showModal()"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code><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. Add width utilities to the inner <code><div></code> when a dialog needs a custom size. The macro also adds backdrop-click close handling.
|
||||
<dl>
|
||||
<dt><code><div></code></dt>
|
||||
<dd>
|
||||
Dialog content surface.
|
||||
<dl>
|
||||
<dt><code><header></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>
|
||||
Dialog header.
|
||||
<dl>
|
||||
<dt><code><h2 id="{ TITLE_ID }"></code></dt>
|
||||
<dd>Dialog title. Reference it from <code>aria-labelledby</code>.</dd>
|
||||
<dt><code><p id="{ DESCRIPTION_ID }"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Dialog description. Reference it from <code>aria-describedby</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code><section></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Dialog body/content area. Add overflow utilities when the body should scroll.</dd>
|
||||
<dt><code><footer></code> <span class="badge" data-variant="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><button type="button" onclick="this.closest('dialog').close()"></code> <span class="badge" data-variant="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" data-variant="outline">Share</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 class="sm:max-w-md">
|
||||
<header>
|
||||
<h2 id="dialog-custom-close-title">Share link</h2>
|
||||
<p id="dialog-custom-close-description">Anyone who has this link will be able to view this.</p>
|
||||
</header>
|
||||
<section class="flex items-center gap-2">
|
||||
<div class="grid flex-1 gap-2">
|
||||
<label class="label sr-only" for="dialog-custom-close-link">Link</label>
|
||||
<input class="input" id="dialog-custom-close-link" value="https://basecoatui.com/installation/" readonly />
|
||||
</div>
|
||||
</section>
|
||||
<footer class="sm:justify-start">
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Close</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### No close button
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-no-close').showModal()" class="btn" data-variant="outline">No Close Button</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">This dialog doesn't have a close button in the top-right corner.</p>
|
||||
</header>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Sticky footer
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-sticky-footer').showModal()" class="btn" data-variant="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">This dialog has a sticky footer that stays visible while the content scrolls.</p>
|
||||
</header>
|
||||
<section class="-mx-4 max-h-[50vh] overflow-y-auto px-4 scrollbar">
|
||||
<p class="mb-4 leading-normal">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.</p>
|
||||
<p class="mb-4 leading-normal">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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
||||
<p class="mb-4 leading-normal">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
|
||||
<p class="mb-4 leading-normal">Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
</footer>
|
||||
<button type="button" class="btn" data-variant="ghost" data-size="icon-sm" 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" data-variant="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">This is a dialog with scrollable content.</p>
|
||||
</header>
|
||||
<section class="-mx-4 max-h-[50vh] overflow-y-auto px-4 scrollbar">
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
||||
<p class="mb-4 leading-normal">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
|
||||
<p class="mb-4 leading-normal">Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
||||
<p class="mb-4 leading-normal">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
|
||||
<p class="mb-4 leading-normal">Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<p class="mb-4 leading-normal">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
||||
</section>
|
||||
<button type="button" class="btn" data-variant="ghost" data-size="icon-sm" 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" data-variant="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 class="sm:max-w-sm">
|
||||
<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" name="name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="dialog-rtl-username">اسم المستخدم</label>
|
||||
<input class="input" id="dialog-rtl-username" name="username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">حفظ التغييرات</button>
|
||||
</footer>
|
||||
<button type="button" class="btn" data-variant="ghost" data-size="icon-sm" 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,177 +0,0 @@
|
||||
---
|
||||
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: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Scrollable content
|
||||
id: example-scrollable-content
|
||||
---
|
||||
|
||||
{% 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"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus/>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-username">Username</label>
|
||||
<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>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, class="") }}
|
||||
|
||||
<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 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 <code><dialog></code>.</li>
|
||||
<li><code>aria-describedby="{ DESCRIPTION_ID }"</code>: linked to by the <code>aria-describedby</code> attribute of the <code><dialog></code>.</li>
|
||||
<li><code>onclick</code>: closes the dialog when the backdrop is clicked.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><article></code></dt>
|
||||
<dd>Wraps the content of the dialog. It includes an <code>onclick</code> attribute to prevent from closing the dialog if we have an <code>onclick</code> event on <code><dialog></code>.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the header for the dialog:
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code></dt>
|
||||
<dd>The title of the 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 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>
|
||||
<dt><code class="highlight language-html"><form method="dialog"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>A form that contains a single button to close the dialog.</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="/installation/#install-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 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"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<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-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 %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-scrollable-content", code | prettyHtml) }}
|
||||
@@ -0,0 +1,510 @@
|
||||
# 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" data-variant="outline">Open</button>
|
||||
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="w-40">
|
||||
<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">
|
||||
<span>Profile</span>
|
||||
<kbd>⇧⌘P</kbd>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<span>Billing</span>
|
||||
<kbd>⌘B</kbd>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<span>Settings</span>
|
||||
<kbd>⌘S</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group">
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem">
|
||||
<span>New Team</span>
|
||||
<kbd>⌘+T</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group">
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group">
|
||||
<div role="menuitem">
|
||||
<span>Log out</span>
|
||||
<kbd>⇧⌘Q</kbd>
|
||||
</div>
|
||||
</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 CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Dropdown Menu component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/dropdown-menu.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Include JavaScript">
|
||||
|
||||
Copy or serve the full Basecoat JavaScript bundle.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/all.min.js" defer></script>
|
||||
```
|
||||
|
||||
Or copy or serve the Basecoat runtime and Dropdown Menu script.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/basecoat.min.js" defer></script>
|
||||
<script src="/assets/js/dropdown-menu.min.js" defer></script>
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</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" data-variant="outline">Open</button>
|
||||
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="w-40">
|
||||
<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">
|
||||
<span>Profile</span>
|
||||
<kbd>⇧⌘P</kbd>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<span>Billing</span>
|
||||
<kbd>⌘B</kbd>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<span>Settings</span>
|
||||
<kbd>⌘S</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group">
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem">
|
||||
<span>New Team</span>
|
||||
<kbd>⌘+T</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group">
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group">
|
||||
<div role="menuitem">
|
||||
<span>Log out</span>
|
||||
<kbd>⇧⌘Q</kbd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><div class="dropdown-menu"></code></dt>
|
||||
<dd>
|
||||
Relative wrapper for the trigger and inline menu content.
|
||||
<dl>
|
||||
<dt><code><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><div data-popover aria-hidden="true"></code></dt>
|
||||
<dd>
|
||||
Menu content popover. Set <code>data-side="top|right|bottom|left|inline-start|inline-end"</code> and <code>data-align="start|center|end"</code> to control placement.
|
||||
<dl>
|
||||
<dt><code><div role="menu"></code></dt>
|
||||
<dd>
|
||||
Container for menu items, groups, labels, and separators.
|
||||
<dl>
|
||||
<dt><code><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Groups related menu items.</dd>
|
||||
<dt><code><div role="heading" id="{ HEADING_ID }"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Group heading/label.</dd>
|
||||
<dt><code><div role="menuitem"></code></dt>
|
||||
<dd>Standard action item. Use <code>aria-disabled="true"</code> for disabled items.</dd>
|
||||
<dt><code><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><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><kbd></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code><hr role="separator"></code> <span class="badge" data-variant="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" data-variant="outline">Open</button>
|
||||
<div id="dropdown-basic-popover" data-popover aria-hidden="true" class="min-w-32">
|
||||
<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">GitHub</div>
|
||||
<div role="menuitem">Support</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" data-variant="outline">Open</button>
|
||||
<div id="dropdown-shortcuts-popover" data-popover aria-hidden="true" class="min-w-46">
|
||||
<div role="menu" id="dropdown-shortcuts-menu" aria-labelledby="dropdown-shortcuts-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-shortcuts-account">
|
||||
<div role="heading" id="dropdown-shortcuts-account">My Account</div>
|
||||
<div role="menuitem">
|
||||
<span>Profile</span>
|
||||
<kbd>⇧⌘P</kbd>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<span>Billing</span>
|
||||
<kbd>⌘B</kbd>
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
<span>Settings</span>
|
||||
<kbd>⌘S</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="menuitem">
|
||||
<span>Log out</span>
|
||||
<kbd>⇧⌘Q</kbd>
|
||||
</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" data-variant="outline">Open</button>
|
||||
<div id="dropdown-icons-popover" data-popover aria-hidden="true" class="min-w-32">
|
||||
<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" data-variant="destructive"><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" data-variant="outline">Open</button>
|
||||
<div id="dropdown-checkboxes-popover" data-popover aria-hidden="true" class="w-40">
|
||||
<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
|
||||
</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>
|
||||
Activity Bar
|
||||
</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>
|
||||
Panel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Checkboxes Icons
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-checkboxes-icons" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-checkboxes-icons-trigger" aria-haspopup="menu" aria-controls="dropdown-checkboxes-icons-menu" aria-expanded="false" class="btn" data-variant="outline">Notifications</button>
|
||||
<div id="dropdown-checkboxes-icons-popover" data-popover aria-hidden="true" class="w-48">
|
||||
<div role="menu" id="dropdown-checkboxes-icons-menu" aria-labelledby="dropdown-checkboxes-icons-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-checkboxes-icons-label">
|
||||
<div role="heading" id="dropdown-checkboxes-icons-label">Notification Preferences</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>
|
||||
<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>
|
||||
Email notifications
|
||||
</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>
|
||||
<svg class="lucide lucide-message-square" 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 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>
|
||||
SMS notifications
|
||||
</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>
|
||||
<svg class="lucide lucide-bell" 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.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg>
|
||||
Push notifications
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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" data-variant="outline">Open</button>
|
||||
<div id="dropdown-radio-group-popover" data-popover aria-hidden="true" class="min-w-32">
|
||||
<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>
|
||||
</Preview>
|
||||
|
||||
### Radio Icons
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-radio-icons" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-radio-icons-trigger" aria-haspopup="menu" aria-controls="dropdown-radio-icons-menu" aria-expanded="false" class="btn" data-variant="outline">Payment Method</button>
|
||||
<div id="dropdown-radio-icons-popover" data-popover aria-hidden="true" class="min-w-56">
|
||||
<div role="menu" id="dropdown-radio-icons-menu" aria-labelledby="dropdown-radio-icons-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-radio-icons-label">
|
||||
<div role="heading" id="dropdown-radio-icons-label">Select Payment Method</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>
|
||||
<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>
|
||||
Credit Card
|
||||
</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>
|
||||
<svg class="lucide lucide-wallet" 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 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" /><path d="M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" /></svg>
|
||||
PayPal
|
||||
</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>
|
||||
<svg class="lucide lucide-building-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="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z" /><path d="M6 12H4a2 2 0 0 0-2 2v8h20v-8a2 2 0 0 0-2-2h-2" /><path d="M10 6h4" /><path d="M10 10h4" /><path d="M10 14h4" /><path d="M10 18h4" /></svg>
|
||||
Bank Transfer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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" data-variant="outline">Actions</button>
|
||||
<div id="dropdown-destructive-popover" data-popover aria-hidden="true" class="min-w-32">
|
||||
<div role="menu" id="dropdown-destructive-menu" aria-labelledby="dropdown-destructive-trigger">
|
||||
<div role="group">
|
||||
<div role="menuitem"><svg class="lucide lucide-pencil" 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 20h9" /><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z" /></svg>
|
||||
Edit
|
||||
</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="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /><polyline points="16 6 12 2 8 6" /><line x1="12" x2="12" y1="2" y2="15" /></svg>
|
||||
Share
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group">
|
||||
<div role="menuitem" data-variant="destructive"><svg class="lucide lucide-trash" 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 6h18" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /></svg>
|
||||
Delete
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
|
||||
### Avatar
|
||||
|
||||
<Preview>
|
||||
<div id="dropdown-avatar" class="dropdown-menu">
|
||||
<button type="button" id="dropdown-avatar-trigger" aria-haspopup="menu" aria-controls="dropdown-avatar-menu" aria-expanded="false" class="btn rounded-full" data-variant="ghost" data-size="icon">
|
||||
<span class="avatar">
|
||||
<img src="https://github.com/shadcn.png" alt="shadcn" />
|
||||
<span>LR</span>
|
||||
</span>
|
||||
</button>
|
||||
<div id="dropdown-avatar-popover" data-popover aria-hidden="true" data-align="end">
|
||||
<div role="menu" id="dropdown-avatar-menu" aria-labelledby="dropdown-avatar-trigger">
|
||||
<div role="group">
|
||||
<div role="menuitem"><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>
|
||||
Account
|
||||
</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-bell" 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.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg>
|
||||
Notifications
|
||||
</div>
|
||||
</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>
|
||||
Sign Out
|
||||
</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" data-variant="outline">افتح القائمة</button>
|
||||
<div id="dropdown-rtl-popover" data-popover aria-hidden="true" class="w-36" data-align="end">
|
||||
<div role="menu" id="dropdown-rtl-menu" aria-labelledby="dropdown-rtl-trigger">
|
||||
<div role="group" aria-labelledby="dropdown-rtl-team">
|
||||
<div role="heading" id="dropdown-rtl-team">الفريق</div>
|
||||
<div role="menuitem">الفريق</div>
|
||||
<div role="menuitem">
|
||||
<span>فريق جديد</span>
|
||||
<kbd>⌘+T</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="dropdown-rtl-view">
|
||||
<div role="heading" id="dropdown-rtl-view">عرض</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>
|
||||
شريط الحالة
|
||||
</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>
|
||||
شريط النشاط
|
||||
</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>
|
||||
اللوحة
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group" aria-labelledby="dropdown-rtl-position">
|
||||
<div role="heading" id="dropdown-rtl-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>
|
||||
أعلى
|
||||
</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>
|
||||
أسفل
|
||||
</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>
|
||||
يمين
|
||||
</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>
|
||||
يسار
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<div role="group">
|
||||
<div role="menuitem" data-variant="destructive">تسجيل الخروج</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -1,402 +0,0 @@
|
||||
---
|
||||
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 files"
|
||||
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 events
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Checkboxes
|
||||
id: example-checkboxes
|
||||
- label: Radio Group
|
||||
id: example-radio-group
|
||||
---
|
||||
|
||||
{% 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="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 %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage">Usage</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/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="/installation/#install-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>Wraps around the entire component.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" popovertarget="{ POPOVER_ID }"></code></dt>
|
||||
<dd>
|
||||
<p>The trigger to open the popover, can also have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>id="{BUTTON_ID}"</code>: linked to by the <code>aria-labelledby</code> attribute of the listbox.</li>
|
||||
<li><code>aria-haspopup="menu"</code>: indicates that the button opens a menu.</li>
|
||||
<li><code>aria-controls="{ MENU_ID }"</code>: points to the menu's id.</li>
|
||||
<li><code>aria-expanded="false"</code>: tracks the popover's state.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div popover class="popover" id="{ POPOVER_ID }"></code></dt>
|
||||
<dd>As with the <a href="/components/popover">Popover</a> component, you can set up the side and alignment of the popover using the <code>data-side</code> and <code>data-align</code> attributes.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="menu"></code></dt>
|
||||
<dd>The menu containing the options. Should have the following attributes:
|
||||
<ul>
|
||||
<li><code>id="{ MENU_ID }"</code>: refered to by the <code>aria-controls</code> attribute of the trigger.</li>
|
||||
<li><code>aria-labelledby="{ BUTTON_ID }"</code>: linked to by the button's <code>id</code> attribute.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button role="menuitem"></code></dt>
|
||||
<dd>Menu item.</dd>
|
||||
<dt><code class="highlight language-html"><button role="menuitemcheckbox"></code></dt>
|
||||
<dd>Menu item with a checkbox.</dd>
|
||||
<dt><code class="highlight language-html"><button role="menuitemradio"></code></dt>
|
||||
<dd>Menu item with a radio button.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/options.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group of options, can have a <code>aria-labelledby</code> attribute to link to a heading.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading"></code></dt>
|
||||
<dd>Group heading, must have an <code>id</code> attribute if you use the <code>aria-labelledby</code> attribute on the group.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
</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>Once the component is fully initialized, it dispatches a custom (non-bubbling) <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</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="/installation/#install-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
|
||||
<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" disabled>
|
||||
API
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-checkboxes"><a href="#example-checkboxes">Checkboxes</a></h3>
|
||||
|
||||
{% set code %}{% call dropdown_menu(
|
||||
id="dropdown-menu-checkboxes",
|
||||
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">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>
|
||||
{% 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-menu-radio-group",
|
||||
trigger="Open",
|
||||
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>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu", code | prettyHtml) }}
|
||||
@@ -0,0 +1,210 @@
|
||||
# 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" data-variant="outline">Import Project</button>
|
||||
</div>
|
||||
<a href="#" class="btn text-muted-foreground" data-variant="link" data-size="sm">
|
||||
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
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Empty component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/empty.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your empty HTML">
|
||||
|
||||
Add `class="empty"` to 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" data-variant="outline">Import Project</button>
|
||||
</div>
|
||||
<a href="#" class="btn text-muted-foreground" data-variant="link" data-size="sm">
|
||||
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>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
#### HTML structure
|
||||
|
||||
<dl>
|
||||
<dt><code><section class="empty"></code></dt>
|
||||
<dd>
|
||||
The empty-state container.
|
||||
<dl>
|
||||
<dt><code><header></code></dt>
|
||||
<dd>
|
||||
Groups the media, title, and description.
|
||||
<dl>
|
||||
<dt><code><figure></code> <span class="badge" data-variant="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><h2></code>, <code><h3></code>, or <code><h4></code></dt>
|
||||
<dd>The empty-state title.</dd>
|
||||
<dt><code><p></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>The empty-state description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code><section></code> or <code><footer></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Actions and supporting content.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Outline
|
||||
|
||||
<Preview class="w-full">
|
||||
<section class="empty border border-dashed">
|
||||
<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" data-variant="outline" data-size="sm">Upload Files</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Background
|
||||
|
||||
<Preview class="w-full">
|
||||
<section class="empty h-full bg-muted/30">
|
||||
<header>
|
||||
<figure><svg class="lucide lucide-bell" 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.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg></figure>
|
||||
<h3>No Notifications</h3>
|
||||
<p class="max-w-xs text-pretty">You're all caught up. New notifications will appear here.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn" data-variant="outline"><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>
|
||||
Refresh
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</Preview>
|
||||
|
||||
### Avatar
|
||||
|
||||
<Preview>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="size-12 rounded-full">
|
||||
<img class="grayscale" alt="@shadcn" src="https://github.com/shadcn.png" />
|
||||
</figure>
|
||||
<h3>User Offline</h3>
|
||||
<p>This user is currently offline. You can leave a message to notify them or try again later.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn" data-size="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="@shadcn" src="https://github.com/shadcn.png" />
|
||||
<img alt="@maxleiter" src="https://github.com/maxleiter.png" />
|
||||
<img alt="@evilrabbit" src="https://github.com/evilrabbit.png" />
|
||||
</figure>
|
||||
<h3>No Team Members</h3>
|
||||
<p>Invite your team to collaborate on this project.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn" data-size="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>
|
||||
<span>Invite Members</span>
|
||||
</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 sm:w-3/4">
|
||||
<input type="search" placeholder="Try searching for pages..." />
|
||||
<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">/</kbd></span>
|
||||
</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>
|
||||
<div class="flex flex-row justify-center gap-2">
|
||||
<button class="btn">إنشاء مشروع</button>
|
||||
<button class="btn" data-variant="outline">استيراد مشروع</button>
|
||||
</div>
|
||||
<a href="#" class="btn text-muted-foreground" data-variant="link" data-size="sm">
|
||||
تعرف على المزيد
|
||||
<svg class="lucide lucide-arrow-up-right rtl:rotate-270" data-icon="inline-end" 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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user