mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-12 13:18:22 +00:00
Migrate empty and item docs to Basecoat APIs
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
Basecoat is a vanilla JavaScript/HTML/CSS port of shadcn/ui components. It reuses shadcn/ui's design patterns and Tailwind CSS classes while maintaining semantic HTML, native browser elements, and full accessibility (ARIA attributes, proper hierarchies, keyboard navigation). The goal is to provide shadcn-quality components without React dependencies.
|
||||
|
||||
Basecoat is not a literal DOM port of shadcn/ui. It is an alternative implementation with the same visual language and comparable component coverage, but with simpler markup, less JavaScript, less CSS, and no React/runtime primitive dependency.
|
||||
|
||||
## Quickstart
|
||||
|
||||
- Install deps: `npm i`
|
||||
@@ -16,7 +18,7 @@ Basecoat is a vanilla JavaScript/HTML/CSS port of shadcn/ui components. It reuse
|
||||
- Edit sources only:
|
||||
- `src/css/base/base.css` – Shared base layer, tokens, and semantic utility classes
|
||||
- `src/css/components/*.css` – Component CSS classes (Tailwind + custom)
|
||||
- `src/css/styles/style-*.css` – Style-pack overrides (`vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea`)
|
||||
- `src/css/styles/style-*.css` – Style-pack visual rules (`vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea`)
|
||||
- `src/css/basecoat.css` – Aggregate CSS entrypoint
|
||||
- `src/js/*.js` – Individual component JS files (kebab-case, ESM) + `basecoat.js` (component registry)
|
||||
- `src/nunjucks/*.njk` and `src/jinja/*.html.jinja` – Component template macros
|
||||
@@ -47,14 +49,155 @@ Basecoat is a vanilla JavaScript/HTML/CSS port of shadcn/ui components. It reuse
|
||||
- `src/css/styles/style-luma.css`
|
||||
- `src/css/styles/style-sera.css`
|
||||
- `src/css/styles/style-rhea.css`
|
||||
- Default aggregate and CDN behavior should resolve to Vega unless explicitly changed.
|
||||
- Non-default styles should be selected by importing the relevant style file or using a style-specific bundled asset, not by introducing shadcn internal selector conventions.
|
||||
- `basecoat.css` and the default CDN bundle remain Vega-compatible for Basecoat backward compatibility. This is a Basecoat compatibility decision; upstream shadcn/ui's current default generated config is Nova.
|
||||
- Non-default styles must be selected through standalone style bundles (`basecoat-<style>.css`, `basecoat-<style>.cdn.css`, or docs `styles-<style>.css`). Do not load Vega/default first and then layer another style file on top.
|
||||
- Split style files under `src/css/styles/style-*.css` are for source organization and advanced composition only. They must map upstream style intent directly and should not contain defensive resets whose only purpose is undoing another style pack.
|
||||
- During migration, each component must be reviewed in two layers:
|
||||
- shared/base component CSS
|
||||
- style-pack CSS overrides
|
||||
- style-pack CSS visual rules
|
||||
- Do not assume existing Basecoat component CSS is still the right baseline. Reassess shared rules vs style-owned rules component by component.
|
||||
- When Basecoat differs from upstream shadcn/ui, classify the difference as either intentional or drift, and prefer explicit decisions over silent divergence.
|
||||
|
||||
## Implementation philosophy
|
||||
|
||||
- Prefer the browser platform:
|
||||
- native elements before custom widgets
|
||||
- semantic HTML before generic `<div>` structures
|
||||
- CSS state selectors before JavaScript state when reliable
|
||||
- small vanilla JS behavior only when native HTML/CSS cannot provide the interaction
|
||||
- Keep public markup small and obvious. A user should be able to read a component example and understand the DOM without learning shadcn/ui internals.
|
||||
- Use the least JavaScript that preserves expected behavior, accessibility, and keyboard support.
|
||||
- Use the least CSS that preserves the visual contract and style-pack differences.
|
||||
- Use the least markup that remains semantic and accessible.
|
||||
- Prefer a single public class on the component root. Avoid requiring child classes when semantic elements, native controls, ARIA roles, or documented attributes can identify the child.
|
||||
- Child selectors should usually target meaningful HTML (`input`, `textarea`, `select`, `button`, `kbd`, `svg`, `header`, `footer`, `label`) or accessibility semantics (`role`, `aria-*`) rather than invented slot classes.
|
||||
- Do not create classes or DOM structures just because shadcn/ui has internal slots. Map upstream intent onto Basecoat's existing selectors first.
|
||||
- If a new public class, wrapper, `data-*` attribute, or macro argument is needed, treat it as API design. Make it explicit and document it.
|
||||
- Prefer direct semantic selectors where Basecoat already has a semantic structure:
|
||||
- field labels: `.field > label`, not invented `.field-label`
|
||||
- field groups: semantic `fieldset`, `[role='group']`, or `[role='radiogroup']`, not arbitrary wrappers unless documented
|
||||
- native inputs/selects/textarea should remain actual native controls where possible
|
||||
- Avoid purely decorative components when composition is enough. Document patterns instead of adding API surface.
|
||||
- Keep generated or copied shadcn/ui implementation details out of Basecoat:
|
||||
- no `cn-*` classes
|
||||
- no `data-slot` dependency unless Basecoat explicitly owns that API
|
||||
- no React/Base UI/Radix-specific DOM assumptions
|
||||
- Prefer intentional divergence over awkward imitation. If upstream behavior does not map cleanly to native markup, document the Basecoat behavior and why.
|
||||
|
||||
## Markup and accessibility rules
|
||||
|
||||
- Start from the semantic element that matches the control:
|
||||
- button action: `<button>`
|
||||
- navigation/action link: `<a>`
|
||||
- text input: `<input>`
|
||||
- multiline input: `<textarea>`
|
||||
- native select: `<select>`
|
||||
- grouped form controls: `<fieldset>` + `<legend>` when appropriate
|
||||
- dialog: native `<dialog>` when possible
|
||||
- Add ARIA only when native semantics are insufficient or a custom interaction needs it.
|
||||
- ARIA must reflect behavior, not styling. Do not add roles just to target CSS.
|
||||
- Disabled items must be excluded from keyboard and pointer interactions where custom JS is involved.
|
||||
- For composite widgets, keep keyboard behavior predictable:
|
||||
- roving/highlight state should skip disabled items
|
||||
- Escape closes popovers/dialogs where expected
|
||||
- Arrow keys should match orientation
|
||||
- Enter/Space behavior should match the control role
|
||||
- RTL support should use logical properties/utilities by default. Use explicit `:dir(rtl)` only when logical utilities cannot express the behavior.
|
||||
- Directional icons are not automatically flipped. Flip only icons whose meaning is directional.
|
||||
|
||||
## CSS architecture rules
|
||||
|
||||
- Shared component CSS in `src/css/components/*.css` should own structure, layout primitives, accessibility selectors, and behavior hooks.
|
||||
- Style-pack CSS in `src/css/styles/style-*.css` should own visual choices:
|
||||
- color
|
||||
- radius
|
||||
- shadow/ring
|
||||
- typography
|
||||
- spacing that is style-specific
|
||||
- variant visuals
|
||||
- state visuals that differ by style
|
||||
- Do not put style-pack defaults in component CSS and then override them later. Each style bundle must stand on its own.
|
||||
- Avoid defensive resets that exist only because another style was loaded first. Non-default style bundles should not depend on Vega/default being loaded.
|
||||
- Use Tailwind logical utilities (`ps`, `pe`, `ms`, `me`, `start`, `end`, `rounded-s`, `rounded-e`) instead of physical left/right when possible.
|
||||
- Prefer compact selectors with `:is()` and `:has()`, but only when they keep the public API clearer.
|
||||
- Use `border` for separators and lines, especially `<hr>`/separator elements. Avoid fake separator backgrounds unless upstream behavior requires it and markup supports it.
|
||||
- Keep visual rules close to the component style block. Do not scatter one component's style-pack rules across unrelated sections.
|
||||
- When using pseudo-elements for native controls, keep the native control accessible and form-submittable.
|
||||
- Avoid `!important`. Use it only when matching upstream behavior or browser-native styling makes it unavoidable, and keep the selector narrow.
|
||||
|
||||
## JavaScript rules
|
||||
|
||||
- JavaScript files are behavior only. Do not use JS to paper over CSS architecture issues.
|
||||
- Every JS component must register with `window.basecoat.register()` and be safe to initialize multiple times.
|
||||
- Use `data-*-initialized` flags for idempotence.
|
||||
- Emit custom events for meaningful user actions when useful for integration.
|
||||
- Keep generated DOM minimal and consistent with the documented HTML structure.
|
||||
- Do not couple unrelated components through hidden assumptions. If Select and Combobox share behavior, factor it deliberately or keep their files separate when the UX differs.
|
||||
- Prefer native browser APIs:
|
||||
- native `<dialog>` before custom modal stacks
|
||||
- native form controls before custom controls
|
||||
- native events before synthetic abstractions
|
||||
- If JS is needed only to sync visual state, keep it small and local. Example: `range.js` only syncs `--slider-value`.
|
||||
|
||||
## Basecoat vs upstream shadcn/ui
|
||||
|
||||
- Always check upstream before changing a migrated component:
|
||||
- component source under `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/`
|
||||
- docs under `../_sandbox/shadcn-ui/apps/v4/content/docs/components/`
|
||||
- examples under `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/examples/`
|
||||
- all eight style packs under `../_sandbox/shadcn-ui/apps/v4/registry/styles/`
|
||||
- Map upstream concepts, not upstream implementation details.
|
||||
- When upstream uses composed React subcomponents, decide whether Basecoat needs:
|
||||
- one semantic element
|
||||
- a small documented structure
|
||||
- a macro helper
|
||||
- a JS component
|
||||
- only docs, with no new component
|
||||
- When upstream supports behavior that does not map to Basecoat's native implementation, document the gap instead of faking it.
|
||||
- Example: Basecoat Slider uses native single-thumb range inputs; upstream Slider supports multi-thumb and vertical orientation.
|
||||
- Example: Basecoat Tooltip is CSS-only text content; upstream Tooltip supports portalled arbitrary content.
|
||||
- Example: Basecoat Select/Combobox popovers remain inline-positioned rather than matching every portalled positioning behavior.
|
||||
- Style parity matters, but not at the cost of worse Basecoat markup.
|
||||
- If a discrepancy is found, classify it:
|
||||
- `drift fixed`: Basecoat was simply out of date
|
||||
- `intentional`: Basecoat differs because of semantic/native/vanilla constraints
|
||||
- `deferred`: valid upstream feature, not implemented yet
|
||||
|
||||
## Documentation rules
|
||||
|
||||
- Component docs are part of the component API. Update docs whenever CSS, JS, templates, variants, supported states, or supported markup changes.
|
||||
- Docs should be Basecoat docs, not copied React docs. Do not include useless React-specific API tables, `asChild`, or composition diagrams that do not match Basecoat markup.
|
||||
- Prefer concrete HTML examples over abstract component trees.
|
||||
- Each component page should generally include:
|
||||
- frontmatter title/description/toc
|
||||
- a top preview using the most common/default example
|
||||
- `Usage`
|
||||
- install/include notes for JS components
|
||||
- a minimal HTML example
|
||||
- `HTML structure` or class/API notes when the markup is not self-evident
|
||||
- examples aligned with upstream where they apply
|
||||
- Basecoat-specific differences and unsupported upstream features
|
||||
- RTL example or an explicit note if RTL is deferred
|
||||
- For JS components, include:
|
||||
- required script tags (`basecoat.min.js` plus component file, unless included in all bundle)
|
||||
- initialization expectations
|
||||
- custom events when relevant
|
||||
- config object shape when relevant
|
||||
- macro links if Nunjucks/Jinja macros exist
|
||||
- For CSS-only/native components, do not invent an API Reference section unless there is a real Basecoat API to document.
|
||||
- Do not add "Composition" sections unless the component truly has a stable Basecoat composition pattern. If the section only mirrors shadcn/ui React internals, remove it.
|
||||
- Examples should be realistic and named clearly:
|
||||
- `Default`
|
||||
- `Disabled`
|
||||
- `Invalid`
|
||||
- `Groups`
|
||||
- `Scrollable`
|
||||
- `RTL`
|
||||
- style/variant/size examples when applicable
|
||||
- If Basecoat intentionally differs from upstream, say it near the relevant usage/example section.
|
||||
- Keep snippets minimal. Avoid giant copied examples unless the structure is genuinely important.
|
||||
- For deprecated or removed APIs, update the page and `CHANGELOG.md`/migration notes rather than leaving stale examples.
|
||||
|
||||
## Component migration workflow
|
||||
|
||||
For each component migration, follow this sequence:
|
||||
@@ -65,7 +208,15 @@ For each component migration, follow this sequence:
|
||||
4. Decide what belongs in shared Basecoat component CSS versus what belongs in style-pack CSS.
|
||||
5. Keep Basecoat selectors and markup. Do not port shadcn `cn-*` selectors directly.
|
||||
6. Remove outdated Basecoat visual rules from the shared component CSS if those rules are now style-owned.
|
||||
7. Implement or update the component section in:
|
||||
7. Audit RTL support:
|
||||
- check upstream `*-rtl` examples and generated `ui-rtl` component code when present
|
||||
- prefer logical utilities/properties (`ps`, `pe`, `rounded-s`, `rounded-e`, `border-s`, `start`, `end`) over duplicated `:dir(rtl)` branches
|
||||
- flip only directional icons explicitly; do not auto-flip all icons
|
||||
8. Audit standalone style behavior:
|
||||
- verify each style in its own full bundle, not layered over another style pack
|
||||
- compare state selectors (`focus-visible`, `aria-invalid`, `hover`, open/selected states) against upstream for that specific style
|
||||
- avoid `!important` unless upstream behavior or Basecoat markup truly requires it
|
||||
9. Implement or update the component section in:
|
||||
- `src/css/components/<component>.css`
|
||||
- `src/css/styles/style-vega.css`
|
||||
- `src/css/styles/style-nova.css`
|
||||
@@ -75,15 +226,24 @@ For each component migration, follow this sequence:
|
||||
- `src/css/styles/style-luma.css`
|
||||
- `src/css/styles/style-sera.css`
|
||||
- `src/css/styles/style-rhea.css`
|
||||
8. Document any non-trivial difference from upstream as either:
|
||||
10. Document any non-trivial difference from upstream as either:
|
||||
- intentional
|
||||
- drift fixed
|
||||
- deferred
|
||||
9. Update the migration tracker entry for the component.
|
||||
10. Run `npm run build` after each component migration.
|
||||
11. Manually verify the component in docs for:
|
||||
11. Update the migration tracker entry for the component.
|
||||
12. Review and align the component docs against the current upstream docs page:
|
||||
- include equivalent examples where they apply
|
||||
- rename labels/anchors to match upstream where reasonable
|
||||
- document Basecoat-specific differences and extras
|
||||
- add or update the Basecoat class/API reference
|
||||
- call out unsupported or deferred upstream features explicitly
|
||||
- include an RTL section when supported, or document RTL as deferred
|
||||
13. Run `npm run build` after each component migration.
|
||||
14. Run `npm run docs:build` when docs or docs style assets change.
|
||||
15. Manually verify the component in docs for:
|
||||
- light and dark mode
|
||||
- all supported variants and sizes
|
||||
- RTL with `dir="rtl"` when supported
|
||||
- interaction states (`hover`, `focus`, `disabled`, `aria-invalid`, open/selected states when relevant)
|
||||
|
||||
Per-component file checklist:
|
||||
@@ -92,8 +252,9 @@ Per-component file checklist:
|
||||
2. Basecoat templates/macros if relevant: `src/nunjucks/*.njk`, `src/jinja/*.html.jinja`
|
||||
3. Basecoat docs page: `docs/src/components/<component>.njk`
|
||||
4. Upstream base component: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/<component>.tsx`
|
||||
5. Upstream styles: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
6. Basecoat styles: `src/css/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
5. Upstream docs page: `../_sandbox/shadcn-ui/apps/v4/content/docs/components/{base,radix}/<component>.mdx`
|
||||
6. Upstream styles: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
7. Basecoat styles: `src/css/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -130,7 +291,8 @@ Before creating a component, ask:
|
||||
|
||||
Examples:
|
||||
- Field/Fieldset: CSS-only patterns (no JS component)
|
||||
- Empty/Item/Input Group: Pure composition (docs only)
|
||||
- Empty/Item: Pure composition unless a future pass proves a root class is needed
|
||||
- Input Group: Lightweight CSS component with a single `.input-group` root class, semantic children, optional `data-align`, and `data-control` for custom controls
|
||||
- Command/Select: JS behavior required (full component)
|
||||
|
||||
## Common tasks
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Removed the `.form` convenience selector for Basecoat 1.0. Use explicit component classes (`label`, `input`, `textarea`, `select`) or compose fields with `.field` / `.fieldset`.
|
||||
- To keep the previous 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; }
|
||||
```
|
||||
+17
-1
@@ -12,4 +12,20 @@
|
||||
- Test locally before submitting.
|
||||
- Follow existing code style.
|
||||
|
||||
Thanks for helping!
|
||||
## 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!
|
||||
|
||||
+18
-1
@@ -1,3 +1,10 @@
|
||||
/* Basecoat docs font override. This is intentionally not part of the package default. */
|
||||
:root {
|
||||
--font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-heading: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
/* HTMX */
|
||||
.htmx-indicator {
|
||||
@apply invisible opacity-0 transition-discrete transition-all;
|
||||
@@ -11,6 +18,16 @@
|
||||
}
|
||||
|
||||
.content {
|
||||
pre {
|
||||
@apply grid max-h-[650px] overflow-y-auto rounded-lg bg-muted/40 text-sm;
|
||||
}
|
||||
pre > code {
|
||||
@apply block rounded-[inherit] bg-transparent p-3.5 font-mono;
|
||||
}
|
||||
pre > code.hljs {
|
||||
@apply !bg-transparent;
|
||||
}
|
||||
|
||||
> h2 {
|
||||
@apply mt-12 lg:mt-20 mb-6 scroll-m-22 text-2xl font-semibold tracking-tight first:mt-0;
|
||||
}
|
||||
@@ -89,4 +106,4 @@
|
||||
@apply max-md:ml-10 min-h-8 flex items-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "../../src/css/base/base.css";
|
||||
@import "../../src/css/styles/style-luma.css";
|
||||
@@ -1,3 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "../../src/css/base/base.css";
|
||||
@import "../../src/css/styles/style-lyra.css";
|
||||
@@ -1,3 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "../../src/css/base/base.css";
|
||||
@import "../../src/css/styles/style-maia.css";
|
||||
@@ -1,3 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "../../src/css/base/base.css";
|
||||
@import "../../src/css/styles/style-mira.css";
|
||||
@@ -1,3 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "../../src/css/base/base.css";
|
||||
@import "../../src/css/styles/style-nova.css";
|
||||
@@ -1,3 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "../../src/css/base/base.css";
|
||||
@import "../../src/css/styles/style-rhea.css";
|
||||
@@ -1,3 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "../../src/css/base/base.css";
|
||||
@import "../../src/css/styles/style-sera.css";
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-luma.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-lyra.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-maia.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-mira.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-nova.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-rhea.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-sera.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat-vega.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat.css";
|
||||
@import "../../src/css/basecoat-vega.css";
|
||||
@import "./custom.css";
|
||||
@import "./highlight.css";
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@import "./themes/supabase.css";
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<script>
|
||||
(function() {
|
||||
try {
|
||||
const storedStyle = localStorage.getItem('styleVariant') || 'vega';
|
||||
const storedStyle = localStorage.getItem('styleVariant') || 'nova';
|
||||
document.documentElement.dataset.styleVariant = storedStyle;
|
||||
} catch (event) {
|
||||
console.error('Could not apply style variant from localStorage', event);
|
||||
@@ -78,24 +78,21 @@
|
||||
<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">
|
||||
<link rel="stylesheet" id="style-variant-stylesheet">
|
||||
<link rel="stylesheet" id="style-variant-stylesheet" href="/assets/styles-nova.css">
|
||||
<script>
|
||||
(() => {
|
||||
const styleLink = document.getElementById('style-variant-stylesheet');
|
||||
const styleVariant = document.documentElement.dataset.styleVariant || 'vega';
|
||||
if (styleVariant === 'vega') {
|
||||
styleLink.removeAttribute('href');
|
||||
} else {
|
||||
styleLink.href = `/assets/style-${styleVariant}.css`;
|
||||
}
|
||||
const styleVariant = document.documentElement.dataset.styleVariant || 'nova';
|
||||
styleLink.href = `/assets/styles-${styleVariant}.css`;
|
||||
})();
|
||||
</script>
|
||||
{# JS #}
|
||||
<script src="/assets/js/basecoat.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/command.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/combobox.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/dropdown-menu.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/popover.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/range.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/select.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/sidebar.js?v={{ pkg.version }}" defer></script>
|
||||
<script src="/assets/js/tabs.js?v={{ pkg.version }}" defer></script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% 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/40 !p-3.5">{{- code | escape -}}</code></pre>
|
||||
<pre class="scrollbar"><code class="language-{{ language }}">{{- code | escape -}}</code></pre>
|
||||
|
||||
<button
|
||||
onclick="
|
||||
@@ -20,4 +20,4 @@
|
||||
{% lucide "check", {"class": "hidden group-[.copied]:block" } %}
|
||||
</button>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -56,21 +56,17 @@
|
||||
const styleSelect = document.getElementById('style-select');
|
||||
const styleLink = document.getElementById('style-variant-stylesheet');
|
||||
const applyStyle = (styleName) => {
|
||||
const nextStyle = styleName || 'vega';
|
||||
const nextStyle = styleName || 'nova';
|
||||
document.documentElement.dataset.styleVariant = nextStyle;
|
||||
if (nextStyle === 'vega') {
|
||||
styleLink.removeAttribute('href');
|
||||
} else {
|
||||
styleLink.href = `/assets/style-${nextStyle}.css`;
|
||||
}
|
||||
styleLink.href = `/assets/styles-${nextStyle}.css`;
|
||||
};
|
||||
|
||||
const storedStyle = localStorage.getItem('styleVariant') || 'vega';
|
||||
const storedStyle = localStorage.getItem('styleVariant') || 'nova';
|
||||
if (styleSelect) styleSelect.value = storedStyle;
|
||||
applyStyle(storedStyle);
|
||||
|
||||
styleSelect.addEventListener('change', () => {
|
||||
const newStyle = styleSelect.value || 'vega';
|
||||
const newStyle = styleSelect.value || 'nova';
|
||||
applyStyle(newStyle);
|
||||
localStorage.setItem('styleVariant', newStyle);
|
||||
});
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
<p>Enter your details below to login to your account</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-card-form-email">Email</label>
|
||||
<input type="email" id="demo-card-form-email">
|
||||
<label class="label" for="demo-card-form-email">Email</label>
|
||||
<input class="input" type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="demo-card-form-password">Password</label>
|
||||
<label class="label" for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ml-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input type="password" id="demo-card-form-password">
|
||||
<input class="input" type="password" id="demo-card-form-password">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<form class="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" />
|
||||
<label class="label" for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input class="input" type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
<h2 class="text-sm font-medium">Form</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<form class="form grid w-full max-w-sm gap-6">
|
||||
<form class="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">
|
||||
<label class="label" for="demo-form-text">Username</label>
|
||||
<input class="input" type="text" id="demo-form-text" placeholder="hunvreus">
|
||||
<p class="text-muted-foreground text-sm">This is your public display name.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-select">Email</label>
|
||||
<select id="demo-form-select">
|
||||
<label class="label" for="demo-form-select">Email</label>
|
||||
<select class="select" id="demo-form-select">
|
||||
<option value="bob@example.com">m@example.com</option>
|
||||
<option value="alice@example.com">m@google.com</option>
|
||||
<option value="john@example.com">m@support.com</option>
|
||||
@@ -21,64 +21,64 @@
|
||||
</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>
|
||||
<label class="label" for="demo-form-text">Bio</label>
|
||||
<textarea class="textarea" id="demo-form-textarea" placeholder="I like to..." rows="3"></textarea>
|
||||
<p class="text-muted-foreground text-sm">You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<label for="demo-form-radio">Notify me about...</label>
|
||||
<label class="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>
|
||||
<label class="label font-normal"><input class="input" type="radio" name="demo-form-radio" value="1">All new messages</label>
|
||||
<label class="label font-normal"><input class="input" type="radio" name="demo-form-radio" value="2">Direct messages and mentions</label>
|
||||
<label class="label font-normal"><input class="input" type="radio" name="demo-form-radio" value="3" >Nothing</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
|
||||
<input type="checkbox" id="demo-form-checkbox">
|
||||
<input class="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>
|
||||
<label for="demo-form-checkbox" class="label text-sm font-normal leading-snug normal-case tracking-normal">Use different settings for my mobile devices</label>
|
||||
<p class="text-muted-foreground text-sm leading-snug">You can manage your mobile notifications in the mobile settings page.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<header>
|
||||
<label for="demo-form-checkboxes" class="text-base leading-normal">Sidebar</label>
|
||||
<label for="demo-form-checkboxes" class="label text-base leading-normal">Sidebar</label>
|
||||
<p class="text-muted-foreground text-sm">Select the items you want to display in the sidebar.</p>
|
||||
</header>
|
||||
<fieldset id="demo-form-checkboxes" class="flex flex-col gap-2">
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="1" checked>
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="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>
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="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">
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="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">
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="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">
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="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">
|
||||
<label class="label font-normal leading-tight">
|
||||
<input class="input" type="checkbox" name="demo-form-checkboxes" value="6">
|
||||
Documents
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-date">Date of birth</label>
|
||||
<input type="date" id="demo-form-date">
|
||||
<label class="label" for="demo-form-date">Date of birth</label>
|
||||
<input class="input" type="date" id="demo-form-date">
|
||||
<p class="text-muted-foreground text-sm">Your date of birth is used to calculate your age.</p>
|
||||
</div>
|
||||
|
||||
@@ -86,17 +86,17 @@
|
||||
<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>
|
||||
<label for="demo-form-switch" class="label text-sm font-normal leading-normal normal-case tracking-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch" role="switch">
|
||||
<input class="input" type="checkbox" id="demo-form-switch" role="switch">
|
||||
</div>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5 opacity-60">
|
||||
<label for="demo-form-switch-disabled" class="leading-normal">Marketing emails</label>
|
||||
<label for="demo-form-switch-disabled" class="label text-sm font-normal leading-normal normal-case tracking-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch-disabled" role="switch" disabled>
|
||||
<input class="input" type="checkbox" id="demo-form-switch-disabled" role="switch" disabled>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -19,22 +19,22 @@
|
||||
<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">
|
||||
<form class="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"/>
|
||||
<label class="label" for="demo-popover-width">Width</label>
|
||||
<input type="text" id="demo-popover-width" value="100%" class="input col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-width">Max. width</label>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="col-span-2 h-8"/>
|
||||
<label class="label" for="demo-popover-max-width">Max. width</label>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="input col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="col-span-2 h-8"/>
|
||||
<label class="label" for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="input col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="col-span-2 h-8"/>
|
||||
<label class="label" for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="input col-span-2 h-8"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<form class="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" />
|
||||
<label class="label" for="demo-tabs-account-name">Name</label>
|
||||
<input class="input" type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
<label class="label" for="demo-tabs-account-username">Username</label>
|
||||
<input class="input" type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -39,14 +39,14 @@
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<form class="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" />
|
||||
<label class="label" for="demo-tabs-password-current">Current password</label>
|
||||
<input class="input" type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new"/>
|
||||
<label class="label" for="demo-tabs-password-new">New password</label>
|
||||
<input class="input" type="password" id="demo-tabs-password-new"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</a>
|
||||
|
||||
{# <div class="flex flex-col gap-2 px-2 text-sm">
|
||||
<form class="form relative">
|
||||
<form class="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" } %}
|
||||
@@ -98,17 +98,12 @@
|
||||
"hx-swap": "outerHTML"
|
||||
} %}
|
||||
|
||||
{% set new_link_attrs = {
|
||||
"hx-boost": "true",
|
||||
"hx-select": "#content",
|
||||
"hx-target": "#content",
|
||||
"hx-swap": "outerHTML",
|
||||
"data-new-link": "true"
|
||||
} %}
|
||||
|
||||
{% set icon_home %}{% lucide "house" %}{% endset %}
|
||||
{% set icon_info %}{% lucide "info" %}{% endset %}
|
||||
{% set icon_download %}{% lucide "download" %}{% endset %}
|
||||
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
|
||||
{% set icon_settings_2 %}{% lucide "settings-2" %}{% 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 %}
|
||||
@@ -116,9 +111,11 @@
|
||||
{% 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: [
|
||||
{ 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: "Installation", url: "/installation", icon: icon_download, attrs: link_attrs },
|
||||
{ label: "Customization", url: "/customization", icon: icon_settings_2, attrs: link_attrs },
|
||||
{ label: "CLI", url: "/cli", 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" } }
|
||||
@@ -131,30 +128,30 @@
|
||||
{ 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: "Button Group", url: "/components/button-group", attrs: new_link_attrs },
|
||||
{ label: "Button Group", url: "/components/button-group", attrs: link_attrs },
|
||||
{ label: "Card", url: "/components/card", attrs: link_attrs },
|
||||
{ label: "Checkbox", url: "/components/checkbox", attrs: link_attrs },
|
||||
{ label: "Command", url: "/components/command", attrs: new_link_attrs },
|
||||
{ label: "Command", url: "/components/command", 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: "Empty", url: "/components/empty", attrs: new_link_attrs },
|
||||
{ label: "Field", url: "/components/field", attrs: new_link_attrs },
|
||||
{ label: "Form", url: "/components/form", attrs: link_attrs },
|
||||
{ label: "Empty", url: "/components/empty", attrs: link_attrs },
|
||||
{ label: "Field", url: "/components/field", attrs: link_attrs },
|
||||
{ label: "Input", url: "/components/input", attrs: link_attrs },
|
||||
{ label: "Input Group", url: "/components/input-group", attrs: new_link_attrs },
|
||||
{ label: "Item", url: "/components/item", attrs: new_link_attrs },
|
||||
{ label: "Kbd", url: "/components/kbd", attrs: new_link_attrs },
|
||||
{ label: "Input Group", url: "/components/input-group", attrs: link_attrs },
|
||||
{ label: "Item", url: "/components/item", attrs: link_attrs },
|
||||
{ label: "Kbd", url: "/components/kbd", attrs: link_attrs },
|
||||
{ label: "Label", url: "/components/label", attrs: link_attrs },
|
||||
{ label: "Native Select", url: "/components/native-select", attrs: link_attrs },
|
||||
{ label: "Pagination", url: "/components/pagination", attrs: link_attrs },
|
||||
{ label: "Popover", url: "/components/popover", attrs: link_attrs },
|
||||
{ label: "Progress", url: "/components/progress", attrs: new_link_attrs },
|
||||
{ label: "Progress", url: "/components/progress", attrs: link_attrs },
|
||||
{ label: "Radio Group", url: "/components/radio-group", attrs: link_attrs },
|
||||
{ label: "Select", url: "/components/select", attrs: link_attrs },
|
||||
{ label: "Sidebar", url: "/components/sidebar", attrs: link_attrs },
|
||||
{ label: "Skeleton", url: "/components/skeleton", attrs: link_attrs },
|
||||
{ label: "Slider", url: "/components/slider", attrs: link_attrs },
|
||||
{ label: "Spinner", url: "/components/spinner", attrs: new_link_attrs },
|
||||
{ label: "Spinner", url: "/components/spinner", 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 },
|
||||
@@ -171,6 +168,6 @@
|
||||
label="Sidebar navigation",
|
||||
header=header,
|
||||
footer=footer,
|
||||
content_attrs={ "class": "scrollbar [&_[data-new-link]::after]:content-['New'] [&_[data-new-link]::after]:ml-auto [&_[data-new-link]::after]:text-xs [&_[data-new-link]::after]:font-medium [&_[data-new-link]::after]:bg-sidebar-primary [&_[data-new-link]::after]:text-sidebar-primary-foreground [&_[data-new-link]::after]:px-2 [&_[data-new-link]::after]:py-0.5 [&_[data-new-link]::after]:rounded-md" },
|
||||
content_attrs={ "class": "scrollbar" },
|
||||
menu=menu
|
||||
) }}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,56 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: CLI
|
||||
description: Use the Basecoat CLI and template macros.
|
||||
toc:
|
||||
- label: Use the CLI
|
||||
id: cli
|
||||
- label: Use Nunjucks or Jinja macros
|
||||
id: macros
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
<h2 id="cli"><a href="#cli">Use the CLI</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>If you're using Nunjucks or Jinja, you can use the CLI to install the CSS and macros for complex components (e.g. <a href="/components/dialog">Dialog</a>). It will copy the JS and <code>.html.jinja</code> or <code>.njk</code> macros into your code.</p>
|
||||
<p>For example, to add the Dialog component, run one of the following commands:</p>
|
||||
</div>
|
||||
|
||||
{% set code_npm_exec %}npx basecoat-cli add dialog{% endset %}
|
||||
{% set code_pnpm_exec %}pnpm dlx basecoat-cli add dialog{% endset %}
|
||||
{% set code_bun_exec %}bunx basecoat-cli add dialog{% endset %}
|
||||
{% set code_yarn_exec %}yarn dlx basecoat-cli add dialog{% endset %}
|
||||
{% set exec_tabsets = [
|
||||
{ tab: "npm", panel: code_block(code_npm_exec, class="") },
|
||||
{ tab: "pnpm", panel: code_block(code_pnpm_exec, class="") },
|
||||
{ tab: "bun", panel: code_block(code_bun_exec, class="") },
|
||||
{ tab: "yarn", panel: code_block(code_yarn_exec, class="") }
|
||||
] %}
|
||||
{{ tabs(id="cli-execution", tabsets=exec_tabsets, label="Execute CLI commands to add components:") }}
|
||||
|
||||
<div class="prose my-6">
|
||||
<p>You will be asked to confirm the template engine and destination directory for the JavaScript and macros.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="macros"><a href="#macros">Use Nunjucks or Jinja macros</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>For more complex components, you can use <a href="https://mozilla.github.io/nunjucks/" target="_blank">Nunjucks</a> or <a href="https://jinja.palletsprojects.com" target="_blank">Jinja</a> macros to help generate the HTML and JavaScript code.</p>
|
||||
<p>To install them, either <a href="https://github.com/hunvreus/basecoat/tree/main/src" target="_blank">copy them directly from the GitHub repository</a> or use <a href="#cli">the CLI</a>.</p>
|
||||
<p>For example, here's how you could use the <code>select()</code> macro to generate the HTML and JavaScript code for a <a href="/components/select">Select</a> component:</p>
|
||||
</div>
|
||||
|
||||
{% set code %}{% raw %}{% call select() %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
<div role="option" data-value="pineapple">Grapes</div>
|
||||
<div role="option" data-value="pineapple">Pineapple</div>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(code, "jinja") }}
|
||||
@@ -87,7 +87,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
|
||||
@@ -14,6 +14,12 @@ toc:
|
||||
id: example-no-description
|
||||
- label: No icon
|
||||
id: example-no-icon
|
||||
- label: Action
|
||||
id: example-action
|
||||
- label: Custom Colors
|
||||
id: example-custom-colors
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
@@ -47,7 +53,7 @@ toc:
|
||||
{% endset %}
|
||||
{{ code_preview("alert", code | prettyHtml) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code>alert</code> or <code>alert-destructive</code>.</p>
|
||||
@@ -74,6 +80,8 @@ toc:
|
||||
<dd>The title.</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The description.</dd>
|
||||
<dt><code class="highlight language-html"><div data-action></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>An action aligned to the inline end of the alert.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -105,4 +113,41 @@ toc:
|
||||
<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) }}
|
||||
{{ code_preview("alert-no-icon", code) }}
|
||||
|
||||
<h3 id="example-action"><a href="#example-action">Action</a></h3>
|
||||
|
||||
{% set code %}<div class="alert">
|
||||
{% lucide "info" %}
|
||||
<h2>Enable notifications</h2>
|
||||
<section>Receive updates when there is activity in your workspace.</section>
|
||||
<div data-action>
|
||||
<button type="button" class="btn-xs-outline">Enable</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-action", code) }}
|
||||
|
||||
<h3 id="example-custom-colors"><a href="#example-custom-colors">Custom Colors</a></h3>
|
||||
|
||||
{% set code %}<div class="alert bg-amber-50 text-amber-900 dark:bg-amber-950 dark:text-amber-200">
|
||||
{% lucide "triangle-alert" %}
|
||||
<h2>Storage almost full</h2>
|
||||
<section class="text-amber-800 dark:text-amber-300">Upgrade your plan or delete unused files.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-custom-colors", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Alerts support right-to-left layouts through logical spacing and native text flow. Add <code>dir="rtl"</code> to the alert or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div class="alert" dir="rtl">
|
||||
{% lucide "info" %}
|
||||
<h2>تم حفظ التغييرات</h2>
|
||||
<section>تم تحديث إعداداتك بنجاح.</section>
|
||||
<div data-action>
|
||||
<button type="button" class="btn-xs-outline">عرض</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-rtl", code) }}
|
||||
|
||||
@@ -20,6 +20,10 @@ toc:
|
||||
id: example-with-icon
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: Custom Colors
|
||||
id: example-custom-colors
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
@@ -49,7 +53,8 @@ toc:
|
||||
<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>.
|
||||
<p>Use one of the badge classes: <code>badge</code>, <code>badge-primary</code>, <code>badge-secondary</code>, <code>badge-destructive</code>, <code>badge-outline</code>, <code>badge-ghost</code>, or <code>badge-link</code>.</p>
|
||||
<p>When an icon needs directional spacing, add <code>data-icon="inline-start"</code> or <code>data-icon="inline-end"</code> to the icon.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
@@ -88,6 +93,28 @@ toc:
|
||||
|
||||
{% set code %}<a href="#" class="badge-outline">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</a>{% endset %}
|
||||
{{ code_preview("badge-link", code) }}
|
||||
|
||||
<h3 id="example-custom-colors"><a href="#example-custom-colors">Custom Colors</a></h3>
|
||||
|
||||
{% set code %}<span class="badge bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300">
|
||||
Custom
|
||||
</span>{% endset %}
|
||||
{{ code_preview("badge-custom-colors", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Badges support right-to-left layouts through native text flow and logical icon spacing. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="flex flex-wrap gap-2">
|
||||
<span class="badge">نشط</span>
|
||||
<a href="#" class="badge-outline">
|
||||
التفاصيل
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</a>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("badge-rtl", code) }}
|
||||
|
||||
@@ -5,23 +5,35 @@ description: A container that groups related buttons together with consistent st
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Accessibility
|
||||
id: accessibility
|
||||
- label: Button Group vs Toggle Group
|
||||
id: button-group-vs-toggle-group
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Orientation
|
||||
id: example-orientation
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: Nested
|
||||
id: example-nested
|
||||
- label: Separator
|
||||
id: example-separator
|
||||
- label: Split
|
||||
id: example-split
|
||||
- label: Input
|
||||
id: example-input
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: Dropdown Menu
|
||||
id: example-dropdown-menu
|
||||
- label: Select
|
||||
id: example-select
|
||||
- label: Popover
|
||||
id: example-popover
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
@@ -77,22 +89,45 @@ toc:
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-default", code_default | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Simply wrap buttons you want to group together in a <code><div role="group" class="button-group"></code> element. You can also use the <code>data-orientation="vertical"</code> attribute to stack buttons vertically. You can also group buttons with <a href="#example-dropdown-menu">dropdown menus</a> or <a href="#example-popover">popovers</a> (provided the trigger is a button). Finally, you can use a <a href="#example-separator">separator</a> to visually divide buttons within a group (for secondary buttons for example).</p>
|
||||
<section class="prose">
|
||||
<p>Wrap related buttons or inputs in a <code><div role="group" class="button-group"></code> element. Add <code>aria-label</code> or <code>aria-labelledby</code> when the group purpose is not already clear from nearby text.</p>
|
||||
<p>Use <code>data-orientation="vertical"</code> to stack items vertically. Use <code><hr role="separator"></code> to visually divide non-outline buttons. Use <code>button-group-text</code> for static text inside a group.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
<div role="group" class="button-group">
|
||||
<div role="group" aria-label="Message actions" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_block(code_simple | prettyHtml, "html") }}
|
||||
|
||||
<h2 id="accessibility"><a href="#accessibility">Accessibility</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<ul>
|
||||
<li>Use <code>role="group"</code> on the <code>button-group</code> container.</li>
|
||||
<li>Use <code>aria-label</code> or <code>aria-labelledby</code> to label groups whose purpose is not obvious.</li>
|
||||
<li>Keyboard navigation remains native: users tab through each button, input, select, dropdown trigger, or popover trigger.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2 id="button-group-vs-toggle-group"><a href="#button-group-vs-toggle-group">Button Group vs Toggle Group</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<ul>
|
||||
<li>Use Button Group for related actions, such as archive/report/snooze.</li>
|
||||
<li>Use Toggle Group when each item controls selected/on state.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-orientation"><a href="#example-orientation">Orientation</a></h3>
|
||||
@@ -144,7 +179,7 @@ toc:
|
||||
<h3 id="example-nested"><a href="#example-nested">Nested</a></h3>
|
||||
|
||||
{% set code_nested %}
|
||||
<div role="group" aria-label="Pagination controls" class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" aria-label="Pagination controls" class="button-group">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-sm-outline">1</button>
|
||||
<button type="button" class="btn-sm-outline">2</button>
|
||||
@@ -181,18 +216,55 @@ toc:
|
||||
|
||||
{{ code_preview("button-group-separator", code_separator | prettyHtml) }}
|
||||
|
||||
<h3 id="example-split"><a href="#example-split">Split</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use a separator to create a split action with a primary action and a secondary menu trigger.</p>
|
||||
</section>
|
||||
|
||||
{% set code_split %}
|
||||
<div role="group" aria-label="Split save actions" class="button-group">
|
||||
<button type="button" class="btn-secondary">Save</button>
|
||||
<hr role="separator">
|
||||
{% set trigger_split %}
|
||||
{% lucide "chevron-down" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
trigger=trigger_split,
|
||||
trigger_attrs={"class": "btn-icon-secondary", "aria-label": "More save options"},
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="menuitem">Save as draft</div>
|
||||
<div role="menuitem">Save and publish</div>
|
||||
<div role="menuitem">Save as template</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-split", code_split | prettyHtml) }}
|
||||
|
||||
<h3 id="example-input"><a href="#example-input">Input</a></h3>
|
||||
|
||||
{% set code_input %}
|
||||
<div class="button-group">
|
||||
<div role="group" aria-label="Search" class="button-group w-full max-w-sm">
|
||||
<input type="text" class="input" placeholder="Search...">
|
||||
<button type="button" class="btn-icon-outline">{% lucide "search" %}</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Search">{% lucide "search" %}</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-input", code_input | prettyHtml) }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
{% set code_input_group %}
|
||||
<div role="group" aria-label="Amount" class="button-group w-full max-w-sm">
|
||||
<div class="button-group-text">$</div>
|
||||
<input type="text" class="input" placeholder="0.00">
|
||||
<div class="button-group-text">USD</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-input-group", code_input_group | prettyHtml) }}
|
||||
|
||||
<h3 id="example-dropdown-menu"><a href="#example-dropdown-menu">Dropdown Menu</a></h3>
|
||||
|
||||
@@ -246,7 +318,7 @@ toc:
|
||||
|
||||
{% set code_select %}
|
||||
<div class="flex w-fit items-stretch gap-2">
|
||||
<div class="button-group">
|
||||
<div role="group" aria-label="Currency amount" class="button-group">
|
||||
{% call select(trigger_attrs={"class": "[&_[data-name]]:hidden"}) %}
|
||||
<div role="option" data-value="$">$ <span class="text-muted-foreground" data-name>US Dollar</span></div>
|
||||
<div role="option" data-value="€">€ <span class="text-muted-foreground" data-name>Euro</span></div>
|
||||
@@ -269,14 +341,17 @@ toc:
|
||||
{% lucide "bot" %}
|
||||
Copilot
|
||||
</button>
|
||||
{% set trigger_popover %}
|
||||
{% lucide "chevron-down" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
trigger=trigger,
|
||||
trigger=trigger_popover,
|
||||
trigger_attrs={"class": "btn-icon-outline"},
|
||||
popover_attrs={"class": "w-72 p-0", "data-align": "end"}
|
||||
) %}
|
||||
<header class="px-4 py-3 text-sm font-medium border-b">Agent Tasks</header>
|
||||
<section class="p-4 text-sm space-y-2">
|
||||
<textarea class="textarea w-full mb-4" placeholder="Describe your taks in natural language"></textarea>
|
||||
<textarea class="textarea w-full mb-4" placeholder="Describe your task in natural language"></textarea>
|
||||
<p class="font-medium">Start a new task with Copilot</p>
|
||||
<p class="text-muted-foreground">
|
||||
Describe your task in natural language. Copilot will work in the
|
||||
@@ -288,3 +363,24 @@ toc:
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-popover", code_popover | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Button groups support right-to-left layouts through logical border and radius rules. Add <code>dir="rtl"</code> to the group or an ancestor. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl">
|
||||
<div role="group" aria-label="إجراءات الرسالة" class="button-group">
|
||||
<button type="button" class="btn-outline">أرشفة</button>
|
||||
<button type="button" class="btn-outline">تقرير</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
{% lucide "arrow-right", { "class": "rtl:rotate-180" } %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("button-group-rtl", code_rtl | prettyHtml) }}
|
||||
|
||||
+127
-66
@@ -8,57 +8,55 @@ toc:
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Primary
|
||||
id: example-primary
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Ghost
|
||||
id: example-ghost
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: With Icon
|
||||
id: example-with-icon
|
||||
- label: Loading
|
||||
id: example-loading
|
||||
- label: Large
|
||||
id: example-large
|
||||
- label: Extra Small
|
||||
id: example-extra-small
|
||||
- label: Extra Small Icon
|
||||
id: example-extra-small-icon
|
||||
- label: Small
|
||||
id: example-small
|
||||
- label: Rounded
|
||||
id: example-rounded
|
||||
- label: Spinner
|
||||
id: example-spinner
|
||||
- label: Button Group
|
||||
id: example-button-group
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<button class="btn">Button</button>{% endset %}
|
||||
{% set code %}<button type="button" class="btn">Button</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use one of the <code>btn</code> classes to style any element as a button:</p>
|
||||
<p>You can use one of the <code>btn</code> classes to style a button element:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variants</b>:
|
||||
<ul>
|
||||
<li><code>btn</code> or <code>btn-primary</code> for primary
|
||||
buttons,</li>
|
||||
<li><code>btn</code> or <code>btn-primary</code> for default/primary buttons.</li>
|
||||
<li><code>btn-secondary</code> for secondary buttons.</li>
|
||||
<li><code>btn-destructive</code> for destructive buttons.</li>
|
||||
<li><code>btn-outline</code> for outline buttons.</li>
|
||||
<li><code>btn-ghost</code> for ghost buttons.</li>
|
||||
<li><code>btn-link</code> for link buttons.</li>
|
||||
<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>
|
||||
@@ -67,94 +65,157 @@ toc:
|
||||
<li><code>btn-xs</code> for extra small buttons.</li>
|
||||
<li><code>btn-sm</code> for small buttons.</li>
|
||||
<li><code>btn-lg</code> for large buttons.</li>
|
||||
<li>You can combine sizes with any variant, for example <code>btn-lg-destructive</code>, <code>btn-xs-outline</code> or <code>btn-sm-icon-outline</code>.</li>
|
||||
<li><code>btn-icon</code> for icon buttons. This can be combined with sizes and variants, for example <code>btn-sm-icon-outline</code> or <code>btn-xs-icon-destructive</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Button classes are combined into a single class name. Use <code>btn-{variant}</code>, <code>btn-{size}</code>, <code>btn-{size}-{variant}</code>, <code>btn-icon-{variant}</code>, or <code>btn-{size}-icon-{variant}</code>. Do not stack classes like <code>btn-sm btn-outline</code>; use <code>btn-sm-outline</code> instead.</p>
|
||||
|
||||
<p>Basecoat follows Tailwind v4's default button cursor behavior and does not force <code>cursor: pointer</code>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
{% set code_composition %}<button type="button" class="btn">Default</button>
|
||||
<button type="button" class="btn-outline">Outline</button>
|
||||
<button type="button" class="btn-sm">Small</button>
|
||||
<button type="button" class="btn-sm-outline">Small outline</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="Settings">
|
||||
{% lucide "settings" %}
|
||||
</button>
|
||||
<button type="button" class="btn-sm-icon-outline" aria-label="Settings">
|
||||
{% lucide "settings" %}
|
||||
</button>{% endset %}
|
||||
{{ code_block(code_composition) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-primary"><a href="#example-primary">Primary</a></h3>
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code %}<button class="btn">Primary</button>{% endset %}
|
||||
{{ code_preview("button-primary", code) }}
|
||||
<section class="prose">
|
||||
<p>Use size-specific classes to change the size of the button.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
{% set code_size %}<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-xs">Extra Small</button>
|
||||
<button type="button" class="btn-sm">Small</button>
|
||||
<button type="button" class="btn">Default</button>
|
||||
<button type="button" class="btn-lg">Large</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-size", code_size) }}
|
||||
|
||||
{% set code %}<button class="btn-secondary">Button</button>{% endset %}
|
||||
{{ code_preview("button-secondary", code) }}
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
<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) }}
|
||||
{% set code %}<button type="button" class="btn">Button</button>{% endset %}
|
||||
{{ code_preview("button-default", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline">Outline</button>{% endset %}
|
||||
{% set code %}<button type="button" class="btn-outline">Outline</button>{% endset %}
|
||||
{{ code_preview("button-outline", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-secondary">Secondary</button>{% endset %}
|
||||
{{ code_preview("button-secondary", code) }}
|
||||
|
||||
<h3 id="example-ghost"><a href="#example-ghost">Ghost</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-ghost">Ghost</button>{% endset %}
|
||||
{% set code %}<button type="button" class="btn-ghost">Ghost</button>{% endset %}
|
||||
{{ code_preview("button-ghost", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<button type="button" class="btn-destructive">Destructive</button>{% endset %}
|
||||
{{ code_preview("button-destructive", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-link">Link</button>{% endset %}
|
||||
{% set code %}<button type="button" class="btn-link">Link</button>{% endset %}
|
||||
{{ code_preview("button-link", code) }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-icon-outline">
|
||||
{% set code %}<button type="button" class="btn-icon-outline" aria-label="Open">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-icon", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With Icon</a></h3>
|
||||
|
||||
{% set code %}<button class="btn">
|
||||
{% lucide "send" %}
|
||||
Send email
|
||||
<section class="prose">
|
||||
<p>Mark inline icons with <code>data-icon="inline-start"</code> or <code>data-icon="inline-end"</code> when the button should use icon-aware spacing.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button type="button" class="btn">
|
||||
{% lucide "git-branch", { "data-icon": "inline-start" } %}
|
||||
New Branch
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-with-icon", code) }}
|
||||
|
||||
<h3 id="example-loading"><a href="#example-loading">Loading</a></h3>
|
||||
<h3 id="example-rounded"><a href="#example-rounded">Rounded</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
<section class="prose">
|
||||
<p>Use standard Tailwind classes to override the button radius.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button type="button" class="btn rounded-full">
|
||||
{% lucide "arrow-up" %}
|
||||
Rounded
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-loading", code) }}
|
||||
{{ code_preview("button-rounded", code) }}
|
||||
|
||||
<h3 id="example-large"><a href="#example-large">Large</a></h3>
|
||||
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-lg">
|
||||
{% lucide "shopping-cart" %}
|
||||
Buy
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-large", code) }}
|
||||
{% set code_spinner %}<div class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Generating
|
||||
</button>
|
||||
<button type="button" class="btn-outline">
|
||||
Downloading
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-spinner", code_spinner) }}
|
||||
|
||||
<h3 id="example-extra-small"><a href="#example-extra-small">Extra Small</a></h3>
|
||||
<h3 id="example-button-group"><a href="#example-button-group">Button Group</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-xs-outline">
|
||||
XS outline
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-extra-small", code) }}
|
||||
<section class="prose">
|
||||
<p>To group related buttons, use the <a href="/components/button-group/">Button Group</a> component.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="example-extra-small-icon"><a href="#example-extra-small-icon">Extra Small Icon</a></h3>
|
||||
{% set code_button_group %}<div class="flex w-fit items-stretch gap-2">
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Archive</button>
|
||||
<button type="button" class="btn-outline">Report</button>
|
||||
</div>
|
||||
<div role="group" class="button-group">
|
||||
<button type="button" class="btn-outline">Snooze</button>
|
||||
<button type="button" class="btn-icon-outline" aria-label="More options">
|
||||
{% lucide "ellipsis" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-group-demo", code_button_group) }}
|
||||
|
||||
{% set code %}<button class="btn-xs-icon-outline">
|
||||
{% lucide "plus" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-extra-small-icon", code) }}
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<h3 id="example-small"><a href="#example-small">Small</a></h3>
|
||||
<section class="prose">
|
||||
<p>Buttons support right-to-left layouts through logical spacing. Add <code>dir="rtl"</code> to the button or an ancestor. Directional icons should be flipped explicitly.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button class="btn-sm-icon-destructive">
|
||||
{% lucide "trash" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-small", code) }}
|
||||
{% set code_rtl %}<div dir="rtl" class="flex flex-wrap items-center gap-2">
|
||||
<button type="button" class="btn-outline">زر</button>
|
||||
<button type="button" class="btn-destructive">حذف</button>
|
||||
<button type="button" class="btn-outline">
|
||||
إرسال
|
||||
{% lucide "arrow-right", { "data-icon": "inline-end", "class": "rtl:rotate-180" } %}
|
||||
</button>
|
||||
<button type="button" class="btn-secondary" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
جاري التحميل
|
||||
</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("button-rtl", code_rtl) }}
|
||||
|
||||
@@ -8,10 +8,14 @@ toc:
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: With content and footer
|
||||
id: example-with-content-and-footer
|
||||
- label: With image
|
||||
id: example-with-image
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
@@ -23,17 +27,17 @@ toc:
|
||||
<p>Enter your details below to login to your account</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-card-form-email">Email</label>
|
||||
<input type="email" id="demo-card-form-email">
|
||||
<label class="label" for="demo-card-form-email">Email</label>
|
||||
<input class="input" type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label 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>
|
||||
<label class="label" for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ms-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input type="password" id="demo-card-form-password">
|
||||
<input class="input" type="password" id="demo-card-form-password">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -45,7 +49,7 @@ toc:
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<header>
|
||||
@@ -87,6 +91,19 @@ toc:
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code %}<div class="card" data-size="sm">
|
||||
<header>
|
||||
<h2>Small Card</h2>
|
||||
<p>A compact card with reduced spacing.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p class="text-sm">Use <code>data-size="sm"</code> for denser layouts.</p>
|
||||
</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-size", code) }}
|
||||
|
||||
<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">
|
||||
@@ -96,7 +113,7 @@ toc:
|
||||
</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">
|
||||
<ol class="mt-4 flex list-decimal flex-col gap-2 ps-6">
|
||||
<li>New analytics widgets for daily/weekly metrics</li>
|
||||
<li>Simplified navigation menu</li>
|
||||
<li>Dark mode support</li>
|
||||
@@ -148,9 +165,29 @@ toc:
|
||||
{% lucide "land-plot" %}
|
||||
350m²
|
||||
</span>
|
||||
<span class="ml-auto font-medium tabular-nums">
|
||||
<span class="ms-auto font-medium tabular-nums">
|
||||
$135,000
|
||||
</span>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-with-image", code) }}
|
||||
{{ code_preview("card-with-image", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Cards support right-to-left layouts through native text flow. Add <code>dir="rtl"</code> to the card or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div class="card" dir="rtl">
|
||||
<header>
|
||||
<h2>إعدادات الحساب</h2>
|
||||
<p>راجع إعدادات الأمان والإشعارات.</p>
|
||||
</header>
|
||||
<section>
|
||||
<p class="text-sm">يمكنك تعديل هذه الإعدادات في أي وقت.</p>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">حفظ</button>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-rtl", code) }}
|
||||
|
||||
@@ -5,97 +5,162 @@ description: A control that allows the user to toggle between checked and not ch
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Checked state
|
||||
id: checked-state
|
||||
- label: Invalid state
|
||||
id: invalid-state
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Description
|
||||
id: example-description
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: With text
|
||||
id: example-with-text
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: Group
|
||||
id: example-group
|
||||
- label: Table
|
||||
id: example-table
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input">
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="checkbox" id="terms" class="input">
|
||||
<label for="terms">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></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>
|
||||
<p>Add the <code>input</code> class to your <code><input type="checkbox"></code> elements. Pair it with <a href="/components/field">Field</a> and a native <code><label></code> for proper layout and labeling.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="checked-state"><a href="#checked-state">Checked state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use native HTML attributes for state. Add <code>checked</code> for an initially checked checkbox, or read and update the element's <code>checked</code> property in JavaScript.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" class="input" checked>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="invalid-state"><a href="#invalid-state">Invalid state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>aria-invalid="true"</code> on the checkbox and, when using <code>field</code>, set <code>data-invalid="true"</code> on the field wrapper.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start gap-3" data-invalid="true">
|
||||
<input type="checkbox" id="invalid-checkbox" class="input" aria-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="invalid-checkbox">Accept terms and conditions</label>
|
||||
<p class="field-error">You must accept the terms to continue.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-invalid", code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="checkbox" id="checkbox-basic" class="input">
|
||||
<label for="checkbox-basic">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-basic", code) }}
|
||||
|
||||
<h3 id="example-description"><a href="#example-description">Description</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-description" class="input" aria-describedby="checkbox-description-text">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="checkbox-description">Accept terms and conditions</label>
|
||||
<p id="checkbox-description-text" class="field-description">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-description", code) }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input" disabled>
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{% set code %}<div role="group" class="field flex-row items-center gap-3" data-disabled="true">
|
||||
<input type="checkbox" id="checkbox-disabled" class="input" disabled>
|
||||
<label for="checkbox-disabled">Accept terms and conditions</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-disabled", code) }}
|
||||
|
||||
<h3 id="example-with-text"><a href="#example-with-text">With text</a></h3>
|
||||
<h3 id="example-group"><a href="#example-group">Group</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>
|
||||
{% set code %}<fieldset class="fieldset">
|
||||
<legend>Sidebar</legend>
|
||||
<p>Select the items you want to display in the sidebar.</p>
|
||||
<div role="group" aria-label="Sidebar items" data-slot="checkbox-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-recents" checked>
|
||||
<label for="checkbox-recents">Recents</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-home" checked>
|
||||
<label for="checkbox-home">Home</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-applications">
|
||||
<label for="checkbox-applications">Applications</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="checkbox" id="checkbox-desktop">
|
||||
<label for="checkbox-desktop">Desktop</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("checkbox-group", code) }}
|
||||
|
||||
<h3 id="example-table"><a href="#example-table">Table</a></h3>
|
||||
|
||||
{% set code %}<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" class="input" aria-label="Select all"></th>
|
||||
<th>Status</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 1" checked></td>
|
||||
<td>Active</td>
|
||||
<td>olivia@example.com</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="input" aria-label="Select row 2"></td>
|
||||
<td>Inactive</td>
|
||||
<td>noah@example.com</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-table", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Checkbox layout uses document direction and logical spacing. Set <code>dir="rtl"</code> on a parent to render right-to-left.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="field flex-row items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-rtl" class="input" aria-describedby="checkbox-rtl-description" checked>
|
||||
<div class="grid gap-1.5">
|
||||
<label for="checkbox-rtl">قبول الشروط والأحكام</label>
|
||||
<p id="checkbox-rtl-description" class="field-description">يمكنك تغيير هذا الخيار في أي وقت.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-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) }}
|
||||
{{ code_preview("checkbox-rtl", code) }}
|
||||
|
||||
+207
-162
@@ -1,7 +1,7 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Combobox
|
||||
description: Autocomplete input and command palette with a list of suggestions.
|
||||
description: Autocomplete input with a list of suggestions.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
@@ -24,26 +24,39 @@ toc:
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Scrollable
|
||||
id: example-scrollable
|
||||
- label: Top side
|
||||
- label: Multiple
|
||||
id: example-multiple
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Custom Items
|
||||
id: example-custom
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Auto Highlight
|
||||
id: example-auto-highlight
|
||||
- label: Top Side
|
||||
id: example-top
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "select.njk" import select %}
|
||||
{% from "combobox.njk" import combobox %}
|
||||
|
||||
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% set 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..."
|
||||
{% call combobox(
|
||||
id="framework-combobox",
|
||||
name="framework",
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
@@ -57,11 +70,11 @@ toc:
|
||||
<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>
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/combobox.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -69,12 +82,12 @@ toc:
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
|
||||
select.js
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/combobox.js" target="_blank">
|
||||
combobox.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
@@ -82,7 +95,7 @@ toc:
|
||||
<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>
|
||||
<p>Combobox is input-first. The visible input filters the list; the hidden input stores the submitted value.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
@@ -91,54 +104,20 @@ toc:
|
||||
|
||||
<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. Optional attributes:
|
||||
<ul>
|
||||
<li><code>data-filter</code>: custom text used for filtering (if different from displayed content, e.g., <code>data-filter="united states"</code>)</li>
|
||||
<li><code>data-keywords</code>: additional keywords for enhanced filtering (e.g., <code>data-keywords="bread fruit"</code>)</li>
|
||||
<li><code>data-force</code>: forces the option to always display, regardless of filter (e.g., <code>data-force</code>)</li>
|
||||
<li><code>aria-disabled="true"</code>: marks option as disabled (excluded from selection and keyboard navigation)</li>
|
||||
</ul>
|
||||
</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>
|
||||
<dt><code class="highlight language-html"><div class="combobox"></code></dt>
|
||||
<dd>Root element. Add <code>data-multiple="true"</code> for multiple selection and <code>data-auto-highlight="true"</code> to highlight the first match while filtering.</dd>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>The editable control. It should include <code>aria-expanded</code>, <code>aria-controls</code>, and <code>aria-autocomplete="list"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div class="combobox-chips"></code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Wraps selected chips and the editable input in multiple mode.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover></code></dt>
|
||||
<dd>The suggestions popup. Use <code>data-side</code> and <code>data-align</code> like Popover.</dd>
|
||||
<dt><code class="highlight language-html"><div role="listbox"></code></dt>
|
||||
<dd>The suggestions list. Add <code>aria-multiselectable="true"</code> in multiple mode and <code>data-empty</code> for the empty message.</dd>
|
||||
<dt><code class="highlight language-html"><div role="option" data-value="..."></code></dt>
|
||||
<dd>Selectable option. Options can contain custom HTML; use <code>data-label</code> when the visible input should use different text than the rendered content.</dd>
|
||||
<dt><code class="highlight language-html"><input type="hidden"></code></dt>
|
||||
<dd>Submitted value. Single mode stores a string; multiple mode stores a JSON array.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -147,147 +126,213 @@ toc:
|
||||
<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>
|
||||
<dd>Dispatched on the component after initialization.</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>
|
||||
<dd>Dispatched on <code>document</code> when the popup opens so other popup components can close.</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>
|
||||
<dd>Dispatched on selection changes with <code>event.detail.value</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-4">JavaScript methods</a></h4>
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">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>
|
||||
<dt><code>value</code></dt>
|
||||
<dd>Gets or sets the selected value. Multiple mode uses an array.</dd>
|
||||
<dt><code>select(value)</code></dt>
|
||||
<dd>Selects an option by value.</dd>
|
||||
<dt><code>deselect(value)</code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Removes a selected value.</dd>
|
||||
<dt><code>toggle(value)</code> <span class="badge-secondary">Multiple only</span></dt>
|
||||
<dd>Toggles a selected value.</dd>
|
||||
</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>
|
||||
<section class="prose">
|
||||
<p>Use the <code>combobox()</code> macro for Nunjucks or Jinja.</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/select.html.jinja" target="_blank">
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/combobox.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/select.njk" target="_blank">
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/combobox.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call select(
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true
|
||||
{% set raw_code %}{% raw %}{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="nuxtjs">Nuxt.js</div>
|
||||
<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>
|
||||
<h3 id="example-multiple"><a href="#example-multiple">Multiple</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..."
|
||||
{% call combobox(
|
||||
id="frameworks-combobox",
|
||||
name="frameworks",
|
||||
multiple=true,
|
||||
selected=["Next.js"],
|
||||
placeholder="Add framework",
|
||||
input_attrs={"class": "w-[320px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
auto_highlight=true
|
||||
) %}
|
||||
<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 %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-multiple", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
id="timezone-combobox",
|
||||
placeholder="Search timezone",
|
||||
input_attrs={"class": "w-[280px]"},
|
||||
listbox_attrs={"data-empty": "No timezone found."}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="timezone-americas">
|
||||
<div role="heading" id="timezone-americas">Americas</div>
|
||||
<div role="option" data-value="America/New_York">(GMT-5) New York</div>
|
||||
<div role="option" data-value="America/Los_Angeles">(GMT-8) Los Angeles</div>
|
||||
<div role="option" data-value="America/Sao_Paulo">(GMT-3) São Paulo</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="option">
|
||||
{% lucide "circle-plus" %}
|
||||
Create timezone
|
||||
<div role="group" aria-labelledby="timezone-europe">
|
||||
<div role="heading" id="timezone-europe">Europe</div>
|
||||
<div role="option" data-value="Europe/London">(GMT+0) London</div>
|
||||
<div role="option" data-value="Europe/Paris">(GMT+1) Paris</div>
|
||||
<div role="option" data-value="Europe/Berlin">(GMT+1) Berlin</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-with-groups", code | prettyHtml) }}
|
||||
{{ code_preview("combobox-groups", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-top"><a href="#example-top">Top side</a></h3>
|
||||
<h3 id="example-custom"><a href="#example-custom">Custom Items</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...",
|
||||
{% call combobox(
|
||||
id="country-combobox",
|
||||
placeholder="Search countries",
|
||||
input_attrs={"class": "w-[280px]"},
|
||||
listbox_attrs={"data-empty": "No country found."}
|
||||
) %}
|
||||
<div role="option" data-value="argentina" data-label="Argentina" data-filter="Argentina South America">
|
||||
<span class="flex flex-col">
|
||||
<span>Argentina</span>
|
||||
<span class="text-muted-foreground text-xs">South America (ar)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="japan" data-label="Japan" data-filter="Japan Asia">
|
||||
<span class="flex flex-col">
|
||||
<span>Japan</span>
|
||||
<span class="text-muted-foreground text-xs">Asia (jp)</span>
|
||||
</span>
|
||||
</div>
|
||||
<div role="option" data-value="united-states" data-label="United States" data-filter="United States North America USA">
|
||||
<span class="flex flex-col">
|
||||
<span>United States</span>
|
||||
<span class="text-muted-foreground text-xs">North America (us)</span>
|
||||
</span>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-custom", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="invalid-combobox-input">Framework</label>
|
||||
{% call combobox(
|
||||
id="invalid-combobox",
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"id": "invalid-combobox-input", "aria-invalid": "true", "class": "w-[240px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."}
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
<p class="field-error" role="alert">Select a framework to continue.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-invalid", code | prettyHtml, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"disabled": "disabled", "class": "w-[240px]"}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-disabled", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-auto-highlight"><a href="#example-auto-highlight">Auto Highlight</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
auto_highlight=true
|
||||
) %}
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-auto-highlight", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-top"><a href="#example-top">Top Side</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call combobox(
|
||||
placeholder="Select a framework",
|
||||
input_attrs={"class": "w-[240px]"},
|
||||
popover_attrs={"data-side": "top"}
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% for framework in frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}{% endset %}
|
||||
{{ code_preview("combobox", code | prettyHtml) }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-top", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% call combobox(
|
||||
placeholder="اختر إطار عمل",
|
||||
input_attrs={"class": "w-[240px]"}
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="astro">Astro</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-rtl", code | prettyHtml) }}
|
||||
|
||||
+156
-109
@@ -1,8 +1,10 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Command
|
||||
description: Fast, composable command menu for quick navigation and actions.
|
||||
description: Command menu for search and quick actions.
|
||||
toc:
|
||||
- label: About
|
||||
id: about
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
@@ -22,61 +24,76 @@ toc:
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Dialog
|
||||
id: example-dialog
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Shortcuts
|
||||
id: example-shortcuts
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Scrollable
|
||||
id: example-scrollable
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "command.njk" import command, command_dialog %}
|
||||
|
||||
{% macro command_items(suggestions_id="suggestions", settings_id="settings") %}
|
||||
{% macro command_items(suggestions_id="suggestions", settings_id="settings", shortcuts=false) %}
|
||||
<div role="group" aria-labelledby="{{ suggestions_id }}">
|
||||
<span role="heading" id="{{ suggestions_id }}">Suggestions</span>
|
||||
<div role="menuitem" onclick="console.log('Open calendar')">
|
||||
<div role="menuitem" data-filter="Calendar" data-keywords="date event schedule">
|
||||
{% lucide "calendar" %}
|
||||
<span>Calendar</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘K</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" onclick="console.log('Search emoji')">
|
||||
<div role="menuitem" data-filter="Search Emoji" data-keywords="emoji smile reaction">
|
||||
{% lucide "smile" %}
|
||||
<span>Search Emoji</span>
|
||||
{% if shortcuts %}<span data-shortcut>⌘E</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
{% lucide "calculator" %}
|
||||
<span>Calculator</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator" />
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="{{ settings_id }}">
|
||||
<span role="heading" id="{{ settings_id }}">Settings</span>
|
||||
<div role="menuitem" onclick="console.log('Open profile')" data-filter="Profile">
|
||||
<div role="menuitem" data-filter="Profile" data-keywords="user account">
|
||||
{% lucide "user" %}
|
||||
<span>Profile</span>
|
||||
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘P</kbd>
|
||||
{% if shortcuts %}<span data-shortcut>⌘P</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" onclick="console.log('Open billing')" data-filter="Billing">
|
||||
<div role="menuitem" data-filter="Billing" data-keywords="invoice payment">
|
||||
{% lucide "credit-card" %}
|
||||
<span>Billing</span>
|
||||
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘B</kbd>
|
||||
{% if shortcuts %}<span data-shortcut>⌘B</span>{% endif %}
|
||||
</div>
|
||||
<div role="menuitem" onclick="console.log('Open settings')" data-filter="Settings">
|
||||
<div role="menuitem" data-filter="Settings" data-keywords="config preferences">
|
||||
{% lucide "settings" %}
|
||||
<span>Settings</span>
|
||||
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘S</kbd>
|
||||
{% if shortcuts %}<span data-shortcut>⌘S</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% set code_standalone %}
|
||||
{% call command(
|
||||
id="demo-command-standalone",
|
||||
main_attrs={ "class": "rounded-lg border shadow-md" }
|
||||
) %}
|
||||
{% call command(id="demo-command-standalone") %}
|
||||
{{ command_items("suggestions", "settings") }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("command-standalone", code_standalone | prettyHtml, class=" w-full max-w-[450px]") }}
|
||||
{{ code_preview("command-standalone", code_standalone | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="about"><a href="#about">About</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>shadcn/ui's Command component is built on <code>cmdk</code>. Basecoat implements the same command-menu pattern with vanilla HTML, CSS, and a small JavaScript controller for filtering, active item navigation, and dialog closing.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
@@ -97,7 +114,7 @@ toc:
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -109,68 +126,53 @@ toc:
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your command HTML</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use the command menu standalone:</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_standalone | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Wrap the command menu in a <code><dialog class="command-dialog"></code> to display in a dialog:</p>
|
||||
<p>Use <code><dialog class="command-dialog"></code> for the command palette variant.</p>
|
||||
</section>
|
||||
|
||||
{% set code_dialog %}
|
||||
<dialog class="command-dialog" aria-label="Command menu">
|
||||
{% set code_dialog_usage %}<button class="btn-outline" onclick="document.getElementById('command-palette').showModal()">Open</button>
|
||||
<dialog id="command-palette" class="command-dialog" aria-label="Command menu">
|
||||
<div class="command">
|
||||
<!-- Regular command content: header, menu, etc. -->
|
||||
<!-- Command content -->
|
||||
</div>
|
||||
</dialog>
|
||||
{% endset %}
|
||||
|
||||
{{ code_block(code_dialog | prettyHtml, "html") }}
|
||||
</dialog>{% endset %}
|
||||
{{ code_block(code_dialog_usage | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-3"><a href="#usage-html-js-3">HTML structure</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>The command menu can be used standalone or inside a native dialog. The structure is:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><dialog class="command-dialog"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>For dialog variant, wraps a <code><div class="command"></code> to display as a modal. Uses native <code><dialog></code> behavior and requires <code>aria-label</code> or <code>aria-labelledby</code> for accessibility.
|
||||
<dd>Dialog variant. Wraps one <code><div class="command"></code>. Add <code>aria-label</code> or <code>aria-labelledby</code> for an accessible name.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="command"></code></dt>
|
||||
<dd>Wraps around the entire command component. Can be used standalone or inside a dialog.
|
||||
<dd>Root command menu. Can also be used standalone without the dialog wrapper.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the search input to filter command options.
|
||||
<dd>Search input wrapper. The macro includes a search icon and input.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="text"></code></dt>
|
||||
<dd>The search input. Should have:
|
||||
<ul>
|
||||
<li><code>role="combobox"</code>: indicates the input controls a menu with filtering</li>
|
||||
<li><code>aria-expanded="true"</code>: the menu is always expanded</li>
|
||||
<li><code>aria-controls="{ MENU_ID }"</code>: points to the menu's id</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><input type="text" role="combobox"></code></dt>
|
||||
<dd>Filter input. Use <code>aria-expanded="true"</code> and <code>aria-controls="{ MENU_ID }"</code> to point to the command list.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div role="menu"></code></dt>
|
||||
<dd>The menu containing the command items. Should have <code>id="{ MENU_ID }"</code> refered to by the input's <code>aria-controls</code> attribute.
|
||||
<dt><code class="highlight language-html"><div role="menu" id="{ MENU_ID }"></code></dt>
|
||||
<dd>Command list. Set <code>data-empty</code> to customize the empty state.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related command items. Hidden automatically when every item in the group is filtered out.</dd>
|
||||
<dt><code class="highlight language-html"><span role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading. Use an <code>id</code> when the parent group uses <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code> or <code class="highlight language-html"><a role="menuitem"></code></dt>
|
||||
<dd>Command item that triggers an action when clicked. Use <code><a></code> for navigation links, or <code><div></code> for actions. In a command-dialog, clicking a menuitem automatically closes the dialog unless the <code>data-keep-command-open</code> attribute is present. Optional attributes:
|
||||
<ul>
|
||||
<li><code>href</code>: If present on an <code><a></code>, the item navigates to that URL</li>
|
||||
<li><code>data-filter</code>: custom text used for filtering (if different from displayed content, e.g., <code>data-filter="calendar event"</code>)</li>
|
||||
<li><code>data-keywords</code>: additional keywords for enhanced filtering (e.g., <code>data-keywords="calendar event date"</code>)</li>
|
||||
<li><code>data-force</code>: forces the item to always display, regardless of filter (e.g., <code>data-force</code>)</li>
|
||||
<li><code>aria-disabled="true"</code>: marks item as disabled (excluded from keyboard navigation and filtering)</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dd>Selectable command item. Use <code><a></code> for navigation and <code><div></code> for actions. Supports <code>data-filter</code>, <code>data-keywords</code>, <code>data-force</code>, <code>data-keep-command-open</code>, and <code>aria-disabled="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><span data-indicator></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Item indicator. It becomes visible with <code>data-checked="true"</code> or <code>aria-selected="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups/items. Hidden when filtering is active.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group of items, can have <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>
|
||||
<dd>Separator between groups/items. Hidden while filtering.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -185,7 +187,7 @@ toc:
|
||||
<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>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -196,7 +198,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -211,7 +213,7 @@ toc:
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call command() %}
|
||||
<div role="menuitem">
|
||||
<div role="menuitem" data-keywords="date event">
|
||||
<svg>...</svg>
|
||||
<span>Calendar</span>
|
||||
</div>
|
||||
@@ -222,67 +224,112 @@ toc:
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>You can also use the <code>items</code> parameter to pass an array of items:</p>
|
||||
</section>
|
||||
|
||||
{% set icon_calendar %}{% lucide "calendar" %}{% endset %}
|
||||
{% set icon_settings %}{% lucide "settings" %}{% endset %}
|
||||
{% set raw_code_items %}{% raw %}{% set icon_calendar %}{% lucide "calendar" %}{% endset %}
|
||||
{% set icon_settings %}{% lucide "settings" %}{% endset %}
|
||||
{% set items = [
|
||||
{ type: "group", label: "Suggestions", items: [
|
||||
{ label: "Calendar", icon: icon_calendar, keywords: "date event" },
|
||||
{ label: "Search Emoji", icon: icon_settings },
|
||||
{ label: "Calculator", disabled: true }
|
||||
]},
|
||||
{ type: "separator" },
|
||||
{ type: "group", label: "Settings", items: [
|
||||
{ label: "Profile", url: "/profile", icon: icon_settings, keywords: "user account" },
|
||||
{ label: "Billing", url: "/billing", icon: icon_settings },
|
||||
{ label: "Settings", url: "/settings", icon: icon_settings }
|
||||
]}
|
||||
] %}
|
||||
|
||||
{% call command(items=items) %}{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code_items, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-dialog"><a href="#example-dialog">Dialog</a></h3>
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use some simple JavaScript to open the command menu with a keyboard shortcut (in this case <kbd class="kbd">⌘J</kbd>).</p>
|
||||
<p>A simple command menu in a dialog. The button also shows the keyboard shortcut used in this example.</p>
|
||||
</section>
|
||||
|
||||
{% set code_dialog %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline"
|
||||
onclick="document.getElementById('demo-command-dialog').showModal()"
|
||||
>
|
||||
{% set code_basic %}
|
||||
<button type="button" class="btn-outline" onclick="document.getElementById('command-basic').showModal()">
|
||||
Open command menu
|
||||
<kbd class="kbd">⌘J</kbd>
|
||||
</button>
|
||||
|
||||
{% call command_dialog(id="demo-command-dialog") %}
|
||||
{{ command_items("cmd-suggestions", "cmd-settings") }}
|
||||
{% call command_dialog(id="command-basic") %}
|
||||
{{ command_items("command-basic-suggestions", "command-basic-settings") }}
|
||||
{% endcall %}
|
||||
|
||||
<script>
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'j') {
|
||||
e.preventDefault();
|
||||
const dialog = document.getElementById('demo-command-dialog');
|
||||
if (dialog.open) {
|
||||
dialog.close();
|
||||
} else {
|
||||
dialog.showModal();
|
||||
dialog.querySelector('header input')?.focus();
|
||||
}
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'j') {
|
||||
event.preventDefault();
|
||||
const dialog = document.getElementById('command-basic');
|
||||
dialog.open ? dialog.close() : dialog.showModal();
|
||||
dialog.querySelector('header input')?.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("command-basic", code_basic | prettyHtml) }}
|
||||
|
||||
{{ code_preview("command-dialog", code_dialog | prettyHtml) }}
|
||||
<h3 id="example-shortcuts"><a href="#example-shortcuts">Shortcuts</a></h3>
|
||||
|
||||
{% set code_shortcuts %}
|
||||
{% call command(id="command-shortcuts") %}
|
||||
{{ command_items("command-shortcuts-suggestions", "command-shortcuts-settings", true) }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-shortcuts", code_shortcuts | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code_groups %}
|
||||
{% call command(id="command-groups") %}
|
||||
{{ command_items("command-groups-suggestions", "command-groups-settings") }}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-groups", code_groups | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% set code_scrollable %}
|
||||
{% call command(id="command-scrollable") %}
|
||||
<div role="group" aria-labelledby="command-scrollable-heading">
|
||||
<span role="heading" id="command-scrollable-heading">Pages</span>
|
||||
{% for i in range(0, 18) %}
|
||||
<div role="menuitem" data-filter="Page {{ i + 1 }}">
|
||||
{% lucide "file" %}
|
||||
<span>Page {{ i + 1 }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("command-scrollable", code_scrollable | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Command supports document direction. Set <code>dir="rtl"</code> on the command root or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl">
|
||||
{% call command(id="command-rtl", placeholder="اكتب أمراً أو ابحث...") %}
|
||||
<div role="group" aria-labelledby="command-rtl-suggestions">
|
||||
<span role="heading" id="command-rtl-suggestions">اقتراحات</span>
|
||||
<div role="menuitem" data-filter="Calendar">
|
||||
{% lucide "calendar" %}
|
||||
<span>التقويم</span>
|
||||
<span data-shortcut>⌘K</span>
|
||||
</div>
|
||||
<div role="menuitem" data-filter="Settings">
|
||||
{% lucide "settings" %}
|
||||
<span>الإعدادات</span>
|
||||
<span data-shortcut>⌘S</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("command-rtl", code_rtl | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.command</code></dt>
|
||||
<dd>Root command menu.</dd>
|
||||
<dt><code class="highlight language-html">.command-dialog</code></dt>
|
||||
<dd>Native dialog variant. Closes automatically when an item is clicked unless that item has <code>data-keep-command-open</code>.</dd>
|
||||
<dt><code class="highlight language-html">data-filter</code></dt>
|
||||
<dd>Overrides item text used by the filter.</dd>
|
||||
<dt><code class="highlight language-html">data-keywords</code></dt>
|
||||
<dd>Additional whitespace- or comma-separated search terms.</dd>
|
||||
<dt><code class="highlight language-html">data-force</code></dt>
|
||||
<dd>Always keeps an item visible while filtering.</dd>
|
||||
<dt><code class="highlight language-html">data-empty</code></dt>
|
||||
<dd>Custom empty-state text on the menu element.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
+168
-58
@@ -8,13 +8,25 @@ toc:
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML structure
|
||||
id: usage-html-structure
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Scrollable content
|
||||
- label: Custom Close Button
|
||||
id: example-custom-close-button
|
||||
- label: No Close Button
|
||||
id: example-no-close-button
|
||||
- label: Sticky Footer
|
||||
id: example-sticky-footer
|
||||
- label: Scrollable Content
|
||||
id: example-scrollable-content
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
@@ -32,17 +44,16 @@ toc:
|
||||
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">
|
||||
<form class="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/>
|
||||
<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 for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
<label class="label" for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input class="input" type="text" value="@peduarte" id="demo-dialog-edit-profile-username">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
@@ -52,41 +63,42 @@ toc:
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat uses the native <code><dialog></code> element and <code>showModal()</code>. This differs from shadcn/ui's portalled Base UI implementation, but preserves native modality, focus handling, and inert page content without component JavaScript.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, class="") }}
|
||||
|
||||
<h3 id="usage-html-structure"><a href="#usage-html-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button"></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"><button type="button" onclick="dialog.showModal()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code class="highlight language-html"><dialog class="dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>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>
|
||||
<dd>Native modal dialog. Add <code>aria-labelledby="{ TITLE_ID }"</code> and <code>aria-describedby="{ DESCRIPTION_ID }"</code> when title and description are present. The macro also adds backdrop-click close handling.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div></code></dt>
|
||||
<dd>Wraps the content of the dialog.
|
||||
<dd>Dialog content surface.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Contains the header for the dialog:
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog header.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></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>
|
||||
<dt><code class="highlight language-html"><h2 id="{ TITLE_ID }"></code></dt>
|
||||
<dd>Dialog title. Reference it from <code>aria-labelledby</code>.</dd>
|
||||
<dt><code class="highlight language-html"><p id="{ DESCRIPTION_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Dialog description. Reference it from <code>aria-describedby</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>For the content.</dd>
|
||||
<dd>Dialog body/content area. Add overflow utilities when the body should scroll.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Usually contains actions.</dd>
|
||||
<dd>Action area. It stacks actions on small screens and aligns them to the end on larger screens.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="this.closest('dialog').close()"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>A button to close the dialog. Alternatively, you can wrap a button in a <code><form method="dialog"></code> element.</dd>
|
||||
<dd>Close button. You can also wrap a button in <code><form method="dialog"></code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -101,7 +113,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -115,63 +127,161 @@ toc:
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set code_html %}
|
||||
{% set footer %}
|
||||
{% set raw_code %}{% raw %}{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
id="dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
description="Make changes to your profile here.",
|
||||
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%}
|
||||
<form class="grid gap-4">...</form>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-scrollable-content"><a href="#example-scrollable-content">Scrollable content</a></h3>
|
||||
<h3 id="example-custom-close-button"><a href="#example-custom-close-button">Custom Close Button</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Continue</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-custom-close",
|
||||
title="Custom close button",
|
||||
description="The default close icon is hidden and the footer provides the close action.",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
close_button=false,
|
||||
footer=footer
|
||||
) %}
|
||||
<p class="text-sm text-muted-foreground">Use <code>close_button=false</code> and add a close control where it fits your layout.</p>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-custom-close", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-no-close-button"><a href="#example-no-close-button">No Close Button</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn" onclick="this.closest('dialog').close()">I understand</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-no-close",
|
||||
title="No close button",
|
||||
description="The dialog can still be closed with Escape or a custom action.",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
close_button=false,
|
||||
footer=footer
|
||||
) %}
|
||||
<p class="text-sm text-muted-foreground">Native dialogs remain keyboard-accessible even without the default close icon.</p>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-no-close", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-sticky-footer"><a href="#example-sticky-footer">Sticky Footer</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Save</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-sticky-footer",
|
||||
title="Sticky footer",
|
||||
description="Keep actions visible while the body scrolls.",
|
||||
trigger="Sticky Footer",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer_attrs={"class": "sticky bottom-0"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm text-muted-foreground">
|
||||
{% for i in range(0, 8) %}
|
||||
<p>Dialog content row {{ i + 1 }}. Add enough content to make the body scroll while the footer remains available.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-sticky-footer", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-scrollable-content"><a href="#example-scrollable-content">Scrollable Content</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-example",
|
||||
id="dialog-scrollable",
|
||||
title="Scrollable Content",
|
||||
description="This is a dialog with scrollable content.",
|
||||
description="Long content can scroll inside the dialog body.",
|
||||
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 class="space-y-4 text-sm text-muted-foreground">
|
||||
{% for i in range(0, 10) %}
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh sit amet augue commodo ultrices. Row {{ i + 1 }}.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-scrollable-content", code | prettyHtml) }}
|
||||
{{ code_preview("dialog-scrollable-content", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Dialog positioning and close button placement use logical properties. Set <code>dir="rtl"</code> on the dialog or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">حفظ</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-rtl",
|
||||
title="تعديل الملف الشخصي",
|
||||
description="قم بتحديث بياناتك ثم احفظ التغييرات.",
|
||||
trigger="فتح",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label class="label" for="dialog-rtl-name">الاسم</label>
|
||||
<input class="input" id="dialog-rtl-name" value="ليلى">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-rtl", code | prettyHtml) }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.dialog</code></dt>
|
||||
<dd>Native dialog root. It owns the backdrop through <code>::backdrop</code> and the open state through the native <code>open</code> attribute.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div</code></dt>
|
||||
<dd>Dialog content surface. This maps to shadcn/ui's <code>DialogContent</code>.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > header</code></dt>
|
||||
<dd>Header area. Direct <code>h2</code> and <code>p</code> children are styled as title and description.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > section</code></dt>
|
||||
<dd>Body/content area. Add utilities such as <code>overflow-y-auto</code> when you need scrolling.</dd>
|
||||
<dt><code class="highlight language-html">.dialog > div > footer</code></dt>
|
||||
<dd>Action area. Use Basecoat button classes for actions.</dd>
|
||||
<dt><code class="highlight language-html">close_button=false</code></dt>
|
||||
<dd>Macro option to hide the generated close icon.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -9,7 +9,7 @@ toc:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
- label: "Step 1: Include the JavaScript file"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your dropdown menu HTML"
|
||||
id: usage-html-js-2
|
||||
@@ -22,10 +22,22 @@ toc:
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Shortcuts
|
||||
id: example-shortcuts
|
||||
- label: Icons
|
||||
id: example-icons
|
||||
- label: Checkboxes
|
||||
id: example-checkboxes
|
||||
- label: Radio Group
|
||||
id: example-radio-group
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
@@ -39,50 +51,39 @@ toc:
|
||||
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="group" aria-labelledby="demo-dropdown-account">
|
||||
<div role="heading" id="demo-dropdown-account">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
<span data-shortcut>⇧⌘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>
|
||||
<span data-shortcut>⌘B</span>
|
||||
</div>
|
||||
<div role="menuitem">Settings</div>
|
||||
<div role="menuitem">Keyboard shortcuts</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">
|
||||
GitHub
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Support
|
||||
</div>
|
||||
<div role="menuitem" aria-disabled="true">
|
||||
API
|
||||
</div>
|
||||
<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>
|
||||
<div role="menuitem">Log out</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat dropdown menus are inline-positioned relative to the <code>.dropdown-menu</code> wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and matches Basecoat's current popover/select positioning model.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
@@ -97,7 +98,7 @@ toc:
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -116,43 +117,34 @@ toc:
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="dropdown-menu"></code></dt>
|
||||
<dd>Wraps around the entire component.
|
||||
<dd>Relative wrapper for the trigger and inline menu content.
|
||||
<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>
|
||||
<dt><code class="highlight language-html"><button aria-haspopup="menu" aria-expanded="false"></code></dt>
|
||||
<dd>Trigger button. The script toggles <code>aria-expanded</code> and manages keyboard navigation.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover aria-hidden="true"></code></dt>
|
||||
<dd>Menu content popover. Set <code>data-side="top|right|bottom|left"</code> and <code>data-align="start|center|end"</code> to control placement.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="menu"></code></dt>
|
||||
<dd>Container for menu items, groups, labels, and separators.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related menu items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="heading" id="{ HEADING_ID }"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Group heading/label.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitem"></code></dt>
|
||||
<dd>Standard action item. Use <code>aria-disabled="true"</code> for disabled items.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemcheckbox" aria-checked="true"></code></dt>
|
||||
<dd>Checkbox-style item. Add a child with <code>data-indicator</code> for the checked icon.</dd>
|
||||
<dt><code class="highlight language-html"><div role="menuitemradio" aria-checked="true"></code></dt>
|
||||
<dd>Radio-style item. Add a child with <code>data-indicator</code> for the selected icon.</dd>
|
||||
<dt><code class="highlight language-html"><span data-shortcut></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of the item.</dd>
|
||||
<dt><code class="highlight language-html"><hr role="separator"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Separator between groups or options.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<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>
|
||||
@@ -163,9 +155,9 @@ toc:
|
||||
<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>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the 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>
|
||||
<dd>When the menu opens, the component dispatches a custom event on <code>document</code>. Other popover-based components listen for this to close any open popovers.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -176,7 +168,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -198,128 +190,111 @@ toc:
|
||||
) %}
|
||||
<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 role="menuitem">Profile</div>
|
||||
<div role="menuitem">Billing</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%}
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem">Subscription</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-checkboxes"><a href="#example-checkboxes">Checkboxes</a></h3>
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}{% call dropdown_menu(
|
||||
id="dropdown-menu-checkboxes",
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-basic",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="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 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="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"
|
||||
} %}
|
||||
<div role="menuitem">Team</div>
|
||||
<div role="menuitem" aria-disabled="true">API</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-basic", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-shortcuts"><a href="#example-shortcuts">Shortcuts</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-shortcuts",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">New Tab <span data-shortcut>⌘T</span></div>
|
||||
<div role="menuitem">New Window <span data-shortcut>⌘N</span></div>
|
||||
<div role="menuitem">New Private Window <span data-shortcut>⇧⌘N</span></div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">Print <span data-shortcut>⌘P</span></div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-shortcuts", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-icons"><a href="#example-icons">Icons</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-icons",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">{% lucide "user" %} Profile</div>
|
||||
<div role="menuitem">{% lucide "credit-card" %} Billing</div>
|
||||
<div role="menuitem">{% lucide "settings" %} Settings</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem">{% lucide "log-out" %} Log out</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-icons", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-checkboxes"><a href="#example-checkboxes">Checkboxes</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="dropdown-checkboxes-label">
|
||||
<div role="heading" id="dropdown-checkboxes-label">Appearance</div>
|
||||
<div role="menuitemcheckbox" aria-checked="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
<span data-shortcut>⌘S</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"
|
||||
} %}
|
||||
<div role="menuitemcheckbox" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
<span data-shortcut>⌘A</span>
|
||||
</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
<div role="menuitemcheckbox" aria-checked="false" aria-disabled="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
<span data-shortcut>⌘P</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 => {
|
||||
const dropdownMenu = document.querySelector('#dropdown-checkboxes');
|
||||
const checkboxes = dropdownMenu.querySelectorAll('[role="menuitemcheckbox"]');
|
||||
checkboxes.forEach((checkbox) => {
|
||||
checkbox.addEventListener('click', () => {
|
||||
const isChecked = checkbox.getAttribute('aria-checked') === 'true';
|
||||
checkbox.setAttribute('aria-checked', !isChecked);
|
||||
if (checkbox.getAttribute('aria-disabled') === 'true') return;
|
||||
checkbox.setAttribute('aria-checked', checkbox.getAttribute('aria-checked') !== 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
@@ -329,75 +304,111 @@ toc:
|
||||
|
||||
<h3 id="example-radio-group"><a href="#example-radio-group">Radio Group</a></h3>
|
||||
|
||||
{% set code %}{% call dropdown_menu(
|
||||
id="dropdown-menu-radio-group",
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-radio-group",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="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 role="group" aria-labelledby="dropdown-radio-label">
|
||||
<div role="heading" id="dropdown-radio-label">Panel Position</div>
|
||||
<div role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Top
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="true"
|
||||
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 role="menuitemradio" aria-checked="true">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Bottom
|
||||
</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 role="menuitemradio" aria-checked="false">
|
||||
<span data-indicator>{% lucide "check" %}</span>
|
||||
Right
|
||||
</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');
|
||||
const dropdownMenu = document.querySelector('#dropdown-radio-group');
|
||||
const radios = dropdownMenu.querySelectorAll('[role="menuitemradio"]');
|
||||
radios.forEach((radio) => {
|
||||
radio.addEventListener('click', () => {
|
||||
radios.forEach((item) => item.setAttribute('aria-checked', 'false'));
|
||||
radio.setAttribute('aria-checked', 'true');
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu", code | prettyHtml) }}
|
||||
{{ code_preview("dropdown-menu-radio-group", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-destructive",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="menuitem">Edit</div>
|
||||
<div role="menuitem">Duplicate</div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" data-variant="destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Delete
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-destructive", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Dropdown menus support document direction. Set <code>dir="rtl"</code> on the dropdown root or a parent element. Use logical alignment data attributes where possible.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-rtl",
|
||||
trigger="فتح",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "min-w-56", "data-align": "end"}
|
||||
) %}
|
||||
<div role="heading" id="dropdown-rtl-label">الحساب</div>
|
||||
<div role="menuitem">الملف الشخصي <span data-shortcut>⌘P</span></div>
|
||||
<div role="menuitem">الإعدادات <span data-shortcut>⌘S</span></div>
|
||||
<hr role="separator">
|
||||
<div role="menuitem" data-variant="destructive">حذف</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("dropdown-menu-rtl", code | prettyHtml) }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.dropdown-menu</code></dt>
|
||||
<dd>Root component class. Must wrap the trigger and the <code>[data-popover]</code> content.</dd>
|
||||
<dt><code class="highlight language-html">[data-popover]</code></dt>
|
||||
<dd>Inline menu popover. Supports <code>data-side</code> and <code>data-align</code> through the shared popover positioning rules.</dd>
|
||||
<dt><code class="highlight language-html">[role="menu"]</code></dt>
|
||||
<dd>Menu container referenced by the trigger's <code>aria-controls</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitem"]</code></dt>
|
||||
<dd>Action item. Supports <code>aria-disabled="true"</code>, <code>data-inset</code>, and <code>data-variant="destructive"</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitemcheckbox"]</code></dt>
|
||||
<dd>Checkbox item. Use <code>aria-checked="true|false"</code> and an optional <code>[data-indicator]</code> child.</dd>
|
||||
<dt><code class="highlight language-html">[role="menuitemradio"]</code></dt>
|
||||
<dd>Radio item. Use <code>aria-checked="true|false"</code> and an optional <code>[data-indicator]</code> child.</dd>
|
||||
<dt><code class="highlight language-html">[role="heading"]</code></dt>
|
||||
<dd>Group label. Supports <code>data-inset</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="separator"]</code></dt>
|
||||
<dd>Visual separator between groups or actions.</dd>
|
||||
<dt><code class="highlight language-html">[data-shortcut]</code></dt>
|
||||
<dd>Shortcut hint aligned to the inline end of a menu item.</dd>
|
||||
<dt><code class="highlight language-html">[data-indicator]</code></dt>
|
||||
<dd>Indicator slot for checkbox and radio menu items. It becomes visible when the item has <code>aria-checked="true"</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
+115
-79
@@ -5,6 +5,11 @@ description: Display empty states with icons, titles, descriptions, and actions.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML structure
|
||||
id: usage-html-structure
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
@@ -16,38 +21,33 @@ toc:
|
||||
id: example-avatar-group
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Empty component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code_default %}
|
||||
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
|
||||
<header class="flex max-w-sm flex-col items-center gap-2 text-center">
|
||||
<div class="mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6">
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "folder-code" %}
|
||||
</div>
|
||||
<h3 class="text-lg font-medium tracking-tight">No Projects Yet</h3>
|
||||
<p class="text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4">
|
||||
You haven't created any projects yet. Get started by creating your first project.
|
||||
</p>
|
||||
</figure>
|
||||
<h3>No Projects Yet</h3>
|
||||
<p>You haven't created any projects yet. Get started by creating your first project.</p>
|
||||
</header>
|
||||
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance">
|
||||
<footer>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn">Create Project</button>
|
||||
<button class="btn-outline">Import Project</button>
|
||||
</div>
|
||||
</section>
|
||||
<a href="#" class="inline-flex items-center justify-center whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive underline-offset-4 hover:underline h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5 text-muted-foreground">
|
||||
Learn More
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</div>
|
||||
<a href="#" class="btn-link text-muted-foreground">
|
||||
Learn More
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-default", code_default) }}
|
||||
@@ -55,8 +55,37 @@ toc:
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Empty states are pure HTML composition using Tailwind utility classes. Use flex layout utilities to center content, and compose with semantic elements like <code><header></code>, <code><h3></code>, <code><p></code>, and <code><button></code>.</p>
|
||||
<p>Browse the examples below and copy the pattern that fits your needs.</p>
|
||||
<p>Use <code>.empty</code> on a semantic container. The component styles common children like <code><header></code>, <code><figure></code>, headings, descriptions, and <code><footer></code>.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_default) }}
|
||||
|
||||
<h3 id="usage-html-structure"><a href="#usage-html-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><section class="empty"></code></dt>
|
||||
<dd>The empty-state container.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code></dt>
|
||||
<dd>Groups the media, title, and description.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><figure></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Icon, avatar, image, or avatar group. An icon-only figure is styled automatically with <code>:has(> svg:only-child)</code>.</dd>
|
||||
<dt><code class="highlight language-html"><h2></code>, <code class="highlight language-html"><h3></code>, or <code class="highlight language-html"><h4></code></dt>
|
||||
<dd>The empty-state title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The empty-state description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><section></code> or <code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Actions and supporting content.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
@@ -64,20 +93,18 @@ toc:
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code_outline %}
|
||||
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300 border">
|
||||
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
|
||||
<div class="mb-2 bg-muted text-foreground flex size-12 shrink-0 items-center justify-center rounded-lg [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-6">
|
||||
<section class="empty border">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "cloud" %}
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold tracking-tight">Cloud Storage Empty</h3>
|
||||
<p class="text-muted-foreground text-sm/relaxed">
|
||||
Upload files to your cloud storage to access them anywhere.
|
||||
</p>
|
||||
</figure>
|
||||
<h3>Cloud Storage Empty</h3>
|
||||
<p>Upload files to your cloud storage to access them anywhere.</p>
|
||||
</header>
|
||||
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
|
||||
<footer>
|
||||
<button class="btn-sm-outline">Upload Files</button>
|
||||
</section>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-outline", code_outline) }}
|
||||
@@ -85,20 +112,18 @@ toc:
|
||||
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
|
||||
|
||||
{% set code_avatar %}
|
||||
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
|
||||
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
|
||||
<div class="mb-2 flex items-center justify-center">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png" class="grayscale size-12 shrink-0 object-cover rounded-full">
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold tracking-tight">User Offline</h3>
|
||||
<p class="text-muted-foreground text-sm/relaxed">
|
||||
This user is currently offline. You can leave a message to notify them or try again later.
|
||||
</p>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="size-12 rounded-full">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
</figure>
|
||||
<h3>User Offline</h3>
|
||||
<p>This user is currently offline. You can leave a message or try again later.</p>
|
||||
</header>
|
||||
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
|
||||
<footer>
|
||||
<button class="btn-sm">Leave Message</button>
|
||||
</section>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-avatar", code_avatar) }}
|
||||
@@ -106,27 +131,23 @@ toc:
|
||||
<h3 id="example-avatar-group"><a href="#example-avatar-group">Avatar Group</a></h3>
|
||||
|
||||
{% set code_avatar_group %}
|
||||
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
|
||||
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
|
||||
<div class="mb-2 flex items-center justify-center">
|
||||
<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>
|
||||
<h3 class="text-lg font-semibold tracking-tight">No Team Members</h3>
|
||||
<p class="text-muted-foreground text-sm/relaxed">
|
||||
Invite your team to collaborate on this project.
|
||||
</p>
|
||||
<section class="empty">
|
||||
<header>
|
||||
<figure class="-space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-12 [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</figure>
|
||||
<h3>No Team Members</h3>
|
||||
<p>Invite your team to collaborate on this project.</p>
|
||||
</header>
|
||||
<section class="flex w-full max-w-sm min-w-0 flex-col items-center gap-3">
|
||||
<button class="btn-sm inline-flex items-center gap-2">
|
||||
<footer>
|
||||
<button class="btn-sm">
|
||||
{% lucide "plus" %}
|
||||
Invite Members
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-avatar-group", code_avatar_group) }}
|
||||
@@ -134,24 +155,39 @@ toc:
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
{% set code_input_group %}
|
||||
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg p-6 text-center text-balance md:p-12 text-neutral-800 dark:text-neutral-300">
|
||||
<header class="flex max-w-sm flex-col items-center gap-3 text-center">
|
||||
<h3 class="text-lg font-semibold tracking-tight">404 - Not Found</h3>
|
||||
<p class="text-muted-foreground text-sm/relaxed">
|
||||
The page you're looking for doesn't exist. Try searching for what you need below.
|
||||
</p>
|
||||
<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>
|
||||
<section class="flex w-full max-w-md min-w-0 flex-col items-center gap-3">
|
||||
<div class="relative w-3/4">
|
||||
<input type="text" class="input pl-8.5 pr-10" placeholder="Try searching for pages...">
|
||||
{% lucide "search" , { "class": "absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground pointer-events-none shrink-0 size-4" } %}
|
||||
<kbd class="kbd absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">/</kbd>
|
||||
<footer>
|
||||
<div class="input-group w-full">
|
||||
{% lucide "search" %}
|
||||
<input type="search" placeholder="Try searching for pages...">
|
||||
<kbd class="kbd">/</kbd>
|
||||
</div>
|
||||
<p class="text-muted-foreground text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary">
|
||||
Need help? <a href="#">Contact support</a>
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
<p>Need help? <a href="#">Contact support</a></p>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-input-group", code_input_group) }}
|
||||
{{ code_preview("empty-input-group", code_input_group) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code_rtl %}
|
||||
<section class="empty" dir="rtl">
|
||||
<header>
|
||||
<figure>
|
||||
{% lucide "folder-code" %}
|
||||
</figure>
|
||||
<h3>لا توجد مشاريع بعد</h3>
|
||||
<p>ابدأ بإنشاء مشروعك الأول.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn-sm">إنشاء مشروع</button>
|
||||
</footer>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("empty-rtl", code_rtl) }}
|
||||
|
||||
@@ -14,6 +14,8 @@ toc:
|
||||
id: example-textarea
|
||||
- label: Select
|
||||
id: example-select
|
||||
- label: Slider
|
||||
id: example-slider
|
||||
- label: Fieldset
|
||||
id: example-fieldset
|
||||
- label: Checkbox
|
||||
@@ -22,8 +24,14 @@ toc:
|
||||
id: example-radio
|
||||
- label: Switch
|
||||
id: example-switch
|
||||
- label: Choice Card
|
||||
id: example-choice-card
|
||||
- label: Field Group
|
||||
id: example-field-group
|
||||
- label: Responsive Layout
|
||||
id: example-responsive
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
@@ -292,25 +300,25 @@ toc:
|
||||
<p>Select the items you want to show on the desktop.</p>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="field">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
Hard disks
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
External disks
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
CDs, DVDs and iPods
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="checkbox">
|
||||
Connected servers
|
||||
@@ -320,7 +328,7 @@ toc:
|
||||
|
||||
<hr role="separator">
|
||||
|
||||
<div class="field" data-orientation="horizontal">
|
||||
<div role="group" class="field" data-orientation="horizontal">
|
||||
{# <div class="flex w-full items-center gap-3"> #}
|
||||
<input id="sync-desktop-documents" type="checkbox" checked class="input self-start">
|
||||
<section>
|
||||
@@ -342,19 +350,19 @@ toc:
|
||||
<p>Yearly and lifetime plans offer significant savings.</p>
|
||||
|
||||
<div role="radiogroup" class="grid gap-3">
|
||||
<div class="field">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan" checked>
|
||||
Monthly ($9.99/month)
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan">
|
||||
Yearly ($99.99/year)
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div role="group" class="field">
|
||||
<label class="gap-3">
|
||||
<input type="radio" name="subscription-plan">
|
||||
Lifetime ($299.99)
|
||||
@@ -414,6 +422,34 @@ toc:
|
||||
|
||||
{{ code_preview("field-choice-card", code_choice_card, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-field-group"><a href="#example-field-group">Field Group</a></h3>
|
||||
|
||||
{% set code_field_group %}
|
||||
<fieldset class="fieldset">
|
||||
<legend>Account</legend>
|
||||
<p>Use a field group to stack related fields with consistent spacing.</p>
|
||||
|
||||
<div role="group" aria-label="Field group">
|
||||
<div role="group" class="field">
|
||||
<label for="field-group-name">Name</label>
|
||||
<input id="field-group-name" type="text" placeholder="Evil Rabbit">
|
||||
</div>
|
||||
|
||||
<div class="field-separator">
|
||||
<hr role="separator">
|
||||
<span>Optional</span>
|
||||
</div>
|
||||
|
||||
<div role="group" class="field">
|
||||
<label for="field-group-bio">Bio</label>
|
||||
<textarea id="field-group-bio" rows="3" placeholder="Tell us about yourself"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("field-group", code_field_group, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-responsive"><a href="#example-responsive">Responsive Layout</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
@@ -456,4 +492,27 @@ toc:
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("field-responsive", code_responsive, class="w-full max-w-3xl") }}
|
||||
{{ code_preview("field-responsive", code_responsive, class="w-full max-w-3xl") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Fields support right-to-left layouts through native form direction and logical spacing. Add <code>dir="rtl"</code> to the field, fieldset, or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}
|
||||
<fieldset class="fieldset" dir="rtl">
|
||||
<legend>الملف الشخصي</legend>
|
||||
<p>تظهر هذه المعلومات في حسابك.</p>
|
||||
|
||||
<div role="group" aria-label="Field group">
|
||||
<div role="group" class="field">
|
||||
<label for="field-rtl-name">الاسم الكامل</label>
|
||||
<input id="field-rtl-name" type="text" placeholder="أرنب شرير">
|
||||
<p>اكتب اسمك الأول والأخير.</p>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("field-rtl", code_rtl, class="w-full max-w-md") }}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Form
|
||||
description: Building forms with Basecoat components.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<form class="form grid 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-textarea">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="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>
|
||||
|
||||
<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" checked>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>
|
||||
|
||||
<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">Security emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about your account security.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch-disabled" role="switch" disabled>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Adding the <code>form</code> class to the parent of any form element (<code><label></code>, <code><input></code>, <code><textarea></code>, <code><select></code>) will automatically apply the style you would get from adding <code>label</code>, <code>input</code>, <code>textarea</code>, or <code>select</code> classes. Read more about <a href="/components/label">Label</a>, <a href="/components/input">Input</a>, <a href="/components/textarea">Textarea</a>, and <a href="/components/select">Select</a>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
+368
-241
@@ -1,329 +1,364 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Input Group
|
||||
description: Display additional information or actions to an input or textarea.
|
||||
description: Add icons, text, buttons, and helper content to inputs and textareas.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Align
|
||||
id: example-align
|
||||
children:
|
||||
- label: inline-start
|
||||
id: align-inline-start
|
||||
- label: inline-end
|
||||
id: align-inline-end
|
||||
- label: block-start
|
||||
id: align-block-start
|
||||
- label: block-end
|
||||
id: align-block-end
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: Text
|
||||
id: example-text
|
||||
- label: Button
|
||||
id: example-button
|
||||
- label: Tooltip
|
||||
id: example-tooltip
|
||||
- label: Textarea
|
||||
id: example-textarea
|
||||
- label: Spinner
|
||||
id: example-spinner
|
||||
- label: Kbd
|
||||
id: example-kbd
|
||||
- label: Dropdown
|
||||
id: example-dropdown
|
||||
- label: Group
|
||||
id: example-group
|
||||
- label: Spinner
|
||||
id: example-spinner
|
||||
- label: Textarea
|
||||
id: example-textarea
|
||||
- label: Custom Input
|
||||
id: example-custom-input
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Input Group component in Basecoat.</h2>
|
||||
</div>
|
||||
{% set spinner_icon %}{% lucide "loader-circle", { "role": "status", "aria-label": "Loading", "class": "animate-spin" } %}{% endset %}
|
||||
|
||||
{% set code_default %}
|
||||
<div class="grid gap-6">
|
||||
<div class="relative">
|
||||
<input type="text" class="input pl-9 pr-20" placeholder="Search...">
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
|
||||
{% lucide "search" %}
|
||||
</div>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">12 results</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pl-15 pr-9" placeholder="example.com">
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">https://</div>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="This is content in a tooltip.">{% lucide "info" %}</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com">
|
||||
<span data-align="start">https://</span>
|
||||
<button type="button" class="btn-xs-icon-ghost rounded-full" data-align="end" aria-label="Info">
|
||||
{% lucide "info" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<textarea class="textarea pr-10 min-h-27 pb-12" placeholder="Ask, Search or Chat..."></textarea>
|
||||
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
|
||||
<button type="button" class="btn-icon-outline rounded-full size-6 text-muted-foreground hover:text-accent-foreground">
|
||||
{% lucide "plus" %}
|
||||
</button>
|
||||
<div class="input-group min-h-32" data-orientation="vertical">
|
||||
<textarea placeholder="Ask, Search or Chat..."></textarea>
|
||||
<footer data-align="end" role="group" aria-label="Message actions">
|
||||
<button type="button" class="btn-xs-icon-outline rounded-full" aria-label="Add attachment">{% lucide "plus" %}</button>
|
||||
{% set trigger %}Auto{% endset %}
|
||||
{{ dropdown_menu(
|
||||
id="demo-dropdown-menu",
|
||||
trigger="Auto",
|
||||
trigger_attrs={"class": "btn-sm-ghost text-muted-foreground hover:text-accent-foreground h-6 p-2"},
|
||||
popover_attrs={"data-side": "top", "class": "min-w-32"},
|
||||
main_attrs={"class": "min-w-0"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-xs-ghost"},
|
||||
popover_attrs={"data-side": "top", "data-align": "start", "class": "min-w-28 [--radius:0.95rem]"},
|
||||
items=[
|
||||
{ type: "item", label: "Auto" },
|
||||
{ type: "item", label: "Agent" },
|
||||
{ type: "item", label: "Manual" }
|
||||
]
|
||||
) }}
|
||||
<div class="text-muted-foreground text-sm ml-auto">52% used</div>
|
||||
<hr class="w-0 h-4 border-r border-border shrink-0 m-0">
|
||||
<button type="button" class="btn-icon rounded-full size-6 bg-muted-foreground hover:bg-foreground" disabled>
|
||||
{% lucide "arrow-up" %}
|
||||
</button>
|
||||
<span class="ms-auto">52% used</span>
|
||||
<hr role="separator" class="h-4">
|
||||
<button type="button" class="btn-xs-icon rounded-full" disabled aria-label="Send">{% lucide "arrow-up" %}</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-9" placeholder="@shadcn">
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full [&>svg]:size-3">{% lucide "check" %}</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="@shadcn">
|
||||
<span data-align="end" aria-hidden="true">
|
||||
<span class="flex size-4 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||
{% lucide "check", { "class": "size-3" } %}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-default", code_default, class="w-full max-w-sm") }}
|
||||
{{ code_preview("input-group-default", code_default | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
{% set code_usage %}
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">
|
||||
<!-- icon -->
|
||||
</span>
|
||||
<span data-align="end">12 results</span>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_block(code_usage | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Input groups are pure HTML composition using Tailwind utility classes. There is no dedicated component: use <code>relative</code> positioning on a wrapper, add your content with <code>absolute</code> positioning, and adjust input padding to make room.</p>
|
||||
<p>Browse the examples below and copy the pattern that fits your needs.</p>
|
||||
<p>Use <code>.input-group</code> on the root. The group owns the visible shell. Native controls stay semantic and become borderless children inside that shell.</p>
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="input-group"></code></dt>
|
||||
<dd>The root shell. Add <code>role="group"</code> and an accessible label only when the group itself needs to be named.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input></code>, <code class="highlight language-html"><textarea></code>, <code class="highlight language-html"><select></code></dt>
|
||||
<dd>The native control. Use <code>aria-invalid="true"</code> for invalid state and <code>disabled</code> for disabled state.</dd>
|
||||
<dt><code class="highlight language-html"><span></code>, <code class="highlight language-html"><svg></code>, <code class="highlight language-html"><kbd></code></dt>
|
||||
<dd>Decorative or helper content. Use <code>aria-hidden="true"</code> for decorative icons.</dd>
|
||||
<dt><code class="highlight language-html"><button></code>, <code class="highlight language-html"><div role="group"></code></dt>
|
||||
<dd>Interactive actions. Use <code>role="group"</code> with an accessible label when multiple related actions share one side of the input.</dd>
|
||||
<dt><code class="highlight language-html"><header></code>, <code class="highlight language-html"><footer></code></dt>
|
||||
<dd>Header or footer addon rows for vertically oriented input groups.</dd>
|
||||
<dt><code class="highlight language-html">data-align="start|end"</code></dt>
|
||||
<dd>Optional visual placement. Values use logical direction, so start/end automatically flip in RTL. Use <code>data-orientation="vertical"</code> on the root for header/footer layouts.</dd>
|
||||
<dt><code class="highlight language-html">[data-control]</code></dt>
|
||||
<dd>Escape hatch for custom controls that are not native <code>input</code>, <code>textarea</code>, or <code>select</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-align"><a href="#example-align">Align</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>data-align="start|end"</code> on addon wrappers to position them inline. Use <code>data-orientation="vertical"</code> with <code>header</code> or <code>footer</code> for block placement.</p>
|
||||
</section>
|
||||
|
||||
<h4 id="align-inline-start"><a href="#align-inline-start">inline-start</a></h4>
|
||||
|
||||
{% set code_align_inline_start %}
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-start-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-start-input" type="text" placeholder="Search...">
|
||||
<span role="group" data-align="start" aria-hidden="true">
|
||||
{% lucide "search", { "class": "text-muted-foreground" } %}
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the start.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-inline-start", code_align_inline_start | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-inline-end"><a href="#align-inline-end">inline-end</a></h4>
|
||||
|
||||
{% set code_align_inline_end %}
|
||||
<div class="field max-w-sm">
|
||||
<label for="inline-end-input">Input</label>
|
||||
<div class="input-group">
|
||||
<input id="inline-end-input" type="password" placeholder="Enter password">
|
||||
<span role="group" data-align="end" aria-hidden="true">
|
||||
{% lucide "eye-off" %}
|
||||
</span>
|
||||
</div>
|
||||
<p>Icon positioned at the end.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-inline-end", code_align_inline_end | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-block-start"><a href="#align-block-start">block-start</a></h4>
|
||||
|
||||
{% set code_align_block_start %}
|
||||
<div role="group" aria-label="Block start input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-start-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-start-input" type="text" placeholder="Enter your name">
|
||||
<header data-align="start">
|
||||
<span>Full Name</span>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the input.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="block-start-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-start-textarea" class="font-mono text-sm" placeholder="console.log('Hello, world!');"></textarea>
|
||||
<header data-align="start">
|
||||
{% lucide "file-code", { "class": "text-muted-foreground" } %}
|
||||
<span class="font-mono">script.js</span>
|
||||
<button type="button" class="btn-xs-icon-ghost ml-auto" aria-label="Copy">
|
||||
{% lucide "copy" %}
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
<p>Header positioned above the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-block-start", code_align_block_start | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h4 id="align-block-end"><a href="#align-block-end">block-end</a></h4>
|
||||
|
||||
{% set code_align_block_end %}
|
||||
<div role="group" aria-label="Block end input group examples" class="fieldset max-w-sm">
|
||||
<div class="field">
|
||||
<label for="block-end-input">Input</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<input id="block-end-input" type="text" placeholder="Enter amount">
|
||||
<footer data-align="end">
|
||||
<span>USD</span>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the input.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="block-end-textarea">Textarea</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="block-end-textarea" placeholder="Write a comment..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>0/280</span>
|
||||
<button type="button" class="btn-sm ml-auto">Post</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>Footer positioned below the textarea.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-block-end", code_align_block_end | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code_icon %}
|
||||
<div class="grid gap-6">
|
||||
<div class="relative">
|
||||
<input type="text" class="input pl-9" placeholder="Search...">
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
|
||||
{% lucide "search" %}
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="email" class="input pl-9" placeholder="Enter your email">
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
|
||||
{% lucide "mail" %}
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="email" placeholder="Enter your email">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "mail" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input px-9" placeholder="Card number">
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
|
||||
{% lucide "credit-card" %}
|
||||
</div>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4">
|
||||
{% lucide "check" %}
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "credit-card" %}</span>
|
||||
<span data-align="end" aria-hidden="true">{% lucide "check" %}</span>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-16" placeholder="Card number">
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 flex items-center gap-2">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Card number">
|
||||
<span data-align="end" aria-hidden="true">
|
||||
{% lucide "star" %}
|
||||
{% lucide "info" %}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-icon-prefix", code_icon, class="w-full max-w-sm") }}
|
||||
{{ code_preview("input-group-icon", code_icon | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-text"><a href="#example-text">Text</a></h3>
|
||||
|
||||
{% set code_text %}
|
||||
<div class="grid gap-6">
|
||||
<div class="relative">
|
||||
<input type="text" class="input pl-7 pr-12" placeholder="0.00">
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">$</div>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">USD</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="0.00">
|
||||
<span data-align="start">$</span>
|
||||
<span data-align="end">USD</span>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pl-15 pr-12" placeholder="example.com">
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">https://</div>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">.com</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="example.com">
|
||||
<span data-align="start">https://</span>
|
||||
<span data-align="end">.com</span>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-30" placeholder="Enter your username">
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground text-sm">@company.com</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter your username">
|
||||
<span data-align="end">@company.com</span>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<textarea class="textarea pr-10 min-h-25 pb-12" placeholder="Enter your message"></textarea>
|
||||
<footer role="group" class="absolute bottom-0 px-3 pb-3 pt-1.5 flex items-center w-full gap-2">
|
||||
<div class="text-muted-foreground text-sm">120 characters left</div>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea placeholder="Enter your message"></textarea>
|
||||
<footer data-align="end">
|
||||
<span class="text-xs text-muted-foreground">120 characters left</span>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-text", code_text, class="w-full max-w-sm") }}
|
||||
{{ code_preview("input-group-text", code_text | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-button"><a href="#example-button">Button</a></h3>
|
||||
|
||||
{% set code_button %}
|
||||
<div class="grid gap-6">
|
||||
<div class="relative">
|
||||
<input type="text" readonly class="input pr-9" value="https://x.com/hunvreus">
|
||||
<button
|
||||
data-copied="false"
|
||||
onclick="
|
||||
this.dataset.copied = 'true';
|
||||
setTimeout(() => this.dataset.copied = 'false', 2000);
|
||||
"
|
||||
class="group absolute right-1.5 top-1/2 -translate-y-1/2 btn-icon-ghost text-muted-foreground hover:text-accent-foreground size-6"
|
||||
>
|
||||
{% lucide "copy", { "class": "group-data-[copied=true]:hidden" } %}
|
||||
{% lucide "check", { "class": "hidden group-data-[copied=true]:block" } %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pl-21 pr-9 rounded-full">
|
||||
<button
|
||||
aria-pressed="false"
|
||||
onclick="this.ariaPressed = this.ariaPressed === 'true' ? 'false' : 'true'"
|
||||
class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-icon-ghost text-muted-foreground hover:text-accent-foreground size-6 rounded-full aria-pressed:bg-transparent aria-pressed:hover:bg-accent dark:aria-pressed:hover:bg-accent/50 aria-pressed:text-blue-600 aria-pressed:[&>svg]:fill-blue-600"
|
||||
>{% lucide "star" %}</button>
|
||||
<div class="absolute left-1.5 top-1/2 -translate-y-1/2 flex items-center gap-1 z-10">
|
||||
{% set trigger %}
|
||||
{% lucide "info" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-sm-icon-ghost rounded-full size-6"},
|
||||
popover_attrs={"class": "max-w-72"}
|
||||
) %}
|
||||
<h3 class="font-medium mb-1">Your connection is not secure.</h3>
|
||||
<p>You should not enter any sensitive information on this site.</p>
|
||||
{% endcall %}
|
||||
<div class="text-muted-foreground text-sm pointer-events-none">https://</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" readonly class="input pr-9" placeholder="Type to search...">
|
||||
<button type="button" class="absolute right-1.5 top-1/2 -translate-y-1/2 btn-secondary h-6 p-2 rounded">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-button", code_button, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-tooltip"><a href="#example-tooltip">Tooltip</a></h3>
|
||||
|
||||
{% set code_tooltip %}
|
||||
<div class="grid gap-6">
|
||||
<div class="relative">
|
||||
<input type="password" class="input pr-9" placeholder="Enter password">
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="Password must be at least 8 characters long.">{% lucide "info" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="email" class="input pr-9" placeholder="Your email address">
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="We'll use this to send your notifications.">{% lucide "circle-help" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="email" class="input pl-9" placeholder="Your email address">
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4" data-tooltip="Click for help with API keys." data-side="left">{% lucide "circle-help" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-tooltip", code_tooltip, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
|
||||
|
||||
{% set code_textarea %}
|
||||
<div class="relative">
|
||||
<textarea class="textarea pt-15 pb-17 min-h-77" placeholder="console.log('Hello, world!')."></textarea>
|
||||
<header class="absolute top-0 flex items-center w-full gap-2 p-3 border-b">
|
||||
<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="tabler-icon tabler-icon-brand-javascript size-4 text-muted-foreground"><path d="M20 4l-2 14.5l-6 2l-6 -2l-2 -14.5z"></path><path d="M7.5 8h3v8l-2 -1"></path><path d="M16.5 8h-2.5a.5 .5 0 0 0 -.5 .5v3a.5 .5 0 0 0 .5 .5h1.423a.5 .5 0 0 1 .495 .57l-.418 2.93l-2 .5"></path></svg>
|
||||
<span class="font-mono text-sm text-muted-foreground mr-auto">script.js</span>
|
||||
<button class="btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6">
|
||||
{% lucide "refresh-ccw" %}
|
||||
</button>
|
||||
<button class="btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6">
|
||||
<div class="input-group">
|
||||
<input type="text" readonly placeholder="https://x.com/shadcn">
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Copy" title="Copy">
|
||||
{% lucide "copy" %}
|
||||
</button>
|
||||
</header>
|
||||
<footer class="absolute bottom-0 flex items-center w-full gap-2 p-3 border-t">
|
||||
<span class="text-sm text-muted-foreground mr-auto">Line 1, Column 1</span>
|
||||
<button type="button" class="btn-sm">
|
||||
Run
|
||||
{% lucide "corner-down-left" %}
|
||||
</div>
|
||||
|
||||
<div class="input-group [--radius:9999px]">
|
||||
<button type="button" class="btn-xs-icon-secondary" data-align="start" aria-label="Connection information">
|
||||
{% lucide "info" %}
|
||||
</button>
|
||||
<span data-align="start" class="text-muted-foreground">https://</span>
|
||||
<input type="text" id="input-secure-19">
|
||||
<button type="button" class="btn-xs-icon-ghost" data-align="end" aria-label="Favorite">
|
||||
{% lucide "star" %}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-textarea", code_textarea, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
|
||||
|
||||
{% set code_spinner %}
|
||||
<div class="grid gap-6">
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-9" placeholder="Searching..." disabled>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
|
||||
{% lucide "loader-circle", { "class": "animate-spin" } %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pl-9" placeholder="Processing..." disabled>
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
|
||||
{% lucide "loader-circle", { "class": "animate-spin" } %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-24" placeholder="Saving changes..." disabled>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 text-sm text-muted-foreground opacity-50">
|
||||
<div>Saving...</div>
|
||||
<div class="[&>svg]:size-4">
|
||||
{% lucide "loader-circle", { "class": "animate-spin" } %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pl-9 pr-26" placeholder="Refreshing data..." disabled>
|
||||
<div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground [&>svg]:size-4 opacity-50">
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
</div>
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2 text-sm text-muted-foreground opacity-50">
|
||||
Please wait...
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Type to search...">
|
||||
<button type="button" class="btn-secondary" data-align="end">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-spinner", code_spinner, class="w-full max-w-md") }}
|
||||
{{ code_preview("input-group-button", code_button | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-kbd"><a href="#example-kbd">Kbd</a></h3>
|
||||
|
||||
{% set code_kbd %}
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Search...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<kbd data-align="end">⌘K</kbd>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-kbd", code_kbd | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-dropdown"><a href="#example-dropdown">Dropdown</a></h3>
|
||||
|
||||
{% set code_dropdown %}
|
||||
<div class="grid gap-6">
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-9" placeholder="Enter file name">
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Enter file name">
|
||||
{% set trigger %}{% lucide "ellipsis" %}{% endset %}
|
||||
{{ dropdown_menu(
|
||||
main_attrs={"class": "absolute right-1.5 top-1/2 -translate-y-1/2 z-10"},
|
||||
main_attrs={"data-align": "end"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-sm-icon-ghost text-muted-foreground hover:text-accent-foreground size-6"},
|
||||
trigger_attrs={"class": "btn-xs-icon-ghost", "aria-label": "Open file actions"},
|
||||
popover_attrs={"data-align": "end", "class": "min-w-32"},
|
||||
items=[
|
||||
{ type: "item", label: "Settings" },
|
||||
@@ -333,17 +368,14 @@ toc:
|
||||
) }}
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-30" placeholder="Enter search query">
|
||||
{% set trigger %}
|
||||
Search in...
|
||||
{% lucide "chevron-down", { "class": "size-3" } %}
|
||||
{% endset %}
|
||||
<div class="input-group [--radius:1rem]">
|
||||
<input type="text" placeholder="Enter search query">
|
||||
{% set trigger %}Search In... {% lucide "chevron-down", { "class": "size-3" } %}{% endset %}
|
||||
{{ dropdown_menu(
|
||||
main_attrs={"class": "absolute right-1.5 top-1/2 -translate-y-1/2"},
|
||||
main_attrs={"data-align": "end"},
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-sm-ghost text-muted-foreground hover:text-accent-foreground p-2 h-6"},
|
||||
popover_attrs={"data-align": "end", "class": "min-w-32"},
|
||||
trigger_attrs={"class": "btn-xs-ghost pr-1.5! text-xs"},
|
||||
popover_attrs={"data-align": "end", "class": "min-w-32 [--radius:0.95rem]"},
|
||||
items=[
|
||||
{ type: "item", label: "Documentation" },
|
||||
{ type: "item", label: "Blog Posts" },
|
||||
@@ -354,19 +386,114 @@ toc:
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-dropdown", code_dropdown, class="w-full max-w-sm") }}
|
||||
{{ code_preview("input-group-dropdown", code_dropdown | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-group"><a href="#example-group">Group</a></h3>
|
||||
<h3 id="example-spinner"><a href="#example-spinner">Spinner</a></h3>
|
||||
|
||||
{% set code_group %}
|
||||
<div class="flex w-fit items-stretch">
|
||||
<label for="url" class="text-sm flex items-center bg-muted border border-r-0 px-4 rounded-l-md shadow-xs">https://</label>
|
||||
<div class="relative">
|
||||
<input type="text" class="input pr-9 rounded-none">
|
||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4">{% lucide "link-2" %}</div>
|
||||
{% set code_spinner %}
|
||||
<div class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Searching...">
|
||||
<span data-align="end">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Processing...">
|
||||
<span data-align="start">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Saving changes...">
|
||||
<span data-align="end">
|
||||
Saving...
|
||||
{{ spinner_icon | safe }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="Refreshing data...">
|
||||
<span data-align="start" aria-hidden="true">{% lucide "loader", { "class": "animate-spin" } %}</span>
|
||||
<span data-align="end" class="text-muted-foreground">Please wait...</span>
|
||||
</div>
|
||||
<div class="text-sm flex items-center bg-muted border border-l-0 px-4 rounded-r-md shadow-xs">.com</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-group", code_group, class="w-full max-w-sm") }}
|
||||
{{ code_preview("input-group-spinner", code_spinner | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-textarea"><a href="#example-textarea">Textarea</a></h3>
|
||||
|
||||
{% set code_textarea %}
|
||||
<div class="input-group min-h-[200px]" data-orientation="vertical">
|
||||
<textarea placeholder="console.log('Hello, world!');"></textarea>
|
||||
<footer data-align="end" class="border-t">
|
||||
<span>Line 1, Column 1</span>
|
||||
<button type="button" class="btn-sm ml-auto">Run {% lucide "corner-down-left" %}</button>
|
||||
</footer>
|
||||
<header data-align="start" class="border-b">
|
||||
{% lucide "file-code", { "class": "text-muted-foreground" } %}
|
||||
<span>script.js</span>
|
||||
<div role="group" aria-label="File actions" class="ml-auto flex gap-1">
|
||||
<button type="button" class="btn-xs-icon-ghost">{% lucide "refresh-ccw" %}</button>
|
||||
<button type="button" class="btn-xs-icon-ghost">{% lucide "copy" %}</button>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-textarea", code_textarea | prettyHtml, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-custom-input"><a href="#example-custom-input">Custom Input</a></h3>
|
||||
|
||||
{% set code_custom %}
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea
|
||||
data-control
|
||||
placeholder="Autoresize textarea..."
|
||||
class="field-sizing-content min-h-16 resize-none"
|
||||
></textarea>
|
||||
<footer data-align="end">
|
||||
<button type="button" class="btn-sm ml-auto">Submit</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-custom", code_custom | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code_rtl %}
|
||||
<div dir="rtl" class="grid gap-6">
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="بحث...">
|
||||
<span role="group" data-align="start" aria-hidden="true">{% lucide "search" %}</span>
|
||||
<span role="group" data-align="end">١٢ نتيجة</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري البحث...">
|
||||
<span role="group" data-align="end">{{ spinner_icon | safe }}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" placeholder="جاري حفظ التغييرات...">
|
||||
<span role="group" data-align="end">
|
||||
جاري الحفظ...
|
||||
{{ spinner_icon | safe }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="rtl-textarea">منطقة النص</label>
|
||||
<div class="input-group" data-orientation="vertical">
|
||||
<textarea id="rtl-textarea" placeholder="اكتب تعليقًا..."></textarea>
|
||||
<footer data-align="end">
|
||||
<span>٠/٢٨٠</span>
|
||||
<button type="button" class="btn-sm ms-auto">نشر</button>
|
||||
</footer>
|
||||
</div>
|
||||
<p>تذييل موضع أسفل منطقة النص.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("input-group-rtl", code_rtl | prettyHtml, class="w-full max-w-sm") }}
|
||||
|
||||
+152
-42
@@ -1,90 +1,200 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Input
|
||||
description: Displays a form input field or a component that looks like an input field.
|
||||
description: A text input component for forms and user data entry with built-in styling and accessibility features.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Field
|
||||
id: example-field
|
||||
- label: Field Group
|
||||
id: example-field-group
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: With label
|
||||
id: example-with-label
|
||||
- label: With text
|
||||
id: example-with-text
|
||||
- label: With button
|
||||
id: example-with-button
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: File
|
||||
id: example-file
|
||||
- label: Inline
|
||||
id: example-inline
|
||||
- label: Grid
|
||||
id: example-grid
|
||||
- label: Required
|
||||
id: example-required
|
||||
- label: Badge
|
||||
id: example-badge
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: Button Group
|
||||
id: example-button-group
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<input class="input" type="email" placeholder="Email">{% endset %}
|
||||
{{ code_preview("input", code) }}
|
||||
{{ code_preview("input", code, class="w-full max-w-xs") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>input</code> class to your <code><input></code> element 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>
|
||||
<p>Add the <code>input</code> class to supported <code><input></code> elements. Inputs inside a <code>form</code> or <code>field</code> container also receive the same styling automatically for common text-like input types.</p>
|
||||
<p>Use native HTML attributes for behavior: <code>disabled</code> for disabled inputs, <code>required</code> for required fields, and <code>aria-invalid="true"</code> for invalid state styling.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
{{ code_preview("input-default", code) }}
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
{% set code_basic %}<input class="input" type="email" placeholder="Email">{% endset %}
|
||||
{{ code_preview("input-basic", code_basic, class="w-full max-w-xs") }}
|
||||
|
||||
{% set code %}<input class="input" type="email" placeholder="Email" aria-invalid="true">{% endset %}
|
||||
{{ code_preview("input-invalid", code) }}
|
||||
<h3 id="example-field"><a href="#example-field">Field</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>field</code> with a label and description to create an accessible field.</p>
|
||||
</section>
|
||||
|
||||
{% set code_field %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-field">Email</label>
|
||||
<input id="input-field" type="email" placeholder="m@example.com">
|
||||
<p>Enter your email address.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-field", code_field, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-field-group"><a href="#example-field-group">Field Group</a></h3>
|
||||
|
||||
{% set code_field_group %}<div class="grid w-full max-w-xs gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="input-first-name">First name</label>
|
||||
<input id="input-first-name" type="text" placeholder="Jane">
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="input-last-name">Last name</label>
|
||||
<input id="input-last-name" type="text" placeholder="Doe">
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-field-group", code_field_group, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<input class="input" type="email" placeholder="Email" disabled>{% endset %}
|
||||
{{ code_preview("input-disabled", code) }}
|
||||
{% set code_disabled %}<input class="input" type="email" placeholder="Email" disabled>{% endset %}
|
||||
{{ code_preview("input-disabled", code_disabled, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-with-label"><a href="#example-with-label">With label</a></h3>
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label for="input-with-label" class="label">Label</label>
|
||||
<input class="input" id="input-with-label" type="email" placeholder="Email">
|
||||
{% set code_invalid %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-invalid">Email</label>
|
||||
<input class="input" id="input-invalid" type="email" placeholder="Email" aria-invalid="true">
|
||||
<p>Please enter a valid email address.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-with-label", code) }}
|
||||
{{ code_preview("input-invalid", code_invalid, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-with-text"><a href="#example-with-text">With text</a></h3>
|
||||
<h3 id="example-file"><a href="#example-file">File</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label for="input-with-text" class="label">Label</label>
|
||||
<input class="input" id="input-with-text" type="email" placeholder="Email">
|
||||
<p class="text-muted-foreground text-sm">Fill in your email address.</p>
|
||||
{% set code_file %}<input class="input" type="file">{% endset %}
|
||||
{{ code_preview("input-file", code_file, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-inline"><a href="#example-inline">Inline</a></h3>
|
||||
|
||||
{% set code_inline %}<div class="flex w-full max-w-xs items-center gap-2">
|
||||
<input class="input" type="search" placeholder="Search...">
|
||||
<button type="submit" class="btn">Search</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-with-text", code) }}
|
||||
{{ code_preview("input-inline", code_inline, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-with-button"><a href="#example-with-button">With button</a></h3>
|
||||
<h3 id="example-grid"><a href="#example-grid">Grid</a></h3>
|
||||
|
||||
{% set code %}<div class="flex items-center space-x-2">
|
||||
<input class="input" type="email" placeholder="Email">
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
{% set code_grid %}<div class="grid w-full gap-4 md:grid-cols-2">
|
||||
<input class="input" type="text" placeholder="First name">
|
||||
<input class="input" type="text" placeholder="Last name">
|
||||
<input class="input md:col-span-2" type="email" placeholder="Email">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-form", code) }}
|
||||
{{ code_preview("input-grid", code_grid, class="w-full max-w-xl") }}
|
||||
|
||||
<h3 id="example-required"><a href="#example-required">Required</a></h3>
|
||||
|
||||
{% set code_required %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-required">Username <span class="text-destructive">*</span></label>
|
||||
<input id="input-required" type="text" placeholder="hunvreus" required>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-required", code_required, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-badge"><a href="#example-badge">Badge</a></h3>
|
||||
|
||||
{% set code_badge %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="input-badge" class="flex items-center gap-2">
|
||||
Email
|
||||
<span class="badge-secondary">Recommended</span>
|
||||
</label>
|
||||
<input id="input-badge" type="email" placeholder="m@example.com">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-badge", code_badge, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <a href="/components/input-group/">Input Group</a> patterns to add icons, text, or buttons inside an input.</p>
|
||||
</section>
|
||||
|
||||
{% set code_input_group %}<div class="relative w-full max-w-xs">
|
||||
<input class="input ps-9" type="search" placeholder="Search...">
|
||||
<div class="pointer-events-none absolute start-3 top-1/2 -translate-y-1/2 text-muted-foreground [&>svg]:size-4">
|
||||
{% lucide "search" %}
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-input-group", code_input_group, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-button-group"><a href="#example-button-group">Button Group</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <a href="/components/button-group/">Button Group</a> when an input should be grouped with adjacent buttons.</p>
|
||||
</section>
|
||||
|
||||
{% set code_button_group %}<div role="group" aria-label="Search" class="button-group w-full max-w-xs">
|
||||
<input class="input" type="search" placeholder="Search...">
|
||||
<button type="submit" class="btn-icon-outline" aria-label="Search">
|
||||
{% lucide "search" %}
|
||||
</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-button-group", code_button_group, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form space-y-6 w-full">
|
||||
<div class="grid gap-3">
|
||||
<label for="input-form" class="label">Username</label>
|
||||
<input class="input" id="input-form" type="text" placeholder="hunvreus">
|
||||
<p class="text-muted-foreground text-sm">This is your public display name.</p>
|
||||
{% set code_form %}<form class="w-full max-w-sm space-y-6">
|
||||
<div role="group" class="field">
|
||||
<label for="input-form-username">Username</label>
|
||||
<input id="input-form-username" type="text" placeholder="hunvreus" required>
|
||||
<p>This is your public display name.</p>
|
||||
</div>
|
||||
<div role="group" class="field">
|
||||
<label for="input-form-email">Email</label>
|
||||
<input id="input-form-email" type="email" placeholder="m@example.com" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code, "w-full max-w-sm") }}
|
||||
{{ code_preview("input-form", code_form, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Inputs support right-to-left layouts through native browser direction handling. Add <code>dir="rtl"</code> to the input, field, or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}<div role="group" class="field w-full max-w-xs" dir="rtl">
|
||||
<label for="input-rtl-api-key">مفتاح API</label>
|
||||
<input id="input-rtl-api-key" type="password" placeholder="sk-...">
|
||||
<p>مفتاح API الخاص بك مشفر ومخزن بأمان.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-rtl", code_rtl, class="w-full max-w-xs") }}
|
||||
|
||||
+166
-222
@@ -5,6 +5,11 @@ description: A versatile component that you can use to display any content.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML structure
|
||||
id: usage-html-structure
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
@@ -24,47 +29,65 @@ toc:
|
||||
id: example-header
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated Item component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code_basic %}
|
||||
<div class="flex flex-col gap-6">
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Basic Item</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">A simple item with title and description.</p>
|
||||
</div>
|
||||
<article class="item" data-variant="outline">
|
||||
<section>
|
||||
<h3>Basic Item</h3>
|
||||
<p>A simple item with title and description.</p>
|
||||
</section>
|
||||
<aside>
|
||||
<button class="btn-sm-outline">Action</button>
|
||||
</article>
|
||||
|
||||
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border py-3 px-4 gap-2.5">
|
||||
<div class="flex shrink-0 items-center justify-center gap-2 [&_svg]:pointer-events-none bg-transparent [&_svg]:size-5">
|
||||
{% lucide "badge-check" %}
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Your profile has been verified.</h3>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 [&_svg]:size-4">
|
||||
{% lucide "chevron-right" %}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-basic", code_basic | prettyHtml, class="w-full max-w-md") }}
|
||||
{{ code_preview("item-basic", code_basic, class="w-full max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Items are pure HTML composition using Tailwind utility classes. Use semantic elements like <code><article></code> or <code><a></code>, apply flex layout, and compose with standard HTML elements like <code><h3></code>, <code><p></code>, <code><img></code>, or <code><button></code>.</p>
|
||||
<p>Browse the examples below and copy the pattern that fits your needs.</p>
|
||||
<p>Use <code>.item</code> on a semantic element such as <code><article></code> or <code><a></code>. Use <code>.item-group</code> when listing multiple items.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
{{ code_block(code_basic) }}
|
||||
|
||||
<h3 id="usage-html-structure"><a href="#usage-html-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component has the following HTML structure:</p>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><article class="item"></code> or <code class="highlight language-html"><a class="item"></code></dt>
|
||||
<dd>The item root. Use <code>data-variant="outline|muted"</code> and <code>data-size="sm|xs"</code> when needed.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><figure></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Media: icon, avatar, or image.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>Primary content. Headings and paragraphs are styled automatically.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><h2></code>, <code class="highlight language-html"><h3></code>, or <code class="highlight language-html"><h4></code></dt>
|
||||
<dd>The item title.</dd>
|
||||
<dt><code class="highlight language-html"><p></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The item description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><aside></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Actions or trailing metadata.</dd>
|
||||
<dt><code class="highlight language-html"><header></code>, <code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Full-width item regions.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div class="item-group" role="list"></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Groups related items. Use <code>role="listitem"</code> on child items when they are not native list items.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
@@ -72,249 +95,170 @@ toc:
|
||||
<h3 id="example-variants"><a href="#example-variants">Variants</a></h3>
|
||||
|
||||
{% set code_variants %}
|
||||
<div class="flex flex-col gap-6">
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Default Variant</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Standard styling with subtle background and borders.</p>
|
||||
</div>
|
||||
<button class="btn-sm-outline">Open</button>
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" role="listitem">
|
||||
<section>
|
||||
<h3>Default Variant</h3>
|
||||
<p>Transparent border for quiet item rows.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Outline Variant</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Outlined style with clear borders and transparent background.</p>
|
||||
</div>
|
||||
<button class="btn-sm-outline">Open</button>
|
||||
<article class="item" data-variant="outline" role="listitem">
|
||||
<section>
|
||||
<h3>Outline Variant</h3>
|
||||
<p>Outlined style with a visible border.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent bg-muted/50 p-4 gap-4">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Muted Variant</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Subdued appearance with muted colors for secondary content.</p>
|
||||
</div>
|
||||
<button class="btn-sm-outline">Open</button>
|
||||
<article class="item" data-variant="muted" role="listitem">
|
||||
<section>
|
||||
<h3>Muted Variant</h3>
|
||||
<p>Subdued appearance for secondary content.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Open</button></aside>
|
||||
</article>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-variants", code_variants) }}
|
||||
{{ code_preview("item-variants", code_variants, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use different padding and gap sizes for compact or spacious layouts.</p>
|
||||
</section>
|
||||
{% set code_size %}
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" data-variant="outline" role="listitem">
|
||||
<section><h3>Default</h3><p>Default item size.</p></section>
|
||||
</article>
|
||||
<article class="item" data-variant="outline" data-size="sm" role="listitem">
|
||||
<section><h3>Small</h3><p>Small item size.</p></section>
|
||||
</article>
|
||||
<article class="item" data-variant="outline" data-size="xs" role="listitem">
|
||||
<section><h3>Extra Small</h3><p>Extra small item size.</p></section>
|
||||
</article>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-size", code_basic) }}
|
||||
{{ code_preview("item-size", code_size, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code_icon %}
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<div class="flex shrink-0 items-center justify-center gap-2 self-start [&_svg]:pointer-events-none size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4">
|
||||
<article class="item" data-variant="outline">
|
||||
<figure>
|
||||
{% lucide "shield-alert" %}
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Security Alert</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">New login detected from unknown device.</p>
|
||||
</div>
|
||||
<button class="btn-sm-outline">Review</button>
|
||||
</figure>
|
||||
<section>
|
||||
<h3>Security Alert</h3>
|
||||
<p>New login detected from an unknown device.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">Review</button></aside>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-icon", code_icon, class="max-w-lg") }}
|
||||
|
||||
{{ code_preview("item-icon", code_icon, class="w-full max-w-lg") }}
|
||||
|
||||
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
|
||||
|
||||
{% set code_avatar %}
|
||||
<div class="flex flex-col gap-6">
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<img src="https://github.com/hunvreus.png" alt="hunvreus" class="size-10 rounded-full object-cover">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">hunvreus</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Last seen 5 months ago</p>
|
||||
</div>
|
||||
<button class="btn-icon-outline rounded-full">
|
||||
{% lucide "plus" %}
|
||||
</button>
|
||||
</article>
|
||||
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full self-start">
|
||||
<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 flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">No Team Members</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Invite your team to collaborate on this project.</p>
|
||||
</div>
|
||||
<button class="btn-outline">
|
||||
Invite
|
||||
</button>
|
||||
</article>
|
||||
</div>
|
||||
<article class="item" data-variant="outline">
|
||||
<figure>
|
||||
<img src="https://github.com/hunvreus.png" alt="hunvreus">
|
||||
</figure>
|
||||
<section>
|
||||
<h3>hunvreus</h3>
|
||||
<p>Last seen 5 months ago.</p>
|
||||
</section>
|
||||
<aside>
|
||||
<button class="btn-icon-outline rounded-full">{% lucide "plus" %}</button>
|
||||
</aside>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-avatar", code_avatar, class="w-full max-w-lg") }}
|
||||
{{ code_preview("item-avatar", code_avatar, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-image"><a href="#example-image">Image</a></h3>
|
||||
|
||||
{% set code_image %}
|
||||
<div class="flex flex-col gap-6 w-full max-w-md">
|
||||
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<img src="https://avatar.vercel.sh/example_1" alt="Midnight City Lights" class="grayscale size-10 rounded-sm object-cover">
|
||||
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Midnight City Lights - <span class="text-muted-foreground">Electric Nights</span></h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Neon Dreams</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 flex-none text-center">
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">3:45</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<img src="https://avatar.vercel.sh/example_2" alt="Midnight City Lights" class="grayscale size-10 rounded-sm object-cover">
|
||||
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Coffee Shop Conversations - <span class="text-muted-foreground">Urban Stories</span></h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">The Morning Brew</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 flex-none text-center">
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">4:05</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<img src="https://avatar.vercel.sh/example_3" alt="Midnight City Lights" class="grayscale size-10 rounded-sm object-cover">
|
||||
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="w-fit items-center gap-2 text-sm leading-snug font-medium line-clamp-1">Digital Rain - <span class="text-muted-foreground">Binary Beats</span></h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Cyber Symphony</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 flex-none text-center">
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">3:30</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<a href="#" class="item" data-variant="outline">
|
||||
<figure>
|
||||
<img src="https://avatar.vercel.sh/example_1" alt="Midnight City Lights">
|
||||
</figure>
|
||||
<section>
|
||||
<h3>Midnight City Lights</h3>
|
||||
<p>Neon Dreams</p>
|
||||
</section>
|
||||
<section>
|
||||
<p>3:45</p>
|
||||
</section>
|
||||
</a>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-image", code_image) }}
|
||||
|
||||
{{ code_preview("item-image", code_image, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-group"><a href="#example-group">Group</a></h3>
|
||||
|
||||
{% set code_group %}
|
||||
<div class="flex flex-col">
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
|
||||
<img src="https://github.com/hunvreus.png" alt="Ronan Berder" class="grayscale size-10 rounded-full object-cover">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">hunvreus</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">hunvreus@gmail.com</p>
|
||||
</div>
|
||||
<button class="btn-icon-outline rounded-full">
|
||||
{% lucide "plus" %}
|
||||
</button>
|
||||
<div class="item-group" role="list">
|
||||
<article class="item" role="listitem">
|
||||
<figure><img src="https://github.com/hunvreus.png" alt="hunvreus"></figure>
|
||||
<section><h3>hunvreus</h3><p>hunvreus@gmail.com</p></section>
|
||||
<aside><button class="btn-icon-outline rounded-full">{% lucide "plus" %}</button></aside>
|
||||
</article>
|
||||
|
||||
<hr role="separator" class="border-border">
|
||||
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
|
||||
<img src="https://github.com/shadcn.png" alt="shadcn" class="grayscale size-10 rounded-full object-cover">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">shadcn</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">shadcn@vercel.com</p>
|
||||
</div>
|
||||
<button class="btn-icon-outline rounded-full">
|
||||
{% lucide "plus" %}
|
||||
</button>
|
||||
</article>
|
||||
|
||||
<hr role="separator" class="border-border">
|
||||
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
|
||||
<img src="https://github.com/adamwathan.png" alt="Adam Wathan" class="grayscale size-10 rounded-full object-cover">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">adamwathan</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">adam.wathan@gmail.com</p>
|
||||
</div>
|
||||
<button class="btn-icon-outline rounded-full">
|
||||
{% lucide "plus" %}
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<article class="item" role="listitem">
|
||||
<figure><img src="https://github.com/shadcn.png" alt="shadcn"></figure>
|
||||
<section><h3>shadcn</h3><p>shadcn@vercel.com</p></section>
|
||||
<aside><button class="btn-icon-outline rounded-full">{% lucide "plus" %}</button></aside>
|
||||
</article>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-group", code_group, class="w-full max-w-md") }}
|
||||
|
||||
|
||||
<h3 id="example-header"><a href="#example-header">Header</a></h3>
|
||||
|
||||
{% set code_header %}
|
||||
<div class="flex-col grid grid-cols-3 gap-4">
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<img alt="v0-1.5-sm" loading="lazy" class="grayscale aspect-square w-full rounded-sm object-cover flex-basis" src="https://avatar.vercel.sh/v0-1.5-sm">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">v0-1.5-sm</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Everyday tasks and UI generation.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<img alt="v0-1.5-lg" loading="lazy" class="grayscale aspect-square w-full rounded-sm object-cover flex-basis" src="https://avatar.vercel.sh/v0-1.5-lg">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">v0-1.5-lg</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Advanced thinking or reasoning.</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<img alt="v0-2.0-mini" loading="lazy" class="grayscale aspect-square w-full rounded-sm object-cover flex-basis" src="https://avatar.vercel.sh/v0-2.0-mini">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">v0-2.0-mini</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Open Source model for everyone.</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<article class="item" data-variant="outline">
|
||||
<header>
|
||||
<h3>v0-1.5-sm</h3>
|
||||
<span class="badge-outline">Model</span>
|
||||
</header>
|
||||
<figure>
|
||||
<img src="https://avatar.vercel.sh/v0-1.5-sm" alt="v0-1.5-sm">
|
||||
</figure>
|
||||
<section>
|
||||
<p>Everyday tasks and UI generation.</p>
|
||||
</section>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-header", code_header) }}
|
||||
{{ code_preview("item-header", code_header, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Items can be rendered as links by using an <code><a></code> element. Hover and focus states are applied automatically.</p>
|
||||
</section>
|
||||
|
||||
{% set code_link %}
|
||||
<div class="flex flex-col gap-6">
|
||||
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-transparent p-4 gap-4">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">Visit our documentation</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Learn how to get started with our components.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 [&_svg]:size-4">
|
||||
{% lucide "chevron-right" %}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="#" class="group/item flex items-center border text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] border-border p-4 gap-4">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<h3 class="flex w-fit items-center gap-2 text-sm leading-snug font-medium">External resource</h3>
|
||||
<p class="text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4">Opens in a new tab with security attributes.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 [&_svg]:size-4">
|
||||
{% lucide "external-link" %}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<a href="#" class="item">
|
||||
<section>
|
||||
<h3>Visit our documentation</h3>
|
||||
<p>Learn how to get started with our components.</p>
|
||||
</section>
|
||||
<aside>{% lucide "chevron-right" %}</aside>
|
||||
</a>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-link", code_link, class="w-full max-w-md") }}
|
||||
{{ code_preview("item-link", code_link, class="w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code_rtl %}
|
||||
<article class="item" data-variant="outline" dir="rtl">
|
||||
<figure>{% lucide "shield-alert" %}</figure>
|
||||
<section>
|
||||
<h3>تنبيه أمني</h3>
|
||||
<p>تم اكتشاف تسجيل دخول من جهاز غير معروف.</p>
|
||||
</section>
|
||||
<aside><button class="btn-sm-outline">مراجعة</button></aside>
|
||||
</article>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("item-rtl", code_rtl, class="w-full max-w-lg") }}
|
||||
|
||||
@@ -12,6 +12,10 @@ toc:
|
||||
id: example-group
|
||||
- label: Button
|
||||
id: example-button
|
||||
- label: Input Group
|
||||
id: example-input-group
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
@@ -19,13 +23,13 @@ toc:
|
||||
|
||||
{% set code_html %}
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">⌘</kbd>
|
||||
<kbd class="kbd">⇧</kbd>
|
||||
<kbd class="kbd">⌥</kbd>
|
||||
<kbd class="kbd">⌃</kbd>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl</kbd>
|
||||
<span>+</span>
|
||||
<kbd class="kbd">B</kbd>
|
||||
@@ -38,7 +42,7 @@ toc:
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use the <code><kbd></code> element with the <code>kbd</code> class:</p>
|
||||
<p>Use the <code><kbd></code> element with the <code>kbd</code> class. Use <code>kbd-group</code> to group multiple keys.</p>
|
||||
</section>
|
||||
|
||||
{% set code_simple %}
|
||||
@@ -54,7 +58,7 @@ toc:
|
||||
{% set group_html %}
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Use
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl + B</kbd>
|
||||
<kbd class="kbd">Ctrl + K</kbd>
|
||||
</span>
|
||||
@@ -68,9 +72,40 @@ toc:
|
||||
|
||||
{% set button_html %}
|
||||
<div class="flex flex-wrap items-center gap-x-4">
|
||||
<button class="btn-sm-outline pr-2">Accept <kbd class="kbd">⏎</kbd></button>
|
||||
<button class="btn-sm-outline pr-2">Cancel <kbd class="kbd">Esc</kbd></button>
|
||||
<button class="btn-sm-outline pe-2">Accept <kbd class="kbd">⏎</kbd></button>
|
||||
<button class="btn-sm-outline pe-2">Cancel <kbd class="kbd">Esc</kbd></button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("kbd-button", button_html | prettyHtml) }}
|
||||
{{ code_preview("kbd-button", button_html | prettyHtml) }}
|
||||
|
||||
<h3 id="example-input-group"><a href="#example-input-group">Input Group</a></h3>
|
||||
|
||||
{% set input_group_html %}
|
||||
<div class="relative w-full max-w-sm">
|
||||
<input type="search" class="input ps-9 pe-12" placeholder="Search...">
|
||||
{% lucide "search", { "class": "absolute start-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" } %}
|
||||
<kbd class="kbd absolute end-3 top-1/2 -translate-y-1/2">/</kbd>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("kbd-input-group", input_group_html | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Keyboard labels are direction-neutral. Surrounding spacing should use logical utilities when position matters.</p>
|
||||
</section>
|
||||
|
||||
{% set rtl_html %}
|
||||
<p dir="rtl" class="text-muted-foreground text-sm">
|
||||
استخدم
|
||||
<span class="kbd-group">
|
||||
<kbd class="kbd">Ctrl</kbd>
|
||||
<kbd class="kbd">K</kbd>
|
||||
</span>
|
||||
لفتح البحث
|
||||
</p>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("kbd-rtl", rtl_html | prettyHtml) }}
|
||||
|
||||
@@ -10,6 +10,8 @@ toc:
|
||||
children:
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
@@ -21,10 +23,10 @@ toc:
|
||||
</label>{% endset %}
|
||||
{{ code_preview("label", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>label</code> class to your <code><label></code> element or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). Labels' style may be affected by the presence of a disabled peer or child element.</p>
|
||||
<p>Add the <code>label</code> class to your <code><label></code> element. Labels' style may be affected by the presence of a disabled peer or child element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<label class="label" for="email">Your email address</label>{% endset %}
|
||||
@@ -38,4 +40,16 @@ toc:
|
||||
<label class="label" for="email">Email</label>
|
||||
<input class="input" id="email" type="email" placeholder="Email" disabled>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("label-disabled", code) }}
|
||||
{{ code_preview("label-disabled", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Labels support right-to-left layouts through native text flow. Add <code>dir="rtl"</code> to the label or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div class="grid gap-3" dir="rtl">
|
||||
<label class="label" for="email-rtl">البريد الإلكتروني</label>
|
||||
<input class="input" id="email-rtl" type="email" placeholder="name@example.com">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("label-rtl", code) }}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Native Select
|
||||
description: A styled native HTML select element with consistent design system integration.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: Native Select vs Select
|
||||
id: native-select-vs-select
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<select class="select w-[180px]" aria-label="Fruit">
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
<option value="blueberry">Blueberry</option>
|
||||
<option value="pineapple">Pineapple</option>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("native-select", code) }}
|
||||
|
||||
<section class="prose mt-6">
|
||||
<p>For a custom listbox with richer styling and JavaScript behavior, see the <a href="/components/select">Select</a> component.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>select</code> class to a native <code><select></code>. Use standard <code><option></code> and <code><optgroup></code> elements.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code %}<select class="select w-[220px]" aria-label="Food">
|
||||
<option value="">Select food</option>
|
||||
<optgroup label="Fruits">
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
<option value="blueberry">Blueberry</option>
|
||||
</optgroup>
|
||||
<optgroup label="Vegetables">
|
||||
<option value="carrot">Carrot</option>
|
||||
<option value="potato">Potato</option>
|
||||
<option value="tomato">Tomato</option>
|
||||
</optgroup>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("native-select-groups", code) }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<select class="select w-[220px]" aria-label="Fruit" disabled>
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("native-select-disabled", code) }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field" data-invalid="true">
|
||||
<label for="native-select-invalid">Fruit</label>
|
||||
<select id="native-select-invalid" class="select w-full" aria-invalid="true" aria-describedby="native-select-invalid-error">
|
||||
<option value="">Select a fruit</option>
|
||||
<option value="apple">Apple</option>
|
||||
<option value="banana">Banana</option>
|
||||
</select>
|
||||
<p id="native-select-invalid-error" class="field-error">Select a fruit to continue.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("native-select-invalid", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code %}<div class="flex flex-wrap items-center gap-3">
|
||||
<select class="select w-[180px]" aria-label="Default size">
|
||||
<option>Default</option>
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
</select>
|
||||
<select class="select w-[180px]" data-size="sm" aria-label="Small size">
|
||||
<option>Small</option>
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
</select>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("native-select-size", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Native select supports document direction. Set <code>dir="rtl"</code> on the select or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<select dir="rtl" class="select w-[220px]" aria-label="الفاكهة">
|
||||
<option value="">اختر فاكهة</option>
|
||||
<option value="apple">تفاح</option>
|
||||
<option value="banana">موز</option>
|
||||
<option value="blueberry">توت</option>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("native-select-rtl", code) }}
|
||||
|
||||
<h2 id="native-select-vs-select"><a href="#native-select-vs-select">Native Select vs Select</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<ul>
|
||||
<li>Use <strong>Native Select</strong> for native browser behavior, better performance, form integration, and mobile-optimized pickers.</li>
|
||||
<li>Use <strong>Select</strong> for a custom popover, richer option content, multi-select behavior, or custom JavaScript events.</li>
|
||||
</ul>
|
||||
</section>
|
||||
+175
-66
@@ -1,7 +1,7 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Popover
|
||||
description: Displays rich content in a portal, triggered by a button.
|
||||
description: Displays rich content triggered by a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
@@ -9,7 +9,7 @@ toc:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1 (Optional): Include the JavaScript file"
|
||||
- label: "Step 1: Include the JavaScript file"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your popover HTML"
|
||||
id: usage-html-js-2
|
||||
@@ -19,6 +19,19 @@ toc:
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Align
|
||||
id: example-align
|
||||
- label: With Form
|
||||
id: example-form
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
@@ -30,39 +43,74 @@ toc:
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-80"}
|
||||
popover_attrs={"class": "w-72"}
|
||||
) %}
|
||||
<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" autofocus/>
|
||||
</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>
|
||||
<header>
|
||||
<h4>Dimensions</h4>
|
||||
<p>Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="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="input col-span-2 h-8" autofocus>
|
||||
</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="input 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="input 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="input col-span-2 h-8">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("popover", code_html | prettyHtml) }}
|
||||
|
||||
{% set code_form %}
|
||||
{% call popover(
|
||||
id="popover-form",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-72"}
|
||||
) %}
|
||||
<header>
|
||||
<h4>Dimensions</h4>
|
||||
<p>Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-width">Width</label>
|
||||
<input type="text" id="popover-form-width" value="100%" class="input col-span-2 h-8" autofocus>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-max-width">Max. width</label>
|
||||
<input type="text" id="popover-form-max-width" value="300px" class="input col-span-2 h-8">
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-height">Height</label>
|
||||
<input type="text" id="popover-form-height" value="25px" class="input col-span-2 h-8">
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="popover-form-max-height">Max. height</label>
|
||||
<input type="text" id="popover-form-max-height" value="none" class="input col-span-2 h-8">
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat popovers are inline-positioned relative to the <code>.popover</code> wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and works with Basecoat's existing dropdown/select positioning model.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript file</a></h4>
|
||||
@@ -80,10 +128,14 @@ toc:
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/popover.js" target="_blank">
|
||||
popover.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your popover HTML</a></h4>
|
||||
@@ -94,17 +146,20 @@ toc:
|
||||
|
||||
<section class="prose">
|
||||
<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-expanded="false"</code>: tracks the popover's state.</li>
|
||||
<li><code>onclick</code>: a simple function to update <code>aria-expanded</code>.</li>
|
||||
</ul>
|
||||
<dt><code class="highlight language-html"><div class="popover"></code></dt>
|
||||
<dd>Relative wrapper for the trigger and inline popover content.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button" aria-expanded="false" aria-controls="{ POPOVER_ID }"></code></dt>
|
||||
<dd>Trigger button. The script toggles <code>aria-expanded</code> and opens/closes the content.</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover id="{ POPOVER_ID }" aria-hidden="true"></code></dt>
|
||||
<dd>Popover content. Set <code>data-side="top|right|bottom|left"</code> and <code>data-align="start|center|end"</code> to control placement.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Header area. A heading inside the header is styled as the popover title, and a paragraph is styled as the description.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div data-popover class="popover" id="{ POPOVER_ID }"></code></dt>
|
||||
<dd>You can set up the side and alignment of the popover using the <code>data-side</code> and <code>data-align</code> attributes.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -113,9 +168,9 @@ toc:
|
||||
<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>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
<dt><code>basecoat:popover</code></dt>
|
||||
<dd>When the 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>
|
||||
<dd>When the popover opens, the component dispatches a custom event on <code>document</code>. Other popover-based components listen for this to close any open popovers.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -126,7 +181,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -144,31 +199,85 @@ toc:
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-80 p-4"}
|
||||
popover_attrs={"class": "w-72", "data-align": "center"}
|
||||
) %}
|
||||
<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>
|
||||
<header>
|
||||
<h4>Title</h4>
|
||||
<p>Description text here.</p>
|
||||
</header>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call popover(
|
||||
id="popover-basic",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-72"}
|
||||
) %}
|
||||
<header>
|
||||
<h4>Title</h4>
|
||||
<p>Description text here.</p>
|
||||
</header>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("popover-basic", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-align"><a href="#example-align">Align</a></h3>
|
||||
|
||||
{% set code %}<div class="flex flex-wrap gap-3">
|
||||
{% call popover(id="popover-align-start", trigger="Start", trigger_attrs={"class": "btn-outline"}, popover_attrs={"class": "w-56", "data-align": "start"}) %}
|
||||
<p>Aligned to start.</p>
|
||||
{% endcall %}
|
||||
{% call popover(id="popover-align-center", trigger="Center", trigger_attrs={"class": "btn-outline"}, popover_attrs={"class": "w-56", "data-align": "center"}) %}
|
||||
<p>Aligned to center.</p>
|
||||
{% endcall %}
|
||||
{% call popover(id="popover-align-end", trigger="End", trigger_attrs={"class": "btn-outline"}, popover_attrs={"class": "w-56", "data-align": "end"}) %}
|
||||
<p>Aligned to end.</p>
|
||||
{% endcall %}
|
||||
</div>{% endset %}
|
||||
{{ code_preview("popover-align", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">With Form</a></h3>
|
||||
|
||||
{{ code_preview("popover-form", code_form | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Popover alignment uses logical start and end offsets. Set <code>dir="rtl"</code> on a parent to flip start/end alignment.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl">
|
||||
{% call popover(
|
||||
id="popover-rtl",
|
||||
trigger="فتح",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
popover_attrs={"class": "w-72", "data-align": "start"}
|
||||
) %}
|
||||
<header>
|
||||
<h4>العنوان</h4>
|
||||
<p>نص الوصف هنا.</p>
|
||||
</header>
|
||||
{% endcall %}
|
||||
</div>{% endset %}
|
||||
{{ code_preview("popover-rtl", code | prettyHtml) }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.popover</code></dt>
|
||||
<dd>Root wrapper. Must contain the trigger button and direct <code>[data-popover]</code> content.</dd>
|
||||
<dt><code class="highlight language-html">[data-popover]</code></dt>
|
||||
<dd>Popover content. Supports <code>data-side</code>, <code>data-align</code>, and <code>aria-hidden</code>.</dd>
|
||||
<dt><code class="highlight language-html">data-side</code></dt>
|
||||
<dd>Placement side: <code>top</code>, <code>right</code>, <code>bottom</code>, or <code>left</code>. Defaults to <code>bottom</code>.</dd>
|
||||
<dt><code class="highlight language-html">data-align</code></dt>
|
||||
<dd>Alignment: <code>start</code>, <code>center</code>, or <code>end</code>. Defaults to <code>start</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -1,48 +1,219 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Radio Group
|
||||
description: A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.
|
||||
description: A set of checkable buttons where only one option can be selected at a time.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Checked state
|
||||
id: checked-state
|
||||
- label: Invalid state
|
||||
id: invalid-state
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Description
|
||||
id: example-description
|
||||
- label: Choice Card
|
||||
id: example-choice-card
|
||||
- label: Fieldset
|
||||
id: example-fieldset
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<fieldset class="grid gap-3">
|
||||
<label class="label"><input type="radio" name="radio-group" value="default" class="input">Default</label>
|
||||
<label class="label"><input type="radio" name="radio-group" value="comfortable" class="input" checked>Comfortable</label>
|
||||
<label class="label"><input type="radio" name="radio-group" value="compact" class="input">Compact</label>
|
||||
{% set code %}<fieldset class="fieldset">
|
||||
<legend>Plan</legend>
|
||||
<div role="radiogroup" aria-label="Plan" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="plan-free" name="plan" value="free" class="input" checked>
|
||||
<label for="plan-free">Free</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="plan-pro" name="plan" value="pro" class="input">
|
||||
<label for="plan-pro">Pro</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("radio-group", code) }}
|
||||
{{ code_preview("radio-group", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply 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>
|
||||
<p>Add the <code>input</code> class to your <code><input type="radio"></code> elements. Use a shared <code>name</code> attribute for mutually exclusive options, and wrap related options in a semantic <code><fieldset></code> or an element with <code>role="radiogroup"</code>.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="radio" name="plan" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="checked-state"><a href="#checked-state">Checked state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use native HTML state. Add <code>checked</code> for the initially selected option, or read and update the element's <code>checked</code> property in JavaScript.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="radio" name="plan" class="input" checked>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="invalid-state"><a href="#invalid-state">Invalid state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>aria-invalid="true"</code> on the invalid radio and, when using <code>field</code>, set <code>data-invalid="true"</code> on the field wrapper.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start gap-3" data-invalid="true">
|
||||
<input type="radio" id="radio-invalid" name="radio-invalid" class="input" aria-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="radio-invalid">I agree to the terms</label>
|
||||
<p class="field-error">You must select an option to continue.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-invalid-state", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}<form class="form space-y-6 w-full">
|
||||
<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>
|
||||
{% set code %}<div role="radiogroup" aria-label="View density" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="density-default" name="density" value="default" class="input">
|
||||
<label for="density-default">Default</label>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("radio-group-form", code, "w-full max-w-sm") }}
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="density-comfortable" name="density" value="comfortable" class="input" checked>
|
||||
<label for="density-comfortable">Comfortable</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="density-compact" name="density" value="compact" class="input">
|
||||
<label for="density-compact">Compact</label>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-basic", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-description"><a href="#example-description">Description</a></h3>
|
||||
|
||||
{% set code %}<div role="radiogroup" aria-label="Notifications" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-start gap-3">
|
||||
<input type="radio" id="notify-all" name="notifications" value="all" class="input" checked aria-describedby="notify-all-description">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="notify-all">All new messages</label>
|
||||
<p id="notify-all-description" class="field-description">Receive a notification for every new message.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-start gap-3">
|
||||
<input type="radio" id="notify-mentions" name="notifications" value="mentions" class="input" aria-describedby="notify-mentions-description">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="notify-mentions">Direct messages and mentions</label>
|
||||
<p id="notify-mentions-description" class="field-description">Only notify me for direct messages and mentions.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-description", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-choice-card"><a href="#example-choice-card">Choice Card</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Wrap a <code>.field</code> in a native <code><label></code> when the whole card should select the option.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div role="radiogroup" aria-label="Plans" data-slot="radio-group" class="w-full max-w-md">
|
||||
<label>
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<div class="grid gap-1.5">
|
||||
<span>Starter</span>
|
||||
<span class="field-description">For small teams getting started.</span>
|
||||
</div>
|
||||
<input type="radio" name="choice-plan" value="starter" class="input" checked>
|
||||
</div>
|
||||
</label>
|
||||
<label>
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<div class="grid gap-1.5">
|
||||
<span>Team</span>
|
||||
<span class="field-description">Collaboration tools for growing teams.</span>
|
||||
</div>
|
||||
<input type="radio" name="choice-plan" value="team" class="input">
|
||||
</div>
|
||||
</label>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-choice-card", code, "w-full") }}
|
||||
|
||||
<h3 id="example-fieldset"><a href="#example-fieldset">Fieldset</a></h3>
|
||||
|
||||
{% set code %}<fieldset class="fieldset">
|
||||
<legend>Notify me about...</legend>
|
||||
<div role="radiogroup" aria-label="Notification preference" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="radio" id="notify-all-fieldset" name="notify-fieldset" value="all" checked>
|
||||
<label for="notify-all-fieldset">All new messages</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="radio" id="notify-mentions-fieldset" name="notify-fieldset" value="mentions">
|
||||
<label for="notify-mentions-fieldset">Direct messages and mentions</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input class="input" type="radio" id="notify-none-fieldset" name="notify-fieldset" value="none">
|
||||
<label for="notify-none-fieldset">Nothing</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("radio-fieldset", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<div role="radiogroup" aria-label="Disabled notifications" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3" data-disabled="true">
|
||||
<input type="radio" id="radio-disabled" name="radio-disabled" class="input" disabled>
|
||||
<label for="radio-disabled">Disabled option</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3" data-disabled="true">
|
||||
<input type="radio" id="radio-disabled-checked" name="radio-disabled" class="input" checked disabled>
|
||||
<label for="radio-disabled-checked">Disabled checked option</label>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-disabled", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<fieldset class="fieldset" data-invalid="true">
|
||||
<legend>Plan</legend>
|
||||
<div role="radiogroup" aria-label="Invalid plan" data-slot="radio-group" aria-describedby="radio-group-error">
|
||||
<div role="group" class="field flex-row items-center gap-3" data-invalid="true">
|
||||
<input type="radio" id="radio-invalid-free" name="radio-invalid-plan" class="input" aria-invalid="true">
|
||||
<label for="radio-invalid-free">Free</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3" data-invalid="true">
|
||||
<input type="radio" id="radio-invalid-pro" name="radio-invalid-plan" class="input" aria-invalid="true">
|
||||
<label for="radio-invalid-pro">Pro</label>
|
||||
</div>
|
||||
</div>
|
||||
<p id="radio-group-error" class="field-error">Select a plan to continue.</p>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("radio-invalid", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Radio group layout uses document direction and logical spacing. Set <code>dir="rtl"</code> on a parent to render right-to-left.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" role="radiogroup" aria-label="طريقة العرض" data-slot="radio-group">
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="radio-rtl-default" name="radio-rtl" value="default" class="input" checked>
|
||||
<label for="radio-rtl-default">افتراضي</label>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center gap-3">
|
||||
<input type="radio" id="radio-rtl-comfortable" name="radio-rtl" value="comfortable" class="input">
|
||||
<label for="radio-rtl-comfortable">مريح</label>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("radio-rtl", code, "w-full max-w-sm") }}
|
||||
|
||||
+113
-44
@@ -6,8 +6,6 @@ toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
children:
|
||||
@@ -26,45 +24,40 @@ toc:
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Align
|
||||
id: example-align
|
||||
- label: Groups
|
||||
id: example-groups
|
||||
- label: Scrollable
|
||||
id: example-scrollable
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: With icon
|
||||
id: example-with-icon
|
||||
- label: Multiple
|
||||
id: example-multiple
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
{% set code_native %}<select class="select w-[180px]">
|
||||
<optgroup label="Fruits">
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
<option>Blueberry</option>
|
||||
<option>Grapes</option>
|
||||
<option>Pineapple</option>
|
||||
</optgroup>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("select-native", code_native) }}
|
||||
|
||||
{% set code_html %}
|
||||
{{ select(
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
is_combobox=true,
|
||||
items=[
|
||||
{
|
||||
type: "group",
|
||||
label: "Fruits",
|
||||
label: "Theme",
|
||||
items: [
|
||||
{ type: "item", value: "apple", label: "Apple" },
|
||||
{ type: "item", value: "banana", label: "Banana" },
|
||||
{ type: "item", value: "blueberry", label: "Blueberry" },
|
||||
{ type: "item", value: "grapes", label: "Grapes" },
|
||||
{ type: "item", value: "pineapple", label: "Pineapple" }
|
||||
{ type: "item", value: "light", label: "Light" },
|
||||
{ type: "item", value: "dark", label: "Dark" },
|
||||
{ type: "item", value: "system", label: "System" }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -78,13 +71,6 @@ toc:
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>If you use a <code><select></code> element, just add the <code>select</code> class to it or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>).</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_native) }}
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
@@ -103,7 +89,7 @@ toc:
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -306,7 +292,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -339,6 +325,62 @@ toc:
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-align"><a href="#example-align">Align</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use <code>data-align</code> on the popover to align the list to the trigger edge. Basecoat keeps the popup below the control; it does not implement shadcn/ui's selected-item-over-trigger positioning.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div class="flex flex-wrap gap-3">
|
||||
{{ select(
|
||||
id="select-align-start",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
popover_attrs={"data-align": "start"},
|
||||
items=[
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
{ value: "system", label: "System" }
|
||||
]
|
||||
) }}
|
||||
{{ select(
|
||||
id="select-align-end",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
popover_attrs={"data-align": "end"},
|
||||
items=[
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
{ value: "system", label: "System" }
|
||||
]
|
||||
) }}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("select-align", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-groups"><a href="#example-groups">Groups</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
id="select-groups",
|
||||
trigger_attrs={"class": "w-[220px]"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="select-groups-fruits">
|
||||
<div role="heading" id="select-groups-fruits">Fruits</div>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="select-groups-vegetables">
|
||||
<div role="heading" id="select-groups-vegetables">Vegetables</div>
|
||||
<div role="option" data-value="carrot">Carrot</div>
|
||||
<div role="option" data-value="potato">Potato</div>
|
||||
<div role="option" data-value="tomato">Tomato</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("select-groups", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% set code %}
|
||||
@@ -365,22 +407,27 @@ toc:
|
||||
|
||||
{{ code_preview("select-disabled", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-invalid-state"><a href="#example-invalid-state">Invalid State</a></h3>
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{{ select(
|
||||
trigger_attrs={"aria-invalid": "true"},
|
||||
items=[
|
||||
{ value: "", label: "Select a role" },
|
||||
{ value: "admin", label: "Admin" },
|
||||
{ value: "editor", label: "Editor" },
|
||||
{ value: "viewer", label: "Viewer" },
|
||||
{ value: "guest", label: "Guest" }
|
||||
]
|
||||
) }}
|
||||
<div role="group" class="field" data-invalid="true">
|
||||
<label for="select-invalid-trigger">Role</label>
|
||||
{{ select(
|
||||
id="select-invalid",
|
||||
trigger_attrs={"aria-invalid": "true", "class": "w-[220px]"},
|
||||
items=[
|
||||
{ value: "", label: "Select a role" },
|
||||
{ value: "admin", label: "Admin" },
|
||||
{ value: "editor", label: "Editor" },
|
||||
{ value: "viewer", label: "Viewer" },
|
||||
{ value: "guest", label: "Guest" }
|
||||
]
|
||||
) }}
|
||||
<p class="field-error" role="alert">Select a role to continue.</p>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-disabled", code | prettyHtml) }}
|
||||
{{ code_preview("select-invalid", code | prettyHtml, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>
|
||||
|
||||
@@ -389,19 +436,19 @@ toc:
|
||||
id="select-with-icons",
|
||||
trigger_attrs={'class': 'w-[180px]'}
|
||||
) %}
|
||||
<div type="button" role="option" data-value="bar">
|
||||
<div 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">
|
||||
<div 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">
|
||||
<div role="option" data-value="pie">
|
||||
<span class="flex items-center gap-2">
|
||||
{% lucide "chart-pie", {"class": "text-muted-foreground"} %}
|
||||
Pie
|
||||
@@ -415,7 +462,7 @@ toc:
|
||||
<h3 id="example-multiple"><a href="#example-multiple">Multiple</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Enable multiple selection by setting adding <code>aria-multiselectable="true"</code> to the <code>listbox</code>. The selected options are displayed as a comma-separated list in the trigger button. Use <code>data-label</code> to specify clean text labels for options that contain HTML (like icons), otherwise the text content will be extracted automatically.</p>
|
||||
<p>Enable multiple selection by adding <code>aria-multiselectable="true"</code> to the <code>listbox</code>. The selected options are displayed as a comma-separated list in the trigger button. Use <code>data-label</code> to specify clean text labels for options that contain HTML (like icons), otherwise the text content will be extracted automatically.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
@@ -455,3 +502,25 @@ toc:
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-multiple", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>dir="rtl"</code> on the select root or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{{ select(
|
||||
id="select-rtl",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
popover_attrs={"data-align": "start"},
|
||||
items=[
|
||||
{ value: "light", label: "فاتح" },
|
||||
{ value: "dark", label: "داكن" },
|
||||
{ value: "system", label: "النظام" }
|
||||
]
|
||||
) }}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("select-rtl", code | prettyHtml) }}
|
||||
|
||||
@@ -15,8 +15,10 @@ toc:
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
- label: JavaScript events
|
||||
- label: Basecoat differences
|
||||
id: usage-html-js-4
|
||||
- label: JavaScript events
|
||||
id: usage-html-js-5
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
@@ -43,7 +45,7 @@ toc:
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -90,80 +92,60 @@ toc:
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><aside class="sidebar" aria-hidden="false"></code></dt>
|
||||
<dd>Wraps around the entire component. It can have the following attributes:
|
||||
<dd>Wraps the sidebar component. Supported attributes:
|
||||
<ul>
|
||||
<li><code>aria-hidden="true"</code>: controls the default state of the sidebar (hidden or visible).</li>
|
||||
<li><code>data-side="left"</code>: specifies the side of the sidebar (<code>left</code> or <code>right</code>, defaults to <code>left</code>).</li>
|
||||
<li><code>id</code>: target a specific sidebar from the <code>basecoat:sidebar</code> event.</li>
|
||||
<li><code>aria-hidden</code>: current open state. <code>false</code> means visible, <code>true</code> means hidden.</li>
|
||||
<li><code>data-side</code>: physical side of the viewport. Use <code>left</code> or <code>right</code>. Defaults to <code>left</code>.</li>
|
||||
<li><code>data-initial-open</code>: set to <code>false</code> to start closed on desktop.</li>
|
||||
<li><code>data-initial-mobile-open</code>: set to <code>true</code> to start open below the breakpoint.</li>
|
||||
<li><code>data-breakpoint</code>: pixel width used to switch between desktop and mobile behavior. Defaults to <code>768</code>.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><nav></code></dt>
|
||||
<dd>
|
||||
<p>The navigation element that contains the sidebar's content. It can 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>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The header of the sidebar.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>The main navigation list.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="group"></code></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>
|
||||
<dt><code class="highlight language-html"><ul></code></dt>
|
||||
<dd>List of links or buttons.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><li></code></dt>
|
||||
<dd>Individual item.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><a></code></dt>
|
||||
<dd>A link. By default, clicking on a link will close the sidebar on mobile unless the <code>data-keep-mobile-sidebar-open</code> attribute is present. Set <code>aria-current="page"</code> on the active link.</dd>
|
||||
<dt><code class="highlight language-html"><button></code></dt>
|
||||
<dd>A button. By default, clicking on a button will close the sidebar on mobile unless the <code>data-keep-mobile-sidebar-open</code> attribute is present. If your app uses buttons for navigation, set <code>aria-current="page"</code> on the active item.</dd>
|
||||
<dt><code class="highlight language-html"><details></code></dt>
|
||||
<dd>Collapsible section.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><summary></code></dt>
|
||||
<dd>Summary of the collapsible section.</dd>
|
||||
<dt><code class="highlight language-html"><ul></code></dt>
|
||||
<dd>List of links or buttons.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>The footer of the sidebar.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><nav aria-label="..."></code></dt>
|
||||
<dd>The semantic navigation landmark inside the sidebar.</dd>
|
||||
<dt><code class="highlight language-html"><header></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Top area for branding, workspace switchers, or primary controls.</dd>
|
||||
<dt><code class="highlight language-html"><section></code></dt>
|
||||
<dd>Scrollable sidebar content.</dd>
|
||||
<dt><code class="highlight language-html"><div role="group" aria-labelledby="..."></code></dt>
|
||||
<dd>A grouped set of navigation items. Use an <code>h3</code> with a matching <code>id</code> when the group has a visible label.</dd>
|
||||
<dt><code class="highlight language-html"><ul></code> / <code class="highlight language-html"><li></code></dt>
|
||||
<dd>Navigation list structure. Items may contain an <code>a</code>, a <code>button</code>, or a <code>details</code> submenu.</dd>
|
||||
<dt><code class="highlight language-html"><a aria-current="page"></code></dt>
|
||||
<dd>Use <code>aria-current="page"</code> for the active link. You can also use <code>data-active="true"</code> for non-link active items.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button"></code></dt>
|
||||
<dd>Use buttons for actions. On mobile, links and buttons close the sidebar unless they include <code>data-keep-mobile-sidebar-open</code>.</dd>
|
||||
<dt><code class="highlight language-html"><details></code> / <code class="highlight language-html"><summary></code></dt>
|
||||
<dd>Use native disclosure for submenus. The nested <code>ul</code> contains the submenu items.</dd>
|
||||
<dt><code class="highlight language-html"><footer></code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd>Bottom area for account controls, settings, or secondary actions.</dd>
|
||||
<dt><code class="highlight language-html"><main></code></dt>
|
||||
<dd>A wrapper for the content of the page.</dd>
|
||||
<dd>The sibling content wrapper. Basecoat applies the desktop margin to the sibling that follows the sidebar.</dd>
|
||||
<dt><code class="highlight language-html"><button type="button" onclick="document.dispatchEvent(new CustomEvent('basecoat:sidebar'))"></code></dt>
|
||||
<dd>A button to toggle the sidebar. If you want to use multiple sidebars you will need to add unique ids to the sidebars (i.e. <code><aside class="sidebar" id="{SIDEBAR_ID}"></code>) and refer to them in the event <code>detail</code> (i.e. <code>document.dispatchEvent(new CustomEvent('basecoat:sidebar', { detail: { id: '{SIDEBAR_ID}' } }));</code>).</dd>
|
||||
<dd>Dispatches the event used to toggle, open, or close the sidebar.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{% set code_current %}<!-- Server-side or router-driven current link -->
|
||||
<nav>
|
||||
{% set code_current %}<!-- Server-side or router-driven current item -->
|
||||
<nav aria-label="Sidebar navigation">
|
||||
<ul>
|
||||
<li><a href="/my-path?page=overview">Overview</a></li>
|
||||
<li><a href="/my-path?page=settings" aria-current="page">Settings</a></li>
|
||||
<li><button type="button" data-active="true">Active action</button></li>
|
||||
</ul>
|
||||
</nav>{% endset %}
|
||||
{{ code_block(code_current | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">Basecoat differences</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Basecoat keeps sidebar intentionally smaller than shadcn/ui. It supports fixed left/right sidebars, mobile overlay behavior, grouped navigation, active states, and native <code>details</code> submenus.</p>
|
||||
<p>It does not currently expose shadcn/ui's React provider, rail, inset/floating variants, icon-only collapse mode, menu actions, menu badges, or menu skeleton API. Those can be composed manually if needed, but they are not part of the Basecoat sidebar contract.</p>
|
||||
<p>RTL content is supported through logical spacing and borders. The <code>data-side</code> value remains physical: <code>left</code> means the left side of the viewport and <code>right</code> means the right side.</p>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-html-js-5"><a href="#usage-html-js-5">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
@@ -190,7 +172,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
|
||||
@@ -8,44 +8,103 @@ toc:
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Avatar
|
||||
id: example-avatar
|
||||
- label: Card
|
||||
id: example-card
|
||||
- label: Text
|
||||
id: example-text
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: Table
|
||||
id: example-table
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated skeleton component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<div class="flex items-center gap-4">
|
||||
<div class="bg-accent animate-pulse size-10 shrink-0 rounded-full"></div>
|
||||
<div class="skeleton 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 class="skeleton h-4 w-[150px]"></div>
|
||||
<div class="skeleton h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton", code | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Simply use the <code>animate-pulse</code> class to create a skeleton.</p>
|
||||
<p class="prose">Use the <code>skeleton</code> class with sizing utilities to create loading placeholders.</p>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-avatar"><a href="#example-avatar">Avatar</a></h3>
|
||||
|
||||
{% set code %}<div class="flex items-center gap-4">
|
||||
<div class="skeleton size-12 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-32"></div>
|
||||
<div class="skeleton h-4 w-48"></div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-avatar", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-card"><a href="#example-card">Card</a></h3>
|
||||
|
||||
{% set code %}<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>
|
||||
<div class="skeleton h-4 w-2/3"></div>
|
||||
<div class="skeleton h-4 w-1/2"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="bg-accent animate-pulse rounded-md aspect-square w-full"></div>
|
||||
<div class="skeleton aspect-square w-full"></div>
|
||||
</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-card", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-text"><a href="#example-text">Text</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-full"></div>
|
||||
<div class="skeleton h-4 w-5/6"></div>
|
||||
<div class="skeleton h-4 w-2/3"></div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-text", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-4 w-full max-w-sm">
|
||||
<div class="skeleton h-4 w-24"></div>
|
||||
<div class="skeleton h-9 w-full"></div>
|
||||
<div class="skeleton h-4 w-32"></div>
|
||||
<div class="skeleton h-9 w-full"></div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-form", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-table"><a href="#example-table">Table</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3 w-full">
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
<div class="skeleton h-8 w-full"></div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-table", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Skeleton blocks are direction-neutral. Use logical layout utilities around them when spacing depends on direction.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="flex items-center gap-4">
|
||||
<div class="skeleton size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="skeleton h-4 w-[150px]"></div>
|
||||
<div class="skeleton h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-rtl", code | prettyHtml) }}
|
||||
|
||||
@@ -5,6 +5,22 @@ description: An input where the user selects a value from within a given range.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-html-js
|
||||
- label: HTML structure
|
||||
id: usage-structure
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Controlled Label
|
||||
id: example-controlled-label
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
@@ -16,32 +32,78 @@ toc:
|
||||
min="0"
|
||||
max="100"
|
||||
value="50"
|
||||
>
|
||||
<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>{% endset %}
|
||||
>{% endset %}
|
||||
{{ code_preview("slider", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can style an <code><input type="range"></code> element by either adding the <code>input</code> class or having a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). However, the range input also requries some Javacript code to be able to paint the left side of the slider.</p>
|
||||
<p>Use a native <code><input type="range"></code> with the <code>input</code> class. Include <code>range.js</code> so Basecoat can keep the filled track in sync with the input value.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/basecoat.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/range.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="https://github.com/hunvreus/basecoat/blob/main/src/js/range.js" target="_blank">
|
||||
range.js
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{ code_block(code | prettyHtml, "html") }}
|
||||
|
||||
<h3 id="usage-structure"><a href="#usage-structure">HTML structure</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><input type="range" class="input"></code></dt>
|
||||
<dd>Native range input styled as a slider. Width utilities such as <code>w-full</code> can be added directly.</dd>
|
||||
</dl>
|
||||
<p>Basecoat intentionally uses the native range input. Unlike shadcn/ui's Base UI slider, it does not support multiple thumbs or vertical orientation.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{% set code %}<input type="range" class="input w-full" min="0" max="100" value="50">{% endset %}
|
||||
{{ code_preview("slider-default", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-controlled-label"><a href="#example-controlled-label">Controlled Label</a></h3>
|
||||
|
||||
{% set code %}<div class="grid w-full gap-3 max-w-sm">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<label class="label" for="temperature">Temperature</label>
|
||||
<output class="text-sm text-muted-foreground" for="temperature">50</output>
|
||||
</div>
|
||||
<input
|
||||
id="temperature"
|
||||
type="range"
|
||||
class="input w-full"
|
||||
min="0"
|
||||
max="100"
|
||||
value="50"
|
||||
oninput="this.previousElementSibling.querySelector('output').value = this.value"
|
||||
>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("slider-controlled-label", code, "w-full") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<input type="range" class="input w-full" min="0" max="100" value="50" disabled>{% endset %}
|
||||
{{ code_preview("slider-disabled", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code %}<div dir="rtl" class="w-full max-w-sm">
|
||||
<input type="range" class="input w-full" min="0" max="100" value="50">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("slider-rtl", code, "w-full") }}
|
||||
|
||||
+124
-24
@@ -5,50 +5,150 @@ description: A control that allows the user to toggle between checked and not ch
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Checked state
|
||||
id: checked-state
|
||||
- label: Invalid state
|
||||
id: invalid-state
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: Basic
|
||||
id: example-basic
|
||||
- label: Description
|
||||
id: example-description
|
||||
- label: Choice Card
|
||||
id: example-choice-card
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Size
|
||||
id: example-size
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<label class="label">
|
||||
<input type="checkbox" name="switch" role="switch" class="input">
|
||||
Airplane Mode
|
||||
</label>{% endset %}
|
||||
{{ code_preview("switch", code) }}
|
||||
{% set code %}<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="airplane-mode">Airplane Mode</label>
|
||||
<input type="checkbox" id="airplane-mode" role="switch" class="input">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can style a <code><input type="checkbox"></code> element as a switch by adding a <code>role="switch"</code> attribute, and adding the <code>input</code> class or having a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>).</p>
|
||||
<p>Add <code>role="switch"</code> and the <code>input</code> class to a native <code><input type="checkbox"></code>. Use a native <code><label></code> or <code>aria-label</code> for accessible naming.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" role="switch" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="checked-state"><a href="#checked-state">Checked state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use native HTML state. Add <code>checked</code> for an initially enabled switch, or read and update the element's <code>checked</code> property in JavaScript.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" role="switch" class="input" checked>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="invalid-state"><a href="#invalid-state">Invalid state</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Set <code>aria-invalid="true"</code> on the switch and, when using <code>field</code>, set <code>data-invalid="true"</code> on the field wrapper.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start justify-between gap-3" data-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-invalid-state">Marketing emails</label>
|
||||
<p class="field-error">Choose whether marketing emails are enabled.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="switch-invalid-state" role="switch" class="input" aria-invalid="true">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-invalid-state", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
<h3 id="example-basic"><a href="#example-basic">Basic</a></h3>
|
||||
|
||||
{% set code %}<form class="form 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">
|
||||
{% set code %}<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="switch-basic">Airplane Mode</label>
|
||||
<input type="checkbox" id="switch-basic" role="switch" class="input">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-basic", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-description"><a href="#example-description">Description</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start justify-between gap-3">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-description">Marketing emails</label>
|
||||
<p id="switch-description-text" class="field-description">Receive emails about new products, features, and more.</p>
|
||||
</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>
|
||||
<input type="checkbox" id="switch-description" role="switch" class="input" aria-describedby="switch-description-text" checked>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-description", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-choice-card"><a href="#example-choice-card">Choice Card</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Wrap a <code>.field</code> in a native <code><label></code> when the whole card should toggle the switch.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<label class="w-full max-w-md">
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<div class="grid gap-1.5">
|
||||
<span>Marketing emails</span>
|
||||
<span class="field-description">Receive emails about new products, features, and more.</span>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch-disabled" role="switch" disabled>
|
||||
<input type="checkbox" role="switch" class="input" checked>
|
||||
</div>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("switch-form", code, "w-full") }}
|
||||
</label>{% endset %}
|
||||
{{ code_preview("switch-choice-card", code, "w-full") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-center justify-between" data-disabled="true">
|
||||
<label for="switch-disabled">Airplane Mode</label>
|
||||
<input type="checkbox" id="switch-disabled" role="switch" class="input" disabled>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-disabled", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<div role="group" class="field flex-row items-start justify-between gap-3" data-invalid="true">
|
||||
<div class="grid gap-1.5">
|
||||
<label for="switch-invalid">Marketing emails</label>
|
||||
<p class="field-error">Choose whether marketing emails are enabled.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="switch-invalid" role="switch" class="input" aria-invalid="true">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-invalid", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-size"><a href="#example-size">Size</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-4">
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="switch-size-default">Default</label>
|
||||
<input type="checkbox" id="switch-size-default" role="switch" class="input" checked>
|
||||
</div>
|
||||
<div role="group" class="field flex-row items-center justify-between">
|
||||
<label for="switch-size-sm">Small</label>
|
||||
<input type="checkbox" id="switch-size-sm" role="switch" class="input" data-size="sm" checked>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-size", code, "w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Switch field layout uses document direction. Set <code>dir="rtl"</code> on a parent to render right-to-left.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<div dir="rtl" class="field flex-row items-center justify-between">
|
||||
<label for="switch-rtl">وضع الطيران</label>
|
||||
<input type="checkbox" id="switch-rtl" role="switch" class="input" checked>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("switch-rtl", code, "w-full max-w-sm") }}
|
||||
|
||||
@@ -5,12 +5,21 @@ description: A responsive table component.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Footer
|
||||
id: example-footer
|
||||
- label: Actions
|
||||
id: example-actions
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div class="overflow-x-auto">
|
||||
{% set code %}<div class="table-container">
|
||||
<table class="table">
|
||||
<caption>A list of your recent invoices.</caption>
|
||||
<thead>
|
||||
@@ -26,49 +35,49 @@ toc:
|
||||
<td class="font-medium">INV001</td>
|
||||
<td>Paid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$250.00</td>
|
||||
<td class="text-end">$250.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV002</td>
|
||||
<td>Pending</td>
|
||||
<td>PayPal</td>
|
||||
<td class="text-right">$150.00</td>
|
||||
<td class="text-end">$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>
|
||||
<td class="text-end">$350.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV004</td>
|
||||
<td>Paid</td>
|
||||
<td>Paypal</td>
|
||||
<td class="text-right">$450.00</td>
|
||||
<td class="text-end">$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>
|
||||
<td class="text-end">$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>
|
||||
<td class="text-end">$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>
|
||||
<td class="text-end">$300.00</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3">Total</td>
|
||||
<td class="text-right">$2,500.00</td>
|
||||
<td class="text-end">$2,500.00</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -78,10 +87,85 @@ toc:
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>table</code> class to your table element.</p>
|
||||
<p>Add the <code>table</code> class to your table element. Wrap it in <code>table-container</code> when horizontal scrolling is needed.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<table class="table">
|
||||
{% set code %}<div class="table-container">
|
||||
<table class="table">
|
||||
<!-- Content of your table -->
|
||||
</table>
|
||||
</div>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-footer"><a href="#example-footer">Footer</a></h3>
|
||||
|
||||
{% set code_footer %}<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>INV001</td>
|
||||
<td class="text-end">$250.00</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>Total</td>
|
||||
<td class="text-end">$250.00</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
{{ code_preview("table-footer", code_footer, "w-full") }}
|
||||
|
||||
<h3 id="example-actions"><a href="#example-actions">Actions</a></h3>
|
||||
|
||||
{% set code_actions %}<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-medium">Project Alpha</td>
|
||||
<td class="text-end">
|
||||
<button type="button" class="btn-xs-outline">Open</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>{% endset %}
|
||||
{{ code_preview("table-actions", code_actions, "w-full") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Tables support right-to-left layouts through native table direction and logical alignment utilities such as <code>text-end</code>.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}<div class="table-container" dir="rtl">
|
||||
<table class="table">
|
||||
<caption>قائمة الفواتير الأخيرة.</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>الفاتورة</th>
|
||||
<th>الحالة</th>
|
||||
<th class="text-end">المبلغ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-medium">INV001</td>
|
||||
<td>مدفوعة</td>
|
||||
<td class="text-end">$250.00</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("table-rtl", code_rtl, "w-full") }}
|
||||
|
||||
+154
-87
@@ -1,17 +1,17 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Tabs
|
||||
description: A set of layered sections of content—known as tab panels—that are displayed one at a time.
|
||||
description: A set of layered sections of content that are displayed one at a time.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + JavaScript
|
||||
id: usage-default
|
||||
id: usage-html-js
|
||||
children:
|
||||
- label: "Step 1: Include the JavaScript files"
|
||||
id: usage-html-js-1
|
||||
- label: "Step 2: Add your select HTML"
|
||||
- label: "Step 2: Add your tabs HTML"
|
||||
id: usage-html-js-2
|
||||
- label: HTML structure
|
||||
id: usage-html-js-3
|
||||
@@ -19,13 +19,27 @@ toc:
|
||||
id: usage-html-js-4
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Line
|
||||
id: example-line
|
||||
- label: Vertical
|
||||
id: example-vertical
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: Icons
|
||||
id: example-icons
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
- label: API Reference
|
||||
id: api-reference
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
{% set code_html %}
|
||||
{% set account_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
@@ -33,14 +47,14 @@ toc:
|
||||
<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">
|
||||
<form class="grid gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte">
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -49,6 +63,7 @@ toc:
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% set password_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
@@ -56,38 +71,43 @@ toc:
|
||||
<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">
|
||||
<form class="grid gap-4">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
<input type="password" id="demo-tabs-password-current">
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<div role="group" class="field">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new"/>
|
||||
<input type="password" id="demo-tabs-password-new">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save Password</button>
|
||||
<button type="button" class="btn">Save password</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% set tabsets_demo = [
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
] %}
|
||||
|
||||
{% set code_html %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-panels',
|
||||
id="demo-tabs-with-panels",
|
||||
tabsets=tabsets_demo,
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "class": "w-full" }
|
||||
tablist_attrs={ "class": "w-full", "aria-orientation": "horizontal" }
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("tabs", code_html | prettyHtml, "max-w-[300px] w-full") }}
|
||||
{{ code_preview("tabs", code_html | prettyHtml, "max-w-[400px] w-full") }}
|
||||
|
||||
<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">
|
||||
@@ -103,7 +123,7 @@ toc:
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -113,7 +133,7 @@ toc:
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your select HTML</a></h4>
|
||||
<h4 id="usage-html-js-2"><a href="#usage-html-js-2">Step 2: Add your tabs HTML</a></h4>
|
||||
|
||||
{{ code_block(code_html | prettyHtml, "html") }}
|
||||
|
||||
@@ -122,21 +142,19 @@ toc:
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div class="tabs"></code></dt>
|
||||
<dd>Wraps around the entire component.
|
||||
<dd>Root tabs container. It becomes horizontal or vertical based on the child tablist's <code>aria-orientation</code>.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><nav role="tablist" aria-orientation="horizontal"></code></dt>
|
||||
<dd>The tablist containing the tab buttons
|
||||
<dd>Tablist containing tab buttons. Set <code>aria-orientation="vertical"</code> for vertical tabs and <code>data-variant="line"</code> for the line style.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button role="tab" id="{ TAB_ID }" aria-controls="{ PANEL_ID }" aria-selected="false" tabindex="0"></code></dt>
|
||||
<dd>The tab button. When active, the <code>aria-selected</code> attribute is set to <code>true</code>. <code>tabindex="0"</code> is required for keyboard navigation.</dd>
|
||||
<dt><code class="highlight language-html"><button role="tab" id="{ TAB_ID }" aria-controls="{ PANEL_ID }" aria-selected="true" tabindex="0"></code></dt>
|
||||
<dd>Tab trigger. The active tab has <code>aria-selected="true"</code> and <code>tabindex="0"</code>. Inactive tabs use <code>tabindex="-1"</code>. Disabled tabs use <code>disabled</code> or <code>aria-disabled="true"</code>.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dt><code class="highlight language-html"><div role="tabpanel" id="{ PANEL_ID }" aria-labelledby="{ TAB_ID }"></code></dt>
|
||||
<dd>Panel controlled by a tab. Inactive panels are hidden.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><div role="tabpanel" id="{ PANEL_ID }" aria-labelledby="{ TAB_ID }" tabindex="-1" aria-selected="false"></code></dt>
|
||||
<dd>The tab panel. When active, the <code>aria-selected</code> attribute is set to <code>true</code>.</dd>
|
||||
</dl>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -145,7 +163,7 @@ toc:
|
||||
<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>
|
||||
<dd>Once the component is initialized, it dispatches a custom non-bubbling <code>basecoat:initialized</code> event on itself.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -156,7 +174,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -170,63 +188,112 @@ toc:
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% 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 raw_code %}{% raw %}{{ tabs(
|
||||
id="account-tabs",
|
||||
tabsets=[
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
],
|
||||
tablist_attrs={ "data-variant": "line" }
|
||||
) }}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
{% 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 %}
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
{% set tabsets_demo = [
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
] %}
|
||||
<h3 id="example-line"><a href="#example-line">Line</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets_demo,
|
||||
id="tabs-line",
|
||||
tabsets=[
|
||||
{ tab: "Account", panel: "Make changes to your account here." },
|
||||
{ tab: "Password", panel: "Change your password here." },
|
||||
{ tab: "Settings", panel: "Update your settings here." }
|
||||
],
|
||||
tablist_attrs={ "data-variant": "line" }
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-line", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-vertical"><a href="#example-vertical">Vertical</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{{ tabs(
|
||||
id="tabs-vertical",
|
||||
tabsets=[
|
||||
{ tab: "Account", panel: "Manage account settings." },
|
||||
{ tab: "Password", panel: "Update your password." },
|
||||
{ tab: "Notifications", panel: "Configure notifications." }
|
||||
],
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "class": "w-full" }
|
||||
) }}{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
tablist_attrs={ "aria-orientation": "vertical" }
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-vertical", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{{ tabs(
|
||||
id="tabs-disabled",
|
||||
tabsets=[
|
||||
{ tab: "Account", panel: "Account content." },
|
||||
{ tab: "Password", panel: "Password content.", tab_attrs: { "disabled": "disabled" } },
|
||||
{ tab: "Settings", panel: "Settings content." }
|
||||
]
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-disabled", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-icons"><a href="#example-icons">Icons</a></h3>
|
||||
|
||||
{% set icon_account %}{% lucide "user" %}Account{% endset %}
|
||||
{% set icon_password %}{% lucide "lock" %}Password{% endset %}
|
||||
{% set icon_settings %}{% lucide "settings" %}Settings{% endset %}
|
||||
{% set code %}
|
||||
{{ tabs(
|
||||
id="tabs-icons",
|
||||
tabsets=[
|
||||
{ tab: icon_account, panel: "Account content." },
|
||||
{ tab: icon_password, panel: "Password content." },
|
||||
{ tab: icon_settings, panel: "Settings content." }
|
||||
]
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-icons", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Tabs support document direction. Set <code>dir="rtl"</code> on the tabs root or a parent element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}
|
||||
<div dir="rtl">
|
||||
{{ tabs(
|
||||
id="tabs-rtl",
|
||||
tabsets=[
|
||||
{ tab: "الحساب", panel: "إعدادات الحساب." },
|
||||
{ tab: "كلمة المرور", panel: "تحديث كلمة المرور." },
|
||||
{ tab: "الإشعارات", panel: "إعدادات الإشعارات." }
|
||||
],
|
||||
tablist_attrs={ "data-variant": "line" }
|
||||
) }}
|
||||
</div>
|
||||
{% endset %}
|
||||
{{ code_preview("tabs-rtl", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h2 id="api-reference"><a href="#api-reference">API Reference</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code class="highlight language-html">.tabs</code></dt>
|
||||
<dd>Root component class.</dd>
|
||||
<dt><code class="highlight language-html">[role="tablist"]</code></dt>
|
||||
<dd>Tablist. Supports <code>aria-orientation="horizontal"</code>, <code>aria-orientation="vertical"</code>, and <code>data-variant="line"</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="tab"]</code></dt>
|
||||
<dd>Tab trigger. Supports <code>aria-selected</code>, <code>disabled</code>, and <code>aria-disabled="true"</code>.</dd>
|
||||
<dt><code class="highlight language-html">[role="tabpanel"]</code></dt>
|
||||
<dd>Tab panel associated with a tab by <code>aria-controls</code> and <code>aria-labelledby</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@@ -8,83 +8,93 @@ toc:
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Field
|
||||
id: example-field
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: With label
|
||||
id: example-with-label
|
||||
- label: With text
|
||||
id: example-with-text
|
||||
- label: With button
|
||||
id: example-with-button
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Button
|
||||
id: example-button
|
||||
- label: Form
|
||||
id: example-form
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<textarea class="textarea" placeholder="Type your message here"></textarea>{% endset %}
|
||||
{{ code_preview("button", code, "w-full") }}
|
||||
{{ code_preview("textarea", code, class="w-full max-w-xs") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>textarea</code> class to your <code><textarea></code> element 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 textarea invalid.</p>
|
||||
<p>Add the <code>textarea</code> class to a <code><textarea></code> element. Textareas inside a <code>form</code> or <code>field</code> container also receive the same styling automatically.</p>
|
||||
<p>Use native HTML attributes for behavior: <code>disabled</code> for disabled textareas and <code>aria-invalid="true"</code> for invalid state styling.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
{{ code_preview("textarea-default", code, "w-full") }}
|
||||
<h3 id="example-field"><a href="#example-field">Field</a></h3>
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
<section class="prose">
|
||||
<p>Use <code>field</code> with a label and description to create an accessible textarea.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<textarea class="textarea" placeholder="Type your message here" aria-invalid="true"></textarea>{% endset %}
|
||||
{{ code_preview("textarea-invalid", code, "w-full") }}
|
||||
{% set code_field %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="textarea-field">Message</label>
|
||||
<textarea id="textarea-field" placeholder="Type your message here"></textarea>
|
||||
<p>Write a short message.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-field", code_field, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<textarea class="textarea" placeholder="Type your message here" disabled></textarea>{% endset %}
|
||||
{{ code_preview("textarea-disabled", code, "w-full") }}
|
||||
{% set code_disabled %}<textarea class="textarea" placeholder="Type your message here" disabled></textarea>{% endset %}
|
||||
{{ code_preview("textarea-disabled", code_disabled, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-with-label"><a href="#example-with-label">With label</a></h3>
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label for="textarea-with-label" class="label">Label</label>
|
||||
<textarea id="textarea-with-label" class="textarea" placeholder="Type your message here"></textarea>
|
||||
{% set code_invalid %}<div role="group" class="field w-full max-w-xs">
|
||||
<label for="textarea-invalid">Message</label>
|
||||
<textarea class="textarea" id="textarea-invalid" placeholder="Type your message here" aria-invalid="true"></textarea>
|
||||
<p>Message is required.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-with-label", code, "w-full") }}
|
||||
{{ code_preview("textarea-invalid", code_invalid, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-with-text"><a href="#example-with-text">With text</a></h3>
|
||||
<h3 id="example-button"><a href="#example-button">Button</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label for="textarea-with-label" class="label">Label</label>
|
||||
<textarea id="textarea-with-label" 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>{% endset %}
|
||||
{{ code_preview("textarea-with-text", code, "w-full") }}
|
||||
|
||||
<h3 id="example-with-button"><a href="#example-with-button">With button</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<textarea id="textarea-with-label" class="textarea" placeholder="Type your message here"></textarea>
|
||||
{% set code_button %}<div class="grid w-full max-w-xs gap-3">
|
||||
<textarea class="textarea" placeholder="Type your message here"></textarea>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-form", code, "w-full") }}
|
||||
{{ code_preview("textarea-button", code_button, class="w-full max-w-xs") }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form space-y-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-form" class="label">Bio</label>
|
||||
{% set code_form %}<form class="w-full max-w-sm space-y-6">
|
||||
<div role="group" class="field">
|
||||
<label for="textarea-form">Bio</label>
|
||||
<textarea id="textarea-form" placeholder="Tell us a bit about yourself"></textarea>
|
||||
<p class="text-muted-foreground text-sm">You can @mention other users and organizations.</p>
|
||||
<p>You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("textarea-form", code, "w-full max-w-sm") }}
|
||||
{{ code_preview("textarea-form", code_form, class="w-full max-w-sm") }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>Textareas support right-to-left layouts through native browser direction handling. Add <code>dir="rtl"</code> to the textarea, field, or an ancestor.</p>
|
||||
</section>
|
||||
|
||||
{% set code_rtl %}<div role="group" class="field w-full max-w-xs" dir="rtl">
|
||||
<label for="textarea-rtl">التعليقات</label>
|
||||
<textarea id="textarea-rtl" placeholder="تعليقاتك تساعدنا على التحسين..." rows="4"></textarea>
|
||||
<p>شاركنا أفكارك حول الخدمة.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-rtl", code_rtl, class="w-full max-w-xs") }}
|
||||
|
||||
@@ -78,7 +78,7 @@ toc:
|
||||
Components with JavaScript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
<a class="badge-outline" href="/cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
@@ -232,7 +232,7 @@ toc:
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
<a class="badge-outline" href="/cli#macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Tooltip
|
||||
description: A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.
|
||||
description: A popup that displays information related to an element when the mouse hovers over it.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
@@ -10,44 +10,86 @@ toc:
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Right
|
||||
id: example-right
|
||||
- label: Bottom + Start
|
||||
id: example-bottom-start
|
||||
- label: Sides
|
||||
id: example-sides
|
||||
- label: Icon Button
|
||||
id: example-icon-button
|
||||
- label: Disabled Button
|
||||
id: example-disabled-button
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: RTL
|
||||
id: example-rtl
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Add to library">Hover</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
{{ code_preview("tooltip", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>On any element, set the value of the <code>data-tooltip</code> attribute to the text you want to display in the tooltip. Additionally, you can set:</p>
|
||||
<ul>
|
||||
<li><code>data-side</code> attribute to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the tooltip.</li>
|
||||
<li><code>data-align</code> attribute to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the tooltip.</li>
|
||||
</ul>
|
||||
<p>Add <code>data-tooltip</code> to the trigger element. Use <code>data-side</code> and <code>data-align</code> to change the position.</p>
|
||||
<p>Basecoat tooltips are CSS-only and text-only. This intentionally differs from shadcn/ui's composed tooltip content, which can render arbitrary markup and uses a portal.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Tooltip text" data-side="bottom" data-align="center">Bottom left</button>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Tooltip text" data-side="bottom" data-align="center">Bottom</button>{% endset %}
|
||||
{{ code_block(code | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<dl>
|
||||
<dt><code>data-tooltip</code></dt>
|
||||
<dd>Tooltip text.</dd>
|
||||
<dt><code>data-side</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd><code>top</code>, <code>bottom</code>, <code>left</code>, <code>right</code>, <code>inline-start</code>, or <code>inline-end</code>. Defaults to <code>top</code>.</dd>
|
||||
<dt><code>data-align</code> <span class="badge-secondary">Optional</span></dt>
|
||||
<dd><code>start</code>, <code>center</code>, or <code>end</code>. Defaults to <code>center</code>.</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Default tooltip">Default</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
{{ code_preview("tooltip-default", code) }}
|
||||
|
||||
<h3 id="example-right"><a href="#example-right">Right</a></h3>
|
||||
<h3 id="example-sides"><a href="#example-sides">Sides</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Right side tooltip" data-side="right">Right</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
{% set code %}<div class="flex flex-wrap gap-2">
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="inline-start">Inline Start</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="top">Top</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="bottom">Bottom</button>
|
||||
<button class="btn-outline" data-tooltip="Tooltip" data-side="inline-end">Inline End</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("tooltip-sides", code) }}
|
||||
|
||||
<h3 id="example-bottom-start"><a href="#example-bottom-start">Bottom + Start</a></h3>
|
||||
<h3 id="example-icon-button"><a href="#example-icon-button">Icon Button</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Bottom side and start aligned tooltip" data-side="bottom" data-align="start">Bottom + Start</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
{% set code %}<button class="btn-icon-ghost" data-tooltip="Additional information">
|
||||
{% lucide "info" %}
|
||||
<span class="sr-only">Info</span>
|
||||
</button>{% endset %}
|
||||
{{ code_preview("tooltip-icon-button", code) }}
|
||||
|
||||
<h3 id="example-disabled-button"><a href="#example-disabled-button">Disabled Button</a></h3>
|
||||
|
||||
{% set code %}<span class="inline-block" data-tooltip="This feature is currently unavailable">
|
||||
<button class="btn-outline" disabled>Disabled</button>
|
||||
</span>{% endset %}
|
||||
{{ code_preview("tooltip-disabled-button", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<a href="#" class="text-primary text-sm underline-offset-4 hover:underline" data-tooltip="Click to read the documentation">Learn more</a>{% endset %}
|
||||
{{ code_preview("tooltip-link", code) }}
|
||||
|
||||
<h3 id="example-rtl"><a href="#example-rtl">RTL</a></h3>
|
||||
|
||||
{% set code %}<div dir="rtl" class="flex gap-2">
|
||||
<button class="btn-outline" data-tooltip="تلميح" data-side="inline-start">Inline Start</button>
|
||||
<button class="btn-outline" data-tooltip="تلميح" data-side="inline-end">Inline End</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("tooltip-rtl", code) }}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Customization
|
||||
description: Configure icons, themes, fonts, and style overrides.
|
||||
toc:
|
||||
- label: Icons
|
||||
id: icons
|
||||
- label: Theming
|
||||
id: theming
|
||||
- label: Fonts
|
||||
id: fonts
|
||||
- label: Style overrides
|
||||
id: style-overrides
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<h2 id="icons"><a href="#icons">Icons</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>Basecoat uses <a href="https://lucide.dev" target="_blank">Lucide icons</a>. You have three options:</p>
|
||||
|
||||
<ul>
|
||||
<li><b>Copy SVG code</b>: Visit <a href="https://lucide.dev/icons" target="_blank">lucide.dev/icons</a>, click any icon to copy the SVG, and paste it directly in your HTML. Best for simple projects or occasional icon usage.</li>
|
||||
<li><b>Install lucide</b>: Follow the <a href="https://lucide.dev/guide/installation" target="_blank">installation guide</a> to install the <code>lucide</code> package and dynamically render icons. Best for projects that need many icons or want tree-shaking.</li>
|
||||
<li><b>Use framework packages</b>: Browse <a href="https://lucide.dev/packages" target="_blank">framework-specific packages</a> (React, Vue, Angular, etc.) for component-based icon usage. Best if you're using a supported framework.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="https://lucide.dev" target="_blank">
|
||||
Browse icons
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://lucide.dev/guide/installation" target="_blank">
|
||||
Installation guide
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://lucide.dev/packages" target="_blank">
|
||||
Packages
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 id="theming"><a href="#theming">Theming</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can import any <a href="https://shadcn-ui.com" target="_blank">shadcn/ui</a> compatible theme (e.g. <a href="https://tweakcn.com" target="_blank">tweakcn</a>). For example:</p>
|
||||
<ul>
|
||||
<li>Go to <a href="https://ui.shadcn.com/themes" target="_blank">ui.shadcn.com/themes</a> and select a theme.</li>
|
||||
<li>Click "Copy code" and save the theme variables in a file (e.g. <code>theme.css</code>).</li>
|
||||
<li>
|
||||
Import the theme in your CSS file:
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css/sera";
|
||||
@import "./theme.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="https://ui.shadcn.com/docs/theming" target="_blank">
|
||||
About shadcn/ui theming
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 id="fonts"><a href="#fonts">Fonts</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>Basecoat does not ship web fonts by default. The Basecoat docs site uses the same font choices as the current shadcn/ui site: <a href="https://fonts.google.com/specimen/Geist" target="_blank">Geist</a> for sans and heading text, and <a href="https://fonts.google.com/specimen/Geist+Mono" target="_blank">Geist Mono</a> for monospace text.</p>
|
||||
<p>To use the same setup, load the fonts and override the font tokens after importing Basecoat:</p>
|
||||
</div>
|
||||
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css/sera";
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@400&display=swap");
|
||||
|
||||
:root {
|
||||
--font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-heading: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
}{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
|
||||
<div class="prose">
|
||||
<p>Keep font overrides in a small CSS file imported after Basecoat. This makes fonts a project-level customization instead of a Basecoat package default.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="style-overrides"><a href="#style-overrides">Style overrides</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can override default styles using Tailwind:</p>
|
||||
</div>
|
||||
|
||||
{% set code %}<button class="btn font-normal">Click me</button>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<div class="prose">
|
||||
<p>You can also extend or override the existing styles in your own Tailwind files:</p>
|
||||
</div>
|
||||
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css/sera";
|
||||
@import "./custom.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
|
||||
<div class="prose">
|
||||
<p>More importantly, you can use the <a href="#theming">theme variables</a> to customize many aspects of the components (colors, fonts, sizes, etc).</p>
|
||||
|
||||
<p><b>If you want to make more drastic changes</b>, you can <a href="https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css" target="_blank">copy the <code>basecoat.css</code> file</a> into your project and make your changes there.</p>
|
||||
</div>
|
||||
+30
-30
@@ -91,21 +91,21 @@ layout: layouts/layout.njk
|
||||
<p>Manage your cookie settings here.</p>
|
||||
</header>
|
||||
<section class="text-sm grid gap-6">
|
||||
<label class="flex items-center justify-between gap-2">
|
||||
<label class="label flex items-center justify-between gap-2 text-sm font-normal normal-case tracking-normal">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="font-medium">Strictly Necessary</div>
|
||||
<div class="text-muted-foreground">These cookies are essential in order to use the website and use its features.</div>
|
||||
</div>
|
||||
<input type="checkbox" role="switch" class="input" checked />
|
||||
</label>
|
||||
<label class="flex items-center justify-between gap-2">
|
||||
<label class="label flex items-center justify-between gap-2 text-sm font-normal normal-case tracking-normal">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="font-medium">Functional Cookies</div>
|
||||
<div class="text-muted-foreground">These cookies allow the website to provide personalized functionality.</div>
|
||||
</div>
|
||||
<input type="checkbox" role="switch" class="input" />
|
||||
</label>
|
||||
<label class="flex items-center justify-between gap-2">
|
||||
<label class="label flex items-center justify-between gap-2 text-sm font-normal normal-case tracking-normal">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="font-medium">Performance Cookies</div>
|
||||
<div class="text-muted-foreground">These cookies help to improve the performance of the website.</div>
|
||||
@@ -124,7 +124,7 @@ layout: layouts/layout.njk
|
||||
<p>Add a new payment method to your account.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<form class="grid gap-6">
|
||||
<ul class="flex gap-4">
|
||||
<li class="flex-1">
|
||||
<input type="radio" name="payment-method-type" id="payment-method-type-apple" class="hidden peer" />
|
||||
@@ -149,21 +149,21 @@ layout: layouts/layout.njk
|
||||
</li>
|
||||
</ul>
|
||||
<div class="grid gap-2">
|
||||
<label for="payment-method-name">Name</label>
|
||||
<input type="text" id="payment-method-name" placeholder="John Doe" />
|
||||
<label class="label" for="payment-method-name">Name</label>
|
||||
<input class="input" type="text" id="payment-method-name" placeholder="John Doe" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label for="payment-method-city">City</label>
|
||||
<input type="text" id="payment-method-city" placeholder="New York" />
|
||||
<label class="label" for="payment-method-city">City</label>
|
||||
<input class="input" type="text" id="payment-method-city" placeholder="New York" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label for="payment-method-card-number">Card Number</label>
|
||||
<input type="text" id="payment-method-card-number" placeholder="1234 5678 9012 3456" />
|
||||
<label class="label" for="payment-method-card-number">Card Number</label>
|
||||
<input class="input" type="text" id="payment-method-card-number" placeholder="1234 5678 9012 3456" />
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="payment-method-expiration-month">Expires</label>
|
||||
<select id="payment-method-expiration-month" class="w-full">
|
||||
<label class="label" for="payment-method-expiration-month">Expires</label>
|
||||
<select id="payment-method-expiration-month" class="select w-full">
|
||||
<option value="01">January</option>
|
||||
<option value="02">February</option>
|
||||
<option value="03">March</option>
|
||||
@@ -179,8 +179,8 @@ layout: layouts/layout.njk
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="payment-method-expiration-year">Year</label>
|
||||
<select id="payment-method-expiration-year" class="w-full">
|
||||
<label class="label" for="payment-method-expiration-year">Year</label>
|
||||
<select id="payment-method-expiration-year" class="select w-full">
|
||||
<option value="2024">2024</option>
|
||||
<option value="2025">2025</option>
|
||||
<option value="2026">2026</option>
|
||||
@@ -195,8 +195,8 @@ layout: layouts/layout.njk
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="payment-method-cvv">CVV</label>
|
||||
<input type="text" id="payment-method-cvv" placeholder="123" maxlength="4" class="w-full" />
|
||||
<label class="label" for="payment-method-cvv">CVV</label>
|
||||
<input type="text" id="payment-method-cvv" placeholder="123" maxlength="4" class="input w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn w-full">Continue</button>
|
||||
@@ -271,17 +271,17 @@ layout: layouts/layout.njk
|
||||
<span class="bg-card px-2 text-muted-foreground">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form grid gap-6">
|
||||
<form class="grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-card-form-email">Email</label>
|
||||
<input type="email" id="demo-card-form-email">
|
||||
<label class="label" for="demo-card-form-email">Email</label>
|
||||
<input class="input" type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="demo-card-form-password">Password</label>
|
||||
<label class="label" for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ml-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input type="password" id="demo-card-form-password">
|
||||
<input class="input" type="password" id="demo-card-form-password">
|
||||
</div>
|
||||
<button type="button" class="btn w-full">Create an account</button>
|
||||
</form>
|
||||
@@ -294,11 +294,11 @@ layout: layouts/layout.njk
|
||||
<p>What area are you having problems with?</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<form class="grid gap-6">
|
||||
<div class="flex gap-4">
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="report-issue-area">Area</label>
|
||||
<select id="report-issue-area" class="w-full">
|
||||
<label class="label" for="report-issue-area">Area</label>
|
||||
<select id="report-issue-area" class="select w-full">
|
||||
<option value="team">Team</option>
|
||||
<option value="billing">Billing</option>
|
||||
<option value="account">Account</option>
|
||||
@@ -307,8 +307,8 @@ layout: layouts/layout.njk
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="report-issue-security-level">Security Level</label>
|
||||
<select id="report-issue-security-level" class="w-full">
|
||||
<label class="label" for="report-issue-security-level">Security Level</label>
|
||||
<select id="report-issue-security-level" class="select w-full">
|
||||
<option value="1">Severity 1 (Highest)</option>
|
||||
<option value="2">Severity 2</option>
|
||||
<option value="3">Severity 3</option>
|
||||
@@ -317,12 +317,12 @@ layout: layouts/layout.njk
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label for="report-issue-subject">Subject</label>
|
||||
<input type="text" id="report-issue-subject" placeholder="I need help with..." class="w-full" />
|
||||
<label class="label" for="report-issue-subject">Subject</label>
|
||||
<input type="text" id="report-issue-subject" placeholder="I need help with..." class="input w-full" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label for="report-issue-description">Description</label>
|
||||
<textarea id="report-issue-description" placeholder="Please include all information relevant to your issue." class="w-full"></textarea>
|
||||
<label class="label" for="report-issue-description">Description</label>
|
||||
<textarea id="report-issue-description" placeholder="Please include all information relevant to your issue." class="textarea w-full"></textarea>
|
||||
</div>
|
||||
<footer class="flex items-center gap-4 justify-between">
|
||||
<button type="button" class="btn-sm-ghost">Cancel</button>
|
||||
|
||||
+36
-153
@@ -13,28 +13,18 @@ toc:
|
||||
- label: Installing using NPM
|
||||
id: install-npm
|
||||
children:
|
||||
- label: Add Tailwind CSS
|
||||
- label: "Step 1: Add Tailwind CSS"
|
||||
id: install-npm-tailwind
|
||||
- label: Add Basecoat
|
||||
- label: "Step 2: Add Basecoat"
|
||||
id: install-npm-basecoat
|
||||
- label: Import basecoat in your CSS
|
||||
- label: "Step 3: Import Basecoat"
|
||||
id: install-npm-import
|
||||
- label: (Optional) Add JavaScript files
|
||||
- label: "Step 4: Add JavaScript files"
|
||||
id: install-npm-js
|
||||
- label: That's it
|
||||
- label: "Step 5: Use components"
|
||||
id: install-npm-done
|
||||
- label: Components with JavaScript
|
||||
id: install-js
|
||||
- label: Use the CLI
|
||||
id: install-cli
|
||||
- label: Use Nunjucks or Jinja macros
|
||||
id: install-macros
|
||||
- label: Icons
|
||||
id: install-icons
|
||||
- label: Theming
|
||||
id: install-theming
|
||||
- label: Customization
|
||||
id: install-customization
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
@@ -52,6 +42,13 @@ toc:
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/all.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The default CDN stylesheet is the Basecoat backward-compatible default. To use a specific style, load the matching standalone stylesheet instead:</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/basecoat-sera.cdn.min.css">{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<h3 id="install-cdn-specific"><a href="#install-cdn-specific">Install specific components</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
@@ -65,16 +62,16 @@ toc:
|
||||
|
||||
<h2 id="install-npm"><a href="#install-npm">Install using NPM</a></h2>
|
||||
|
||||
<ol class="[&>h3]:step steps mb-12 md:ml-4 md:border-l md:pl-8">
|
||||
<li class="step">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-tailwind"><a href="#install-npm-tailwind">Add Tailwind CSS</a></h3>
|
||||
<div class="grid gap-8 mb-12">
|
||||
<section>
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-tailwind"><a href="#install-npm-tailwind">Step 1: Add Tailwind CSS</a></h3>
|
||||
<div class="prose">
|
||||
<p>Basecoat uses Tailwind CSS. You need to install it in your project.</p>
|
||||
<p><a href="https://tailwindcss.com/docs/installation" target="_blank">Follow the Tailwind CSS installation instructions to get started.</a></p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="step mt-8">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-basecoat"><a href="#install-npm-basecoat">Add Basecoat</a></h3>
|
||||
</section>
|
||||
<section>
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-basecoat"><a href="#install-npm-basecoat">Step 2: Add Basecoat</a></h3>
|
||||
<div class="prose">
|
||||
<p>Add Basecoat to your Tailwind CSS file.</p>
|
||||
</div>
|
||||
@@ -92,15 +89,21 @@ toc:
|
||||
<div class="prose">
|
||||
<p>Alternatively, you can directly <a href="https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css" target="_blank">copy the <code>basecoat.css</code> file</a> into your project.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="step mt-8">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-import"><a href="#install-npm-import">Import basecoat in your CSS</a></h3>
|
||||
</section>
|
||||
<section>
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-import"><a href="#install-npm-import">Step 3: Import Basecoat</a></h3>
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
</li>
|
||||
<li class="step mt-8">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-js"><a href="#install-npm-js">(Optional) Add JavaScript files</a></h3>
|
||||
<div class="prose">
|
||||
<p>The default import is the Basecoat backward-compatible default. To use a specific style, import the standalone style bundle instead:</p>
|
||||
</div>
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css/sera";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
</section>
|
||||
<section>
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-js"><a href="#install-npm-js">Step 4: Add JavaScript files</a></h3>
|
||||
<div class="prose">
|
||||
<p>Some components <a href="#install-js">need some JavaScript</a> (e.g. the <a href="/components/tabs">Tabs</a> component). There are two ways to get it into your project:</p>
|
||||
|
||||
@@ -131,15 +134,15 @@ import 'basecoat-css/tabs';{% endraw %}{% endset %}
|
||||
<div class="prose">
|
||||
<p>See each component page for the minimal script required.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="step mt-8">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-done"><a href="#install-npm-done">That's it</a></h3>
|
||||
</section>
|
||||
<section>
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-npm-done"><a href="#install-npm-done">Step 5: Use components</a></h3>
|
||||
<div class="prose">
|
||||
<p>You can now use any of the Basecoat classes in your project (e.g. <code>btn</code>, <code>card</code>, <code>input</code>, etc). Read the documentation about each component to get started (e.g. <a href="/components/button">button</a>, <a href="/components/card">card</a>, <a href="/components/input">input</a>, etc).</p>
|
||||
<p>Some components (e.g. <a href="/components/select">Select</a>) require <a href="#install-js">JavaScript code to work</a>.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<h2 id="install-js"><a href="#install-js">Components with JavaScript</a></h2>
|
||||
|
||||
@@ -150,126 +153,6 @@ import 'basecoat-css/tabs';{% endraw %}{% endset %}
|
||||
|
||||
<ul>
|
||||
<li><b>CDN</b>: you either <a href="#install-cdn-all">add the code for all of the components</a> to the <code><head></code> of your HTML file, or just the file for the component you want to use as instructed on the component's page.</li>
|
||||
<li><b>Local file</b>: you download the script as a separate file and include it in your project (<a href="#install-cli">or let the CLI do it for you</a>). Once again, you have the choice to include the file for all components at once (<a><code>all.min.js</code> or <code>all.js</code>).</p>
|
||||
<li><b>Local file</b>: you download the script as a separate file and include it in your project. Once again, you have the choice to include the file for all components at once (<code>all.min.js</code> or <code>all.js</code>).</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="install-cli"><a href="#install-cli">Use the CLI</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>If you're using Nunjucks or Jinja, you can use the CLI to install the CSS and macros for any of the complex components(e.g. <a href="/components/dialog">Dialog</a>). It will copy the JS and <code>.html.jinja</code> or <code>.njk</code> macros into your code.</p>
|
||||
<p>For example, to add the Dialog component, run one of the following commands:</p>
|
||||
</div>
|
||||
|
||||
{% set code_npm_exec %}npx basecoat-cli add dialog{% endset %}
|
||||
{% set code_pnpm_exec %}pnpm dlx basecoat-cli add dialog{% endset %}
|
||||
{% set code_bun_exec %}bunx basecoat-cli add dialog{% endset %}
|
||||
{% set code_yarn_exec %}yarn dlx basecoat-cli add dialog{% endset %}
|
||||
{% set exec_tabsets = [
|
||||
{ tab: "npm", panel: code_block(code_npm_exec, class="") },
|
||||
{ tab: "pnpm", panel: code_block(code_pnpm_exec, class="") },
|
||||
{ tab: "bun", panel: code_block(code_bun_exec, class="") },
|
||||
{ tab: "yarn", panel: code_block(code_yarn_exec, class="") }
|
||||
] %}
|
||||
{{ tabs(id="cli-execution", tabsets=exec_tabsets, label="Execute CLI commands to add components:") }}
|
||||
|
||||
<div class="prose my-6">
|
||||
<p>You will be asked to confirm the template engine and destination directory for the JavaScript and macros.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="install-macros"><a href="#install-macros">Use Nunjucks or Jinja macros</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>For more complex components, you can use <a href="https://mozilla.github.io/nunjucks/" target="_blank">Nunjucks</a> or <a href="https://jinja.palletsprojects.com" target="_blank">Jinja</a> macros to help you generate the HTML and JavaScript code.</p>
|
||||
<p>To install them, either <a href="https://github.com/hunvreus/basecoat/tree/main/src" target="_blank">copy them directly from the GitHub repository</a> or use <a href="#install-cli">the CLI</a> to do the work for you.</p>
|
||||
<p>For example, here's how you could use the <code>select()</code> macro to generate the HTML and JavaScript code for a <a href="/components/select">Select</a> component:</p>
|
||||
</div>
|
||||
|
||||
{% set code %}{% raw %}{% call select() %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
<div role="option" data-value="pineapple">Grapes</div>
|
||||
<div role="option" data-value="pineapple">Pineapple</div>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(code, "jinja") }}
|
||||
|
||||
<h2 id="install-icons"><a href="#install-icons">Icons</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>Basecoat uses <a href="https://lucide.dev" target="_blank">Lucide icons</a>. You have three options:</p>
|
||||
|
||||
<ul>
|
||||
<li><b>Copy SVG code</b>: Visit <a href="https://lucide.dev/icons" target="_blank">lucide.dev/icons</a>, click any icon to copy the SVG, and paste it directly in your HTML. Best for simple projects or occasional icon usage.</li>
|
||||
<li><b>Install lucide</b>: Follow the <a href="https://lucide.dev/guide/installation" target="_blank">installation guide</a> to install the <code>lucide</code> package and dynamically render icons. Best for projects that need many icons or want tree-shaking.</li>
|
||||
<li><b>Use framework packages</b>: Browse <a href="https://lucide.dev/packages" target="_blank">framework-specific packages</a> (React, Vue, Angular, etc.) for component-based icon usage. Best if you're using a supported framework.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="https://lucide.dev" target="_blank">
|
||||
Browse icons
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://lucide.dev/guide/installation" target="_blank">
|
||||
Installation guide
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://lucide.dev/packages" target="_blank">
|
||||
Packages
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 id="install-theming"><a href="#install-theming">Theming</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can import any <a href="https://shadcn-ui.com" target="_blank">shadcn/ui</a> compatible theme (e.g. <a href="https://tweakcn.com" target="_blank">tweakcn</a>). For example:</p>
|
||||
<ul>
|
||||
<li>Go to <a href="https://ui.shadcn.com/themes" target="_blank">ui.shadcn.com/themes</a> and select a theme.</li>
|
||||
<li>Click "Copy code" and save the theme variables in a file (e.g. <code>theme.css</code>).</li>
|
||||
<li>
|
||||
Import the theme in your CSS file:
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
@import "./theme.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="https://ui.shadcn.com/docs/theming" target="_blank">
|
||||
About shadcn/ui theming
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 id="install-customization"><a href="#install-manual">Customization</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<section class="prose">
|
||||
<p>You can override default styles using Tailwind:</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<button class="btn font-normal ">Click me</button>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>You can also extend or override the existing styles in your own Tailwind files:</p>
|
||||
</section>
|
||||
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
@import "./custom.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>More importantly, you can use the <a href="#install-theming">theme variables</a> to customize many aspects of the components (colors, fonts, sizes, etc).</p>
|
||||
|
||||
<p><b>If you want to make more drastic changes</b>, you can <a href="https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css" target="_blank">copy the <code>basecoat.css</code> file</a> into your project and make your changes there.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ toc:
|
||||
<h2 id="how-it-works"><a href="#how-it-works">How it works</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>Add a single CSS file to your Tailwind setup. Use components by dropping in simple classes like <code>btn</code>, <code>form</code>, or <code>card</code>.</p>
|
||||
<p>Add a single CSS file to your Tailwind setup. Use components by dropping in simple classes like <code>btn</code>, <code>field</code>, or <code>card</code>.</p>
|
||||
|
||||
<p>Some components (like modals or dropdowns) use a tiny bit of vanilla JavaScript. You can skip it if you don't need interactivity.</p>
|
||||
|
||||
|
||||
+3
-3
@@ -21,9 +21,10 @@ Usage
|
||||
- CSS‑only components: use classes like `btn`, `card`, `input`, etc.
|
||||
- JS components: include the JS and use required roles/attributes (see below).
|
||||
|
||||
Theming
|
||||
- Tailwind projects can override design tokens (e.g., `primary`, `muted-foreground`).
|
||||
Theming and customization
|
||||
- Tailwind projects can override design tokens (e.g., `primary`, `muted-foreground`) and font tokens (`--font-sans`, `--font-mono`).
|
||||
- Without Tailwind, use CDN CSS and add overrides after the link tag.
|
||||
- See `/customization` for icons, themes, fonts, and style overrides.
|
||||
|
||||
Common pitfalls
|
||||
- Do not invent class names; use documented classes only.
|
||||
@@ -38,7 +39,6 @@ Components
|
||||
- Textarea (CSS): `.textarea` styled multiline input.
|
||||
- Label (CSS): `.label` for associated form labels.
|
||||
- Field (CSS): `.field` wrapper for label, input, hint, error.
|
||||
- Form (CSS): `.form` patterns for layout and validation styling.
|
||||
- Checkbox (CSS): styled native checkbox; use `input[type='checkbox']`.
|
||||
- Radio Group (CSS): `.radio-group` pattern using native radios.
|
||||
- Switch (CSS): styled checkbox with `role="switch"` if needed.
|
||||
|
||||
+31
-26
@@ -13,29 +13,34 @@ Legend:
|
||||
|
||||
| Component | Status | Basecoat CSS | Upstream Base | Upstream Styles | Notes |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| alert | pending | `src/css/components/alert.css` | `apps/v4/registry/bases/base/ui/alert.tsx` | `style-*.css` | |
|
||||
| badge | pending | `src/css/components/badge.css` | `apps/v4/registry/bases/base/ui/badge.tsx` | `style-*.css` | |
|
||||
| button | done | `src/css/components/button.css` | `apps/v4/registry/bases/base/ui/button.tsx` | all 8 `style-*.css` | Standalone button rules are mapped across all eight styles, including xs sizing and icon-aware padding. Drift fixed: Maia base radius now uses upstream `rounded-4xl`. Button-group adjacency details remain separate and should be handled during button-group migration. |
|
||||
| button-group | pending | `src/css/components/button-group.css` | `apps/v4/registry/bases/base/ui/button-group.tsx` | `style-*.css` | |
|
||||
| card | pending | `src/css/components/card.css` | `apps/v4/registry/bases/base/ui/card.tsx` | `style-*.css` | |
|
||||
| checkbox | pending | `src/css/components/checkbox.css` | `apps/v4/registry/bases/base/ui/checkbox.tsx` | `style-*.css` | |
|
||||
| collapsible | pending | `src/css/components/collapsible.css` | `apps/v4/registry/bases/base/ui/collapsible.tsx` | `style-*.css` | |
|
||||
| command | pending | `src/css/components/command.css` | `apps/v4/registry/bases/base/ui/command.tsx` | `style-*.css` | |
|
||||
| dialog | pending | `src/css/components/dialog.css` | `apps/v4/registry/bases/base/ui/dialog.tsx` | `style-*.css` | |
|
||||
| dropdown-menu | pending | `src/css/components/dropdown-menu.css` | `apps/v4/registry/bases/base/ui/dropdown-menu.tsx` | `style-*.css` | |
|
||||
| field | pending | `src/css/components/field.css` | `apps/v4/registry/bases/base/ui/field.tsx` | `style-*.css` | |
|
||||
| input | pending | `src/css/components/input.css` | `apps/v4/registry/bases/base/ui/input.tsx` | `style-*.css` | |
|
||||
| kbd | pending | `src/css/components/kbd.css` | `apps/v4/registry/bases/base/ui/kbd.tsx` | `style-*.css` | |
|
||||
| label | pending | `src/css/components/label.css` | `apps/v4/registry/bases/base/ui/label.tsx` | `style-*.css` | |
|
||||
| native-select | pending | `src/css/components/native-select.css` | `apps/v4/registry/bases/base/ui/native-select.tsx` | `style-*.css` | |
|
||||
| popover | pending | `src/css/components/popover.css` | `apps/v4/registry/bases/base/ui/popover.tsx` | `style-*.css` | |
|
||||
| radio | pending | `src/css/components/radio.css` | `apps/v4/registry/bases/base/ui/radio-group.tsx` | `style-*.css` | |
|
||||
| range | pending | `src/css/components/range.css` | `apps/v4/registry/bases/base/ui/slider.tsx` | `style-*.css` | Consider moving slider fill sync into shared JS. |
|
||||
| select | pending | `src/css/components/select.css` | `apps/v4/registry/bases/base/ui/select.tsx` | `style-*.css` | |
|
||||
| sidebar | pending | `src/css/components/sidebar.css` | `apps/v4/registry/bases/base/ui/sidebar.tsx` | `style-*.css` | |
|
||||
| switch | pending | `src/css/components/switch.css` | `apps/v4/registry/bases/base/ui/switch.tsx` | `style-*.css` | |
|
||||
| table | pending | `src/css/components/table.css` | `apps/v4/registry/bases/base/ui/table.tsx` | `style-*.css` | |
|
||||
| tabs | pending | `src/css/components/tabs.css` | `apps/v4/registry/bases/base/ui/tabs.tsx` | `style-*.css` | |
|
||||
| textarea | pending | `src/css/components/textarea.css` | `apps/v4/registry/bases/base/ui/textarea.tsx` | `style-*.css` | |
|
||||
| toast | pending | `src/css/components/toast.css` | `apps/v4/registry/bases/base/ui/sonner.tsx` | `style-*.css` | Needs review against current Basecoat toast structure. |
|
||||
| tooltip | pending | `src/css/components/tooltip.css` | `apps/v4/registry/bases/base/ui/tooltip.tsx` | `style-*.css` | |
|
||||
| alert | done | `src/css/components/alert.css` | `apps/v4/registry/bases/base/ui/alert.tsx` | all 8 `style-*.css` | Shared CSS now keeps alert structure only. All eight style files map upstream alert, title, description, action, and destructive/default variants onto Basecoat selectors. Docs include action, custom colors, and RTL. |
|
||||
| badge | done | `src/css/components/badge.css` | `apps/v4/registry/bases/base/ui/badge.tsx` | all 8 `style-*.css` | Shared CSS now keeps badge structure only. All eight style files map upstream default, secondary, destructive, outline, ghost, and link variants. Docs include custom colors and RTL directional icon guidance. |
|
||||
| button | done | `src/css/components/button.css` | `apps/v4/registry/bases/base/ui/button.tsx` | all 8 `style-*.css` | Standalone button rules are mapped across all eight styles, including xs sizing and icon-aware padding. Icon-aware padding now uses logical `ps`/`pe` utilities for RTL. Drift fixed: Maia base radius now uses upstream `rounded-4xl`. Docs include RTL guidance; directional icons must opt into `rtl:rotate-180`. |
|
||||
| button-group | done | `src/css/components/button-group.css` | `apps/v4/registry/bases/base/ui/button-group.tsx` | all 8 `style-*.css` | Shared orientation/focus/adjacency structure remains in component CSS. Horizontal adjacency uses logical start/end border and radius rules for RTL. Style files cover grouped edge radius, nested group gap, text segment styling, separators, and Luma/Sera/Rhea outline input/select border coupling mapped to Basecoat selectors. Docs aligned with upstream sections where applicable and include RTL guidance. |
|
||||
| card | done | `src/css/components/card.css` | `apps/v4/registry/bases/base/ui/card.tsx` | all 8 `style-*.css` | Shared CSS now keeps semantic card structure. All eight style files map upstream card root, header, title, description, content, footer, image, and `data-size="sm"` styling onto Basecoat selectors. Intentional divergence: card overflow remains visible because Basecoat overlays are inline rather than portalled like shadcn/Radix. Docs include size and RTL. |
|
||||
| checkbox | done | `src/css/components/checkbox.css` | `apps/v4/registry/bases/base/ui/checkbox.tsx` | all 8 `style-*.css` | Shared CSS now keeps native checkbox structure and masked checkmark only. All eight style files map upstream checkbox root and indicator visuals onto Basecoat native checkbox selectors using `:checked` instead of shadcn `data-checked`. Docs aligned with upstream sections: checked state, invalid, basic, description, disabled, group, table, and RTL. |
|
||||
| collapsible | deferred | `src/css/components/collapsible.css` | `apps/v4/registry/bases/base/ui/collapsible.tsx` | n/a | Basecoat already ships native `details`/`summary` enhancement CSS. Do not port shadcn/ui Collapsible as a JS component unless a real controlled-state need appears. TODO: add a `Disclosure` docs page for the native pattern with default, open, icon, accordion/group, and RTL examples. |
|
||||
| combobox | done | `src/css/components/combobox.css` | `apps/v4/registry/bases/base/ui/combobox.tsx` | all 8 `style-*.css` | Added dedicated input-first Combobox CSS, JS, Nunjucks macro, and Jinja macro. All eight style files map upstream combobox content, list, empty state, labels, items, active state, separators, chips, chip remove, and trigger/input visuals onto Basecoat selectors. Intentional divergence: Basecoat uses raw semantic HTML and vanilla JS instead of Base UI Combobox primitives; single selection stores text in the visible input while options can render custom HTML via `data-label`; multiple selection uses chips plus a real input and stores a JSON array in the hidden input. Docs include basic, multiple, groups, custom items, invalid, disabled, auto-highlight, top side, and RTL. |
|
||||
| command | done | `src/css/components/command.css` | `apps/v4/registry/bases/base/ui/command.tsx` | all 8 `style-*.css` | Shared CSS now keeps command/dialog structure, filtering visibility, empty state, shortcut/indicator slots, and active item behavior only. All eight style files map upstream command root, dialog, input wrapper, input, list, empty state, group, heading, separator, item, active state, and shortcut visuals onto Basecoat selectors. Intentional divergence: Basecoat implements command behavior with vanilla JS and native `<dialog>` rather than `cmdk` and portalled shadcn/ui Dialog. Docs aligned with upstream sections: about, composition, basic, shortcuts, groups, scrollable, RTL, and API guidance. |
|
||||
| dialog | done | `src/css/components/dialog.css` | `apps/v4/registry/bases/base/ui/dialog.tsx` | all 8 `style-*.css` | Shared CSS now keeps native dialog mechanics, backdrop/open transitions, centered content layout, structural header/body/footer, and close-button structure only. All eight style files map upstream overlay, content, close position, header, footer, title, and description visuals onto Basecoat `.dialog` selectors. Intentional divergence: Basecoat uses the native `<dialog>` element and `showModal()` instead of shadcn/ui's portalled Base UI Dialog. Docs aligned with upstream sections: composition, custom close button, no close button, sticky footer, scrollable content, RTL, and API guidance. |
|
||||
| dropdown-menu | done | `src/css/components/dropdown-menu.css` | `apps/v4/registry/bases/base/ui/dropdown-menu.tsx` | all 8 `style-*.css` | Shared CSS now keeps structure, roles, disabled states, shortcut/indicator slots, and fallback hover only. All eight style files map upstream content, item, checkbox/radio item, indicator, label, separator, shortcut, destructive, and inset visuals onto Basecoat role/data selectors. Intentional divergence: Basecoat dropdown content remains inline-positioned and does not implement upstream submenus/avatar/complex nested examples yet. Docs aligned with upstream sections for basic, shortcuts, icons, checkboxes, radio, destructive, RTL, and API guidance. |
|
||||
| empty | done | `src/css/components/empty.css` | `apps/v4/registry/bases/base/ui/empty.tsx` | all 8 `style-*.css` | Added lightweight `.empty` root API using semantic `header`, `figure`, `section`, and `footer` children instead of shadcn slot classes. All eight style files map upstream empty root, header, media/icon, title, description, and content visuals onto Basecoat selectors. Docs now treat Empty as a real Basecoat component and include outline, avatar, avatar group, input group, and RTL examples. |
|
||||
| field | done | `src/css/components/field.css` | `apps/v4/registry/bases/base/ui/field.tsx` | all 8 `style-*.css` | Shared CSS now covers field, fieldset, semantic field groups, field-description, field-error, and field-separator structure. All eight style files map upstream field visuals onto existing Basecoat selectors, including `cn-field-label` checked/card behavior via `.field > label` and `label:has(> .field)`. Docs include semantic field groups, separator, choice card, responsive layout, and RTL. |
|
||||
| input | done | `src/css/components/input.css` | `apps/v4/registry/bases/base/ui/input.tsx` | all 8 `style-*.css` | Shared CSS now keeps structural input behavior only. All eight style files map upstream input visuals onto Basecoat `.input` and `.field` selectors. Docs aligned with upstream sections where applicable: basic, field, field group, disabled, invalid, file, inline, grid, required, badge, input group, button group, and form. RTL is supported through native direction handling and logical utility usage in examples. |
|
||||
| input-group | done | `src/css/components/input-group.css` | `apps/v4/styles/base-*/ui/input-group.tsx` | all 8 `style-*.css` | Added lightweight `.input-group` root API. Children remain semantic native controls and elements; optional `data-align="inline-start|inline-end|block-start|block-end"` maps upstream addon placement without shadcn slot classes. `data-control` is the escape hatch for custom non-native controls. |
|
||||
| item | done | `src/css/components/item.css` | `apps/v4/registry/bases/base/ui/item.tsx` | all 8 `style-*.css` | Added lightweight `.item` and `.item-group` APIs using semantic `figure`, `section`, `aside`, `header`, and `footer` children instead of shadcn slot classes. All eight style files map upstream item variants, sizes, media, content, title, description, actions, header/footer, group, and separator visuals onto Basecoat selectors. Docs now use the component API and include variants, sizes, icon, avatar, image, group, header, link, and RTL examples. |
|
||||
| kbd | done | `src/css/components/kbd.css` | `apps/v4/registry/bases/base/ui/kbd.tsx` | all 8 `style-*.css` | Added `kbd-group` and mapped upstream kbd/kbd-group styles across all eight style files. Docs include button, input-group, and RTL examples. |
|
||||
| label | done | `src/css/components/label.css` | `apps/v4/registry/bases/base/ui/label.tsx` | all 8 `style-*.css` | Shared CSS now keeps label structure and disabled behavior. All eight style files map upstream label visuals. Sera control-label exceptions are mapped with Basecoat native input selectors instead of shadcn peer data-slot selectors. Docs include RTL. |
|
||||
| native-select | done | `src/css/components/native-select.css` | `apps/v4/registry/bases/base/ui/native-select.tsx` | all 8 `style-*.css` | Shared CSS now keeps native select structure only. All eight style files map upstream native select visuals onto Basecoat `select.select` and `.field select`, using a CSS background chevron instead of upstream's wrapper icon element. Intentional divergence: direct selects stay `w-fit` by default because Basecoat has no `w-fit` wrapper; width utilities remain supported. Docs split Native Select from custom Select and include groups, disabled, invalid, size, RTL, and API guidance. |
|
||||
| popover | done | `src/css/components/popover.css` | `apps/v4/registry/bases/base/ui/popover.tsx` | all 8 `style-*.css` | Shared CSS now keeps positioning, visibility, side, and align behavior only. All eight style files map upstream popover content/header/title/description visuals onto Basecoat `[data-popover]` and semantic header selectors. Intentional divergence: Basecoat popover remains inline-positioned relative to `.popover` rather than portalled through Base UI; animation utilities from upstream are omitted because this Tailwind setup does not include shadcn animation utilities. Docs aligned with upstream sections: composition, basic, align, form, RTL, and API guidance. |
|
||||
| radio | done | `src/css/components/radio.css` | `apps/v4/registry/bases/base/ui/radio-group.tsx` | all 8 `style-*.css` | Shared CSS now keeps native radio structure and checked dot only. All eight style files map upstream radio group/item/indicator visuals onto Basecoat native radio selectors using `:checked` instead of shadcn `data-checked`. Docs aligned with upstream sections: checked state, invalid, basic, description, choice card, fieldset, disabled, invalid, and RTL. |
|
||||
| range | done | `src/css/components/range.css` + `src/js/range.js` | `apps/v4/registry/bases/base/ui/slider.tsx` | all 8 `style-*.css` | Shared CSS keeps native range structure and gradient fill. Added `range.js` to sync `--slider-value`. All eight style files map upstream slider track/thumb visuals onto native range pseudo-elements. Intentional divergence: Basecoat uses native single-thumb range inputs, so upstream multi-thumb and vertical slider examples are not supported. Docs updated with default, controlled label, disabled, RTL, and divergence note. |
|
||||
| select | done | `src/css/components/select.css` | `apps/v4/registry/bases/base/ui/select.tsx` | all 8 `style-*.css` | Shared CSS now keeps custom select structure, listbox behavior selectors, selected checkmark background, and empty state only. All eight style files map upstream trigger, value, content, label, group, item, active state, and separator visuals onto Basecoat selectors. Intentional divergences: Basecoat keeps inline popover positioning below the trigger rather than shadcn/ui's selected-item-over-trigger alignment and retains multiselect as Basecoat API surface. Combobox-specific search-header support has been removed from Select and moved to the dedicated Combobox component. Docs aligned with upstream sections where applicable: align, groups, scrollable, disabled, invalid, RTL, plus Basecoat multiselect. |
|
||||
| sidebar | done | `src/css/components/sidebar.css` | `apps/v4/registry/bases/base/ui/sidebar.tsx` | `style-*.css` | Mapped upstream sidebar visuals onto Basecoat's simpler semantic sidebar. Intentional differences: no React provider, rail, inset/floating variants, icon-only collapse, menu actions, badges, or sidebar skeleton API. |
|
||||
| skeleton | done | `src/css/components/skeleton.css` | `apps/v4/registry/bases/base/ui/skeleton.tsx` | all 8 `style-*.css` | Added dedicated `skeleton` class. All eight style files map upstream skeleton visuals. Docs now use `skeleton` instead of raw utility-only examples and include avatar, card, text, form, table, and RTL examples. |
|
||||
| switch | done | `src/css/components/switch.css` | `apps/v4/registry/bases/base/ui/switch.tsx` | all 8 `style-*.css` | Shared CSS now keeps native checkbox-as-switch structure and pseudo-thumb only. All eight style files map upstream switch track/thumb visuals onto Basecoat native switch selectors using `:checked`, `data-size`, and explicit `:dir(rtl)` thumb transforms. Docs aligned with upstream sections: checked state, invalid, basic, description, choice card, disabled, invalid, size, and RTL. |
|
||||
| table | done | `src/css/components/table.css` | `apps/v4/registry/bases/base/ui/table.tsx` | all 8 `style-*.css` | Added `table-container` and mapped upstream table root, header, body, footer, row, cell, head, and caption styles across all eight style files using logical alignment. Docs include footer, actions, and RTL. |
|
||||
| tabs | done | `src/css/components/tabs.css` | `apps/v4/registry/bases/base/ui/tabs.tsx` | all 8 `style-*.css` | Shared CSS now keeps tabs layout, role structure, and panel structure only. All eight style files map upstream default and line variants, horizontal/vertical orientation, active state, icon sizing, and content text styles onto Basecoat role selectors. JS now skips disabled tabs and supports vertical ArrowUp/ArrowDown navigation. Docs aligned with upstream sections: composition, line, vertical, disabled, icons, RTL, and API guidance. |
|
||||
| textarea | done | `src/css/components/textarea.css` | `apps/v4/registry/bases/base/ui/textarea.tsx` | all 8 `style-*.css` | Shared CSS now keeps structural textarea behavior only. All eight style files map upstream textarea visuals onto Basecoat `.textarea` and `.field` selectors. Docs aligned with upstream sections where applicable: field, disabled, invalid, button, and form. RTL is supported through native direction handling. |
|
||||
| toast | done | `src/css/components/toast.css` | `apps/v4/registry/bases/base/ui/sonner.tsx` | all 8 `style-*.css` | Shared CSS keeps toaster positioning, toast lifecycle layout, icons/content/footer structure, and hide animation. All eight style files map upstream Sonner toast radius onto Basecoat `.toast-content`. Intentional divergence: Basecoat keeps its vanilla JS toast API and does not depend on Sonner. |
|
||||
| tooltip | done | `src/css/components/tooltip.css` | `apps/v4/registry/bases/base/ui/tooltip.tsx` | all 8 `style-*.css` | Shared CSS keeps CSS-only tooltip trigger, positioning, visibility, side, align, and logical inline side behavior. All eight style files map upstream tooltip content radius/spacing/typography and optional arrow radius onto Basecoat `data-tooltip` selectors. Intentional divergence: Basecoat tooltips are text-only CSS tooltips and are not portalled composed content like upstream. Docs updated with sides, disabled trigger wrapper, link, icon button, RTL, and divergence note. |
|
||||
|
||||
Generated
+1412
-14
File diff suppressed because it is too large
Load Diff
+8
-4
@@ -34,11 +34,14 @@
|
||||
"homepage": "https://github.com/hunvreus/basecoat#readme",
|
||||
"scripts": {
|
||||
"build": "node ./scripts/build.js",
|
||||
"docs:build:css": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --minify && tailwindcss -i ./docs/css/style-nova.css -o ./docs/src/assets/style-nova.css --minify && tailwindcss -i ./docs/css/style-maia.css -o ./docs/src/assets/style-maia.css --minify && tailwindcss -i ./docs/css/style-lyra.css -o ./docs/src/assets/style-lyra.css --minify && tailwindcss -i ./docs/css/style-mira.css -o ./docs/src/assets/style-mira.css --minify && tailwindcss -i ./docs/css/style-luma.css -o ./docs/src/assets/style-luma.css --minify && tailwindcss -i ./docs/css/style-sera.css -o ./docs/src/assets/style-sera.css --minify && tailwindcss -i ./docs/css/style-rhea.css -o ./docs/src/assets/style-rhea.css --minify",
|
||||
"docs:build:css": "node ./scripts/build-docs-css.js",
|
||||
"docs:build": "npm run docs:build:css && eleventy",
|
||||
"docs:dev:css": "concurrently \"tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --watch\" \"tailwindcss -i ./docs/css/style-nova.css -o ./docs/src/assets/style-nova.css --watch\" \"tailwindcss -i ./docs/css/style-maia.css -o ./docs/src/assets/style-maia.css --watch\" \"tailwindcss -i ./docs/css/style-lyra.css -o ./docs/src/assets/style-lyra.css --watch\" \"tailwindcss -i ./docs/css/style-mira.css -o ./docs/src/assets/style-mira.css --watch\" \"tailwindcss -i ./docs/css/style-luma.css -o ./docs/src/assets/style-luma.css --watch\" \"tailwindcss -i ./docs/css/style-sera.css -o ./docs/src/assets/style-sera.css --watch\" \"tailwindcss -i ./docs/css/style-rhea.css -o ./docs/src/assets/style-rhea.css --watch\"",
|
||||
"docs:dev:css": "node ./scripts/build-docs-css.js --watch",
|
||||
"docs:dev:eleventy": "ELEVENTY_ENV=prod eleventy --serve",
|
||||
"docs:dev": "concurrently \"npm:docs:dev:css\" \"npm:docs:dev:eleventy\""
|
||||
"docs:dev": "concurrently \"npm:docs:dev:css\" \"npm:docs:dev:eleventy\"",
|
||||
"workers:dev": "npm run docs:build && wrangler dev",
|
||||
"workers:deploy": "npm run docs:build && wrangler deploy",
|
||||
"workers:preview": "npm run docs:build && wrangler versions upload"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
@@ -47,6 +50,7 @@
|
||||
"concurrently": "^9.1.2",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "^4.1.3",
|
||||
"terser": "^5.42.0"
|
||||
"terser": "^5.42.0",
|
||||
"wrangler": "^4.50.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,13 @@ Add it just after Tailwind in your stylesheet:
|
||||
|
||||
That's it, you can use any Basecoat class (`btn`, `card`, `input`, etc) in your markup.
|
||||
|
||||
To use a specific style, import its standalone bundle instead:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/sera";
|
||||
```
|
||||
|
||||
### (Optional) JavaScript files
|
||||
|
||||
Some interactive components (Dropdown Menu, Popover, Select, Sidebar, Tabs, Toast) need some JavaScript.
|
||||
@@ -60,4 +67,4 @@ For more detailed information on components, their usage, and customization opti
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/hunvreus/basecoat/blob/main/LICENSE.md)
|
||||
[MIT](https://github.com/hunvreus/basecoat/blob/main/LICENSE.md)
|
||||
|
||||
@@ -59,6 +59,22 @@
|
||||
"./toast": "./dist/js/toast.js",
|
||||
"./toast.min": "./dist/js/toast.min.js",
|
||||
"./all.css": "./dist/basecoat.all.css",
|
||||
"./vega": "./dist/basecoat-vega.css",
|
||||
"./vega.css": "./dist/basecoat-vega.css",
|
||||
"./nova": "./dist/basecoat-nova.css",
|
||||
"./nova.css": "./dist/basecoat-nova.css",
|
||||
"./maia": "./dist/basecoat-maia.css",
|
||||
"./maia.css": "./dist/basecoat-maia.css",
|
||||
"./lyra": "./dist/basecoat-lyra.css",
|
||||
"./lyra.css": "./dist/basecoat-lyra.css",
|
||||
"./mira": "./dist/basecoat-mira.css",
|
||||
"./mira.css": "./dist/basecoat-mira.css",
|
||||
"./luma": "./dist/basecoat-luma.css",
|
||||
"./luma.css": "./dist/basecoat-luma.css",
|
||||
"./sera": "./dist/basecoat-sera.css",
|
||||
"./sera.css": "./dist/basecoat-sera.css",
|
||||
"./rhea": "./dist/basecoat-rhea.css",
|
||||
"./rhea.css": "./dist/basecoat-rhea.css",
|
||||
"./styles/vega": "./dist/styles/style-vega.css",
|
||||
"./styles/nova": "./dist/styles/style-nova.css",
|
||||
"./styles/maia": "./dist/styles/style-maia.css",
|
||||
|
||||
@@ -25,11 +25,15 @@
|
||||
- one file per style pack: `vega`, `nova`, `maia`, `lyra`, `mira`, `luma`, `sera`, `rhea`
|
||||
- component visuals: size, spacing, radius, typography, shadows, focus rings, variant colors when style-owned
|
||||
4. Aggregates
|
||||
- `src/css/basecoat.css`: backward-compatible default aggregate, includes Vega
|
||||
- `src/css/basecoat.css`: backward-compatible Basecoat default aggregate, currently Vega
|
||||
- `src/css/basecoat-<style>.css`: standalone Tailwind source bundle per style
|
||||
- `src/css/basecoat-<style>.cdn.css`: standalone CDN source bundle per style
|
||||
- `src/css/basecoat.all.css`: explicit all-in-one Tailwind source
|
||||
- `src/css/basecoat.cdn.css`: CDN build entry
|
||||
|
||||
No `.style-*` wrapper is part of the Basecoat public contract for style selection. Users choose a style by importing a style file or using a style-specific generated asset. Docs may switch by swapping stylesheet URLs.
|
||||
No `.style-*` wrapper is part of the Basecoat public contract for style selection. Users choose a style by importing a standalone style bundle or using a style-specific generated CDN asset. Docs switch by swapping the single active full stylesheet URL.
|
||||
|
||||
Do not load Vega/default first and then layer another style pack on top. Split files under `src/css/styles/style-*.css` are source organization and advanced composition only. They should map upstream style intent directly and avoid defensive resets whose only purpose is undoing another style pack.
|
||||
|
||||
## Per-Component File Checklist
|
||||
|
||||
@@ -39,6 +43,7 @@ For every component, check these files before editing:
|
||||
- Basecoat templates/macros if relevant: `src/nunjucks/*.njk`, `src/jinja/*.html.jinja`
|
||||
- Basecoat docs examples: `docs/src/components/<component>.njk`
|
||||
- Upstream base component: `../_sandbox/shadcn-ui/apps/v4/registry/bases/base/ui/<component>.tsx`
|
||||
- Upstream docs page: `../_sandbox/shadcn-ui/apps/v4/content/docs/components/{base,radix}/<component>.mdx`
|
||||
- Upstream style packs: `../_sandbox/shadcn-ui/apps/v4/registry/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
- Basecoat style packs: `src/css/styles/style-{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
|
||||
@@ -49,21 +54,33 @@ For every component, check these files before editing:
|
||||
3. Decide what is shared structure versus style-pack-owned visual rules.
|
||||
4. Remove outdated visual rules from shared component CSS when they are now style-owned.
|
||||
5. Map upstream style intent onto Basecoat selectors. Do not port `cn-*` selectors.
|
||||
6. Update the component section in all eight Basecoat style files.
|
||||
7. Document non-trivial deltas as `intentional`, `drift fixed`, or `deferred`.
|
||||
8. Update `migration-tracker.md`.
|
||||
9. Run `npm run build`.
|
||||
10. Run `npm run docs:build` when docs or docs style assets change.
|
||||
11. Manually verify the docs page in light/dark and all eight styles.
|
||||
6. Audit RTL support using upstream `*-rtl` examples and generated `ui-rtl` code when present.
|
||||
7. Prefer logical utilities/properties (`ps`, `pe`, `rounded-s`, `rounded-e`, `border-s`, `start`, `end`) over duplicated `:dir(rtl)` branches.
|
||||
8. Audit each style as a standalone full bundle, not as an override layered on another style.
|
||||
9. Check state selectors during that audit: `focus-visible`, `aria-invalid`, `hover`, disabled, open, selected, checked, and pressed states.
|
||||
10. Update the component section in all eight Basecoat style files.
|
||||
11. Document non-trivial deltas as `intentional`, `drift fixed`, or `deferred`.
|
||||
12. Align the component docs with the current upstream docs page:
|
||||
- equivalent examples
|
||||
- matching labels/anchors where reasonable
|
||||
- Basecoat-specific differences and extras
|
||||
- class/API reference
|
||||
- explicit notes for unsupported/deferred upstream features
|
||||
- RTL section when supported, or explicit RTL deferral
|
||||
13. Update `migration-tracker.md`.
|
||||
14. Run `npm run build`.
|
||||
15. Run `npm run docs:build` when docs or docs style assets change.
|
||||
16. Manually verify the docs page in light/dark, RTL, and all eight styles.
|
||||
|
||||
Do not mark a component done because CSS compiles. It needs a visual/state check.
|
||||
|
||||
## Packaging Contract
|
||||
|
||||
- Keep `@import "basecoat-css";` as the default Vega-compatible import.
|
||||
- Keep existing CDN files and JS bundle paths.
|
||||
- Export split style files from `packages/css` as `basecoat-css/styles/<style>`.
|
||||
- Keep the default CDN output equivalent to Vega unless a separate style-specific CDN asset is explicitly added.
|
||||
- Keep `@import "basecoat-css";` as the Basecoat backward-compatible default import. It currently resolves to Vega; this is not the upstream shadcn/ui generated default.
|
||||
- Keep existing default CDN and JS bundle paths.
|
||||
- Export standalone style bundles from `packages/css` as `basecoat-css/<style>` and `basecoat-css/<style>.css`.
|
||||
- Export split style files from `packages/css` as `basecoat-css/styles/<style>` for advanced composition.
|
||||
- Generate style-specific CDN assets for every style.
|
||||
- Custom themes should load after Basecoat styles so their `:root` and `.dark` tokens override defaults.
|
||||
|
||||
## Component Migration Order
|
||||
@@ -92,17 +109,22 @@ Do not mark a component done because CSS compiles. It needs a visual/state check
|
||||
22. `sidebar`
|
||||
23. `toast`
|
||||
24. `tooltip`
|
||||
25. `collapsible`
|
||||
25. `collapsible` (deferred: native `details`/`summary`; add `Disclosure` docs page)
|
||||
26. `kbd`
|
||||
|
||||
## Current Status
|
||||
|
||||
- CSS split exists.
|
||||
- Docs style switcher exists.
|
||||
- Button is started and style sections exist for all eight styles.
|
||||
- Button still needs a strict browser audit across all eight styles and light/dark.
|
||||
- Button and Button Group are migrated for current scope and tracked in `migration-tracker.md`.
|
||||
- No other component should be considered migrated.
|
||||
|
||||
## TODO
|
||||
|
||||
- Add a `Disclosure` docs page for Basecoat's native `details`/`summary` pattern.
|
||||
- Include examples for default, open by default, icon/chevron, accordion/group behavior, and RTL.
|
||||
- Keep shadcn/ui `Collapsible` deferred unless Basecoat needs controlled JS state or animation hooks beyond native disclosure.
|
||||
|
||||
## Risks
|
||||
|
||||
- Selector drift between Basecoat markup and upstream shadcn/ui assumptions.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { spawn } from 'child_process';
|
||||
import { execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execFilePromise = promisify(execFile);
|
||||
const styles = ['vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea'];
|
||||
const watch = process.argv.includes('--watch');
|
||||
|
||||
function tailwindArgs(input, output) {
|
||||
const args = ['tailwindcss', '-i', input, '-o', output];
|
||||
if (watch) args.push('--watch');
|
||||
else args.push('--minify');
|
||||
return args;
|
||||
}
|
||||
|
||||
if (watch) {
|
||||
const children = styles.map((style) => {
|
||||
const child = spawn('npx', tailwindArgs(`./docs/css/styles-${style}.css`, `./docs/src/assets/styles-${style}.css`), {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
child.on('exit', (code) => {
|
||||
if (code) process.exitCode = code;
|
||||
});
|
||||
return child;
|
||||
});
|
||||
|
||||
const shutdown = () => {
|
||||
for (const child of children) child.kill('SIGTERM');
|
||||
};
|
||||
process.on('SIGINT', shutdown);
|
||||
process.on('SIGTERM', shutdown);
|
||||
} else {
|
||||
for (const style of styles) {
|
||||
await execFilePromise('npx', tailwindArgs(`./docs/css/styles-${style}.css`, `./docs/src/assets/styles-${style}.css`));
|
||||
}
|
||||
await execFilePromise('npx', tailwindArgs('./docs/css/styles-vega.css', './docs/src/assets/styles.css'));
|
||||
}
|
||||
+20
-11
@@ -122,7 +122,7 @@ async function build() {
|
||||
|
||||
// Create combined component files
|
||||
console.log('Creating combined component files...');
|
||||
const componentsToCombine = ['basecoat.js', 'command.js', 'dropdown-menu.js', 'popover.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
|
||||
const componentsToCombine = ['basecoat.js', 'command.js', 'combobox.js', 'dropdown-menu.js', 'popover.js', 'range.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
|
||||
const componentPaths = componentsToCombine.map(f => path.join(srcJsDir, f));
|
||||
|
||||
// Create non-minified bundle
|
||||
@@ -149,9 +149,14 @@ async function build() {
|
||||
// Build CSS package
|
||||
console.log('Building CSS package...');
|
||||
await ensureDir(cssDistDir); // Ensure dist dir exists for css package
|
||||
const styles = ['vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea'];
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat.css'), path.join(cssDistDir, 'basecoat.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat.all.css'), path.join(cssDistDir, 'basecoat.all.css'));
|
||||
console.log(`Copied basecoat.css to ${cssDistDir}`);
|
||||
for (const style of styles) {
|
||||
await fs.copyFile(path.join(srcCssDir, `basecoat-${style}.css`), path.join(cssDistDir, `basecoat-${style}.css`));
|
||||
await fs.copyFile(path.join(srcCssDir, `basecoat-${style}.cdn.css`), path.join(cssDistDir, `basecoat-${style}.cdn.css`));
|
||||
}
|
||||
console.log(`Copied basecoat CSS entrypoints to ${cssDistDir}`);
|
||||
|
||||
// Copy split CSS folders used by basecoat.css imports.
|
||||
const cssBaseSrcDir = path.join(srcCssDir, 'base');
|
||||
@@ -164,15 +169,19 @@ async function build() {
|
||||
await copyDirRecursive(srcCssStylesDir, cssStylesDistDir);
|
||||
console.log(`Copied split CSS folders to ${cssDistDir}`);
|
||||
|
||||
// Create Tailwind CSS builds for the CSS package
|
||||
const cdnCssSrc = path.join(srcCssDir, 'basecoat.cdn.css');
|
||||
const cssDistCdnPath = path.join(cssDistDir, 'basecoat.cdn.css');
|
||||
const cssDistCdnMinPath = path.join(cssDistDir, 'basecoat.cdn.min.css');
|
||||
|
||||
await execPromise(`npx tailwindcss -i "${cdnCssSrc}" -o "${cssDistCdnPath}"`);
|
||||
console.log(`Generated non-minified CSS: ${cssDistCdnPath}`);
|
||||
await execPromise(`npx tailwindcss -i "${cdnCssSrc}" -o "${cssDistCdnMinPath}" --minify`);
|
||||
console.log(`Generated minified CSS: ${cssDistCdnMinPath}`);
|
||||
// Create Tailwind CSS builds for the CSS package.
|
||||
const cdnEntries = ['basecoat.cdn.css', ...styles.map((style) => `basecoat-${style}.cdn.css`)];
|
||||
for (const entry of cdnEntries) {
|
||||
const cdnCssSrc = path.join(srcCssDir, entry);
|
||||
const baseName = path.basename(entry, '.css');
|
||||
const cssDistCdnPath = path.join(cssDistDir, `${baseName}.css`);
|
||||
const cssDistCdnMinPath = path.join(cssDistDir, `${baseName}.min.css`);
|
||||
|
||||
await execPromise(`npx tailwindcss -i "${cdnCssSrc}" -o "${cssDistCdnPath}"`);
|
||||
console.log(`Generated non-minified CSS: ${cssDistCdnPath}`);
|
||||
await execPromise(`npx tailwindcss -i "${cdnCssSrc}" -o "${cssDistCdnMinPath}" --minify`);
|
||||
console.log(`Generated minified CSS: ${cssDistCdnMinPath}`);
|
||||
}
|
||||
|
||||
console.log('Build process finished successfully!');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "./basecoat-luma.css";
|
||||
@@ -0,0 +1,34 @@
|
||||
@import "./base/base.css";
|
||||
|
||||
/* Components */
|
||||
@import "./components/alert.css";
|
||||
@import "./components/badge.css";
|
||||
@import "./components/button.css";
|
||||
@import "./components/button-group.css";
|
||||
@import "./components/card.css";
|
||||
@import "./components/collapsible.css";
|
||||
@import "./components/command.css";
|
||||
@import "./components/combobox.css";
|
||||
@import "./components/dialog.css";
|
||||
@import "./components/dropdown-menu.css";
|
||||
@import "./components/empty.css";
|
||||
@import "./components/field.css";
|
||||
@import "./components/checkbox.css";
|
||||
@import "./components/input.css";
|
||||
@import "./components/input-group.css";
|
||||
@import "./components/item.css";
|
||||
@import "./components/kbd.css";
|
||||
@import "./components/label.css";
|
||||
@import "./components/native-select.css";
|
||||
@import "./components/popover.css";
|
||||
@import "./components/radio.css";
|
||||
@import "./components/range.css";
|
||||
@import "./components/select.css";
|
||||
@import "./components/sidebar.css";
|
||||
@import "./components/switch.css";
|
||||
@import "./components/table.css";
|
||||
@import "./components/tabs.css";
|
||||
@import "./components/textarea.css";
|
||||
@import "./components/toast.css";
|
||||
@import "./components/tooltip.css";
|
||||
@import "./styles/style-luma.css";
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "./basecoat-lyra.css";
|
||||
@@ -0,0 +1,34 @@
|
||||
@import "./base/base.css";
|
||||
|
||||
/* Components */
|
||||
@import "./components/alert.css";
|
||||
@import "./components/badge.css";
|
||||
@import "./components/button.css";
|
||||
@import "./components/button-group.css";
|
||||
@import "./components/card.css";
|
||||
@import "./components/collapsible.css";
|
||||
@import "./components/command.css";
|
||||
@import "./components/combobox.css";
|
||||
@import "./components/dialog.css";
|
||||
@import "./components/dropdown-menu.css";
|
||||
@import "./components/empty.css";
|
||||
@import "./components/field.css";
|
||||
@import "./components/checkbox.css";
|
||||
@import "./components/input.css";
|
||||
@import "./components/input-group.css";
|
||||
@import "./components/item.css";
|
||||
@import "./components/kbd.css";
|
||||
@import "./components/label.css";
|
||||
@import "./components/native-select.css";
|
||||
@import "./components/popover.css";
|
||||
@import "./components/radio.css";
|
||||
@import "./components/range.css";
|
||||
@import "./components/select.css";
|
||||
@import "./components/sidebar.css";
|
||||
@import "./components/switch.css";
|
||||
@import "./components/table.css";
|
||||
@import "./components/tabs.css";
|
||||
@import "./components/textarea.css";
|
||||
@import "./components/toast.css";
|
||||
@import "./components/tooltip.css";
|
||||
@import "./styles/style-lyra.css";
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "./basecoat-maia.css";
|
||||
@@ -0,0 +1,34 @@
|
||||
@import "./base/base.css";
|
||||
|
||||
/* Components */
|
||||
@import "./components/alert.css";
|
||||
@import "./components/badge.css";
|
||||
@import "./components/button.css";
|
||||
@import "./components/button-group.css";
|
||||
@import "./components/card.css";
|
||||
@import "./components/collapsible.css";
|
||||
@import "./components/command.css";
|
||||
@import "./components/combobox.css";
|
||||
@import "./components/dialog.css";
|
||||
@import "./components/dropdown-menu.css";
|
||||
@import "./components/empty.css";
|
||||
@import "./components/field.css";
|
||||
@import "./components/checkbox.css";
|
||||
@import "./components/input.css";
|
||||
@import "./components/input-group.css";
|
||||
@import "./components/item.css";
|
||||
@import "./components/kbd.css";
|
||||
@import "./components/label.css";
|
||||
@import "./components/native-select.css";
|
||||
@import "./components/popover.css";
|
||||
@import "./components/radio.css";
|
||||
@import "./components/range.css";
|
||||
@import "./components/select.css";
|
||||
@import "./components/sidebar.css";
|
||||
@import "./components/switch.css";
|
||||
@import "./components/table.css";
|
||||
@import "./components/tabs.css";
|
||||
@import "./components/textarea.css";
|
||||
@import "./components/toast.css";
|
||||
@import "./components/tooltip.css";
|
||||
@import "./styles/style-maia.css";
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "./basecoat-mira.css";
|
||||
@@ -0,0 +1,34 @@
|
||||
@import "./base/base.css";
|
||||
|
||||
/* Components */
|
||||
@import "./components/alert.css";
|
||||
@import "./components/badge.css";
|
||||
@import "./components/button.css";
|
||||
@import "./components/button-group.css";
|
||||
@import "./components/card.css";
|
||||
@import "./components/collapsible.css";
|
||||
@import "./components/command.css";
|
||||
@import "./components/combobox.css";
|
||||
@import "./components/dialog.css";
|
||||
@import "./components/dropdown-menu.css";
|
||||
@import "./components/empty.css";
|
||||
@import "./components/field.css";
|
||||
@import "./components/checkbox.css";
|
||||
@import "./components/input.css";
|
||||
@import "./components/input-group.css";
|
||||
@import "./components/item.css";
|
||||
@import "./components/kbd.css";
|
||||
@import "./components/label.css";
|
||||
@import "./components/native-select.css";
|
||||
@import "./components/popover.css";
|
||||
@import "./components/radio.css";
|
||||
@import "./components/range.css";
|
||||
@import "./components/select.css";
|
||||
@import "./components/sidebar.css";
|
||||
@import "./components/switch.css";
|
||||
@import "./components/table.css";
|
||||
@import "./components/tabs.css";
|
||||
@import "./components/textarea.css";
|
||||
@import "./components/toast.css";
|
||||
@import "./components/tooltip.css";
|
||||
@import "./styles/style-mira.css";
|
||||
@@ -0,0 +1,2 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@import "./basecoat-nova.css";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user