Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e992e29f18 | ||
|
|
b7d33eee04 | ||
|
|
30e5bdcf3a | ||
|
|
cafffa0597 | ||
|
|
2a2c64c651 | ||
|
|
0b28009e38 | ||
|
|
d942c8b31d | ||
|
|
d826637eb9 | ||
|
|
2ad4f6a43b | ||
|
|
6953a4adbe | ||
|
|
f49eaa8513 | ||
|
|
fcea07fc3d | ||
|
|
aa09ddb271 | ||
|
|
05ea49ccc5 | ||
|
|
5f84c842f7 | ||
|
|
7dc70daa83 | ||
|
|
b4424e9e8b | ||
|
|
d431873be7 | ||
|
|
537f77507e | ||
|
|
521bd93705 | ||
|
|
a9e1fe7b91 | ||
|
|
f254b92bd3 | ||
|
|
c81900a58d | ||
|
|
2be9606ce4 | ||
|
|
be38645049 | ||
|
|
cc50da553f | ||
|
|
bf8f721662 | ||
|
|
7cd8a09de3 | ||
|
|
4942a79c95 | ||
|
|
3b49a0efc5 | ||
|
|
6c87a5cde8 | ||
|
|
77c5281f49 | ||
|
|
a0715d17ab |
@@ -1 +0,0 @@
|
||||
docs/src/assets/styles.css merge=theirs
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Deploy site on release
|
||||
|
||||
# Cloudflare Workers Builds deploys the site from the `release` branch.
|
||||
# Publishing a GitHub release moves `release` to the released tag.
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Tag to deploy (for example 1.1.0)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
move-release-branch:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Point release branch at tag
|
||||
env:
|
||||
TAG: ${{ inputs.tag || github.event.release.tag_name }}
|
||||
run: |
|
||||
SHA=$(git rev-parse "refs/tags/$TAG^{commit}")
|
||||
git push --force origin "$SHA:refs/heads/release"
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Announce release on Discord
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag to announce
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
announce:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Send Discord announcement
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
RELEASE_TAG_INPUT: ${{ inputs.tag }}
|
||||
REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
test -n "$DISCORD_WEBHOOK_URL"
|
||||
|
||||
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
|
||||
test -n "$RELEASE_TAG_INPUT"
|
||||
curl --fail-with-body \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer $GH_TOKEN" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"$GITHUB_API_URL/repos/$REPO/releases/tags/$RELEASE_TAG_INPUT" \
|
||||
> release.json
|
||||
else
|
||||
jq '.release' "$GITHUB_EVENT_PATH" > release.json
|
||||
fi
|
||||
|
||||
if [ "$(jq -r '.prerelease' release.json)" = "true" ]; then
|
||||
echo "Skipping prerelease."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg repo "$REPO" \
|
||||
--arg name "$(jq -r '.name // ""' release.json)" \
|
||||
--arg tag "$(jq -r '.tag_name' release.json)" \
|
||||
--arg url "$(jq -r '.html_url' release.json)" \
|
||||
--arg body "$(jq -r '.body // ""' release.json)" \
|
||||
'($body | if length > 1500 then .[:1500] + "...\n\n[Read more](" + $url + ")" else . end) as $description |
|
||||
{
|
||||
username: "Basecoat Releases",
|
||||
allowed_mentions: { parse: [] },
|
||||
embeds: [{
|
||||
title: ($repo + " " + $tag),
|
||||
url: $url,
|
||||
description: ((if $name != "" then "**" + $name + "**\n\n" else "" end) + $description),
|
||||
color: 5793266
|
||||
}]
|
||||
}' > payload.json
|
||||
|
||||
curl --fail-with-body \
|
||||
--retry 3 \
|
||||
--retry-all-errors \
|
||||
--retry-delay 2 \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d @payload.json \
|
||||
"$DISCORD_WEBHOOK_URL"
|
||||
@@ -1,16 +1,18 @@
|
||||
.*
|
||||
!.gitignore
|
||||
!.gitattributes
|
||||
!.github/
|
||||
!.github/workflows/
|
||||
!.github/workflows/*.yml
|
||||
!.github/workflows/*.yaml
|
||||
_site/
|
||||
|
||||
node_modules/
|
||||
packages/cli/dist/
|
||||
packages/css/dist/
|
||||
|
||||
# Logs
|
||||
npm-debug.log*
|
||||
dist/
|
||||
docs/generated/
|
||||
public/assets/
|
||||
|
||||
public/fragments/
|
||||
templates/
|
||||
site/dist/
|
||||
site/public/assets/js/
|
||||
site/public/fragments/
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
## Source Boundaries
|
||||
- Do not hand-edit build outputs:
|
||||
- `packages/*/dist/**`
|
||||
- `dist/**`
|
||||
- `docs/generated/**`
|
||||
- `public/assets/**`
|
||||
- `templates/**`
|
||||
- `site/dist/**`
|
||||
- `site/public/assets/js/**`
|
||||
- `site/public/fragments/**`
|
||||
- Generated source entrypoints such as `src/css/basecoat-vega.css` are committed, but must be regenerated through `scripts/generate-css-entrypoints.js` or build scripts, not manually maintained.
|
||||
|
||||
## Public API and Markup
|
||||
@@ -47,6 +48,7 @@
|
||||
## JavaScript
|
||||
- JavaScript is behavior only; do not use JS to paper over CSS architecture issues.
|
||||
- Register JS components with `window.basecoat.register()` and make initialization idempotent with `data-*-initialized` flags.
|
||||
- Component init functions must return before attaching listeners or mutating DOM when the root already has its initialized flag.
|
||||
- Keep generated DOM minimal and aligned with documented HTML.
|
||||
- Prefer native browser APIs and events.
|
||||
- Keep scroll management scoped to component containers. Do not use `scrollIntoView()` when it can scroll the page unexpectedly.
|
||||
@@ -64,8 +66,8 @@
|
||||
- component CSS: `src/css/components/<component>.css`
|
||||
- style packs: `src/css/styles/{vega,nova,maia,lyra,mira,luma,sera,rhea}.css`
|
||||
- JS: `src/js/<component>.js` when behavior changes
|
||||
- templates: `src/nunjucks/*.njk` and `src/jinja/*.html.jinja` when markup changes
|
||||
- docs: `docs/src/components/<component>.md`
|
||||
- templates: `src/templates/nunjucks/*.njk` and `src/templates/jinja/*.html.jinja` when markup changes
|
||||
- docs: `site/src/docs/components/<component>.mdx`
|
||||
- Map upstream concepts, not implementation details. Do not port `cn-*` selectors directly.
|
||||
- Classify differences as `drift fixed`, `intentional`, or `deferred`.
|
||||
|
||||
|
||||
@@ -2,6 +2,63 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [1.0.2] - 2026-07-06
|
||||
|
||||
### Added
|
||||
|
||||
- Added `data-size="sm"` support to Alert Dialog for the compact upstream size.
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed global font smoothing from Basecoat's package CSS so `antialiased` remains an application-level choice.
|
||||
|
||||
## [1.0.1] - 2026-06-28
|
||||
|
||||
### Added
|
||||
|
||||
- Added explicit `force: true` support to `window.basecoat.init()` and `window.basecoat.initAll()` for rehydrating restored DOM after framework or navigation cache restores.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Reinitialized Basecoat components after HTMX history restores in the docs site.
|
||||
|
||||
## [1.0.0] - 2026-06-27
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Removed the CLI workspace from the repo. Template files now ship with `basecoat-css` under `templates/nunjucks` and `templates/jinja`; copy them from `node_modules/basecoat-css/templates/*` instead of installing `basecoat-cli`.
|
||||
|
||||
### Added
|
||||
|
||||
- Added Nunjucks and Jinja template files to the `basecoat-css` package.
|
||||
- Added a custom docs 404 page for Cloudflare static asset fallback.
|
||||
|
||||
### Changed
|
||||
|
||||
- Narrowed dark-mode generated selectors to `html.dark` to reduce broad style recalculation work.
|
||||
- Switched the docs site to Astro's sitemap integration and updated `robots.txt` to point at the generated sitemap index.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Updated Scroll Area examples to use Card surfaces so framed examples inherit style-pack radius and border treatment.
|
||||
|
||||
## [1.0.0-beta.7] - 2026-06-25
|
||||
|
||||
### Added
|
||||
|
||||
- Added a beta Drawer component with native `<dialog>` markup, side placement, animated close behavior, backdrop and Escape handling, style-pack visuals, JavaScript entrypoints, and docs.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated the docs dependency to `reallysimpledocs@^1.0.2`.
|
||||
- Marked Chart and Drawer as beta components in the docs navigation.
|
||||
- Promoted the Chart docs warning from alpha to beta.
|
||||
- Updated installation CDN examples for `basecoat-css@1.0.0-beta.7`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Aligned Drawer examples more closely with upstream shadcn/ui while avoiding Chart API coupling in Drawer docs.
|
||||
|
||||
## [1.0.0-beta.6] - 2026-06-23
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
# Contributing to Basecoat
|
||||
|
||||
- Support development by sponsoring the author: https://github.com/sponsors/hunvreus
|
||||
- Submit pull requests (PRs) against the `development` branch, not `main`.
|
||||
- Submit pull requests (PRs) against the `main` branch.
|
||||
- For branches, we have:
|
||||
- `main` is our production and default branch. This is what we deploy to https://basecoatui.com and publish to https://www.npmjs.com/package/basecoat-css and https://www.npmjs.com/package/basecoat-cli.
|
||||
- `development` is for staging. We will deploy it soon at https://dev.basecoatui.com.
|
||||
- `main` is our production and default branch. This is what we deploy to https://basecoatui.com and publish to https://www.npmjs.com/package/basecoat-css.
|
||||
- New features are worked in `feature/name-of-the-feature` branches.
|
||||
- Issues are addressed in `issue/123-main-isse` branches.
|
||||
- When ready, we PR against `development`, test it and then finally merge to `main`.
|
||||
- Issues are addressed in `issue/123-main-issue` branches.
|
||||
- When ready, open a PR against `main`.
|
||||
- Keep changes focused: one feature or fix per PR.
|
||||
- Test locally before submitting.
|
||||
- Follow existing code style.
|
||||
|
||||
## Cloudflare Workers deploys
|
||||
|
||||
Docs are built with 11ty and deployed as Workers Static Assets from `_site`.
|
||||
Docs are built with ReallySimpleDocs/Astro and deployed as Workers Static Assets from `site/dist`.
|
||||
|
||||
- Local preview: `npm run workers:dev`
|
||||
- Production deploy: `npm run workers:deploy`
|
||||
@@ -29,3 +28,15 @@ Workers Builds settings:
|
||||
- Enable builds for non-production branches for branch preview URLs.
|
||||
|
||||
Thanks for helping!
|
||||
|
||||
## Releases and the docs site
|
||||
|
||||
The site at basecoatui.com deploys from the `release` branch, not `main`, so the docs always match the version on npm. Nobody commits to `release` directly.
|
||||
|
||||
To release:
|
||||
|
||||
1. Bump the version in `package.json`, commit, tag (for example `1.1.0`) and push.
|
||||
2. Run `npm publish`.
|
||||
3. Publish a GitHub release for the tag, for example `gh release create 1.1.0 --generate-notes`.
|
||||
|
||||
Publishing the release runs `.github/workflows/deploy-site.yml`, which points `release` at the tag. Cloudflare then builds and deploys the site. To redeploy or roll back, run that workflow manually with the tag you want live.
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
Basecoat is a Tailwind CSS, vanilla HTML/CSS/JavaScript implementation of the shadcn/ui design system. It provides shadcn-style components for any web stack without React, Radix, or framework runtime dependencies.
|
||||
|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- Semantic HTML-first components.
|
||||
- Tailwind CSS v4 source files and generated CSS bundles.
|
||||
- Small vanilla JavaScript for components that need behavior.
|
||||
- Nunjucks and Jinja template macros.
|
||||
- Standalone style packs: Vega, Nova, Maia, Lyra, Mira, Luma, Sera, and Rhea.
|
||||
- Dark mode and CSS variable theming.
|
||||
- CDN, npm, and CLI usage paths.
|
||||
- Compatible with shadcn/ui themes.
|
||||
- Dark mode.
|
||||
- CDN, npm, and template usage paths.
|
||||
- Nunjucks and Jinja templates.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -20,18 +21,6 @@ Basecoat is a Tailwind CSS, vanilla HTML/CSS/JavaScript implementation of the sh
|
||||
- Installation: [basecoatui.com/installation](https://basecoatui.com/installation)
|
||||
- Customization: [basecoatui.com/customization](https://basecoatui.com/customization)
|
||||
|
||||
## Packages
|
||||
|
||||
This repository publishes two workspace packages:
|
||||
|
||||
- `basecoat-css`: CSS, JavaScript, Nunjucks macros, and Jinja macros.
|
||||
- `basecoat-cli`: CLI for adding Basecoat assets to a project.
|
||||
|
||||
Package details live in:
|
||||
|
||||
- `packages/css/README.md`
|
||||
- `packages/cli/README.md`
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
@@ -64,24 +53,21 @@ Use the styleless base plus a custom style file:
|
||||
|
||||
```text
|
||||
.
|
||||
├── docs/
|
||||
│ ├── generated/ Generated RSD/Astro docs input
|
||||
│ └── src/ Documentation source pages, examples, and fragments
|
||||
├── packages/
|
||||
│ ├── cli/ Published CLI package
|
||||
│ └── css/ Published CSS package
|
||||
├── public/ Generated docs assets for Astro
|
||||
├── dist/ Generated package output
|
||||
├── scripts/ Build and generation scripts
|
||||
└── src/
|
||||
├── components/ Astro overrides for ReallySimpleDocs
|
||||
├── css/
|
||||
│ ├── base/ Shared tokens, base layer, and semantic utilities
|
||||
│ ├── components/ Component structure and behavior hooks
|
||||
│ └── styles/ Style-pack visual rules
|
||||
├── docs.css Documentation-site CSS extension
|
||||
├── jinja/ Jinja component macros
|
||||
├── js/ Vanilla JS components and registry
|
||||
└── nunjucks/ Nunjucks component macros
|
||||
├── site/ ReallySimpleDocs/Astro documentation app
|
||||
│ ├── public/ Static site assets
|
||||
│ └── src/
|
||||
│ ├── components/ RSD component overrides
|
||||
│ ├── docs/ Documentation pages and examples
|
||||
│ ├── fragments/ Site-only rendered fragments
|
||||
│ ├── pages/ Astro routes outside the docs tree
|
||||
│ └── scripts/ Site scripts
|
||||
├── src/ Package source
|
||||
│ ├── css/
|
||||
│ ├── js/
|
||||
│ └── templates/
|
||||
└── templates/ Generated package template output
|
||||
```
|
||||
|
||||
## CSS Architecture
|
||||
@@ -106,6 +92,7 @@ These entrypoints are generated by `scripts/generate-css-entrypoints.js` and by
|
||||
|
||||
- `window.basecoat.init(name)` initializes uninitialized components for one registered component.
|
||||
- `window.basecoat.initAll()` initializes all uninitialized registered components.
|
||||
- `window.basecoat.init(name, { force: true })` and `window.basecoat.initAll({ force: true })` destroy and reinitialize matching components. Use this when a framework or navigation library restores previously initialized DOM from a cache. Force mode resets transient component state such as open menus, focus state, and active interactions.
|
||||
- `window.basecoat.refresh(element)` asks an initialized component to rescan dynamic children when it supports refresh.
|
||||
- Components use internal destroy hooks to clean up listeners when initialized roots are removed from the DOM.
|
||||
|
||||
@@ -114,8 +101,9 @@ These entrypoints are generated by `scripts/generate-css-entrypoints.js` and by
|
||||
```bash
|
||||
# Install dependencies.
|
||||
npm i
|
||||
npm --prefix site i
|
||||
|
||||
# Generate docs input and run the ReallySimpleDocs/Astro site.
|
||||
# Run the ReallySimpleDocs/Astro site.
|
||||
npm run docs:dev
|
||||
|
||||
# Build package assets.
|
||||
@@ -123,12 +111,6 @@ npm run build
|
||||
|
||||
# Build the static docs site.
|
||||
npm run docs:build
|
||||
|
||||
# Run the Workers docs site locally.
|
||||
npm run workers:dev
|
||||
|
||||
# Deploy the Workers docs site.
|
||||
npm run workers:deploy
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discord</title><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,8 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-luma.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/luma";
|
||||
@@ -1,8 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-lyra.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/lyra";
|
||||
@@ -1,8 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-maia.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/maia";
|
||||
@@ -1,8 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-mira.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/mira";
|
||||
@@ -1,8 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-nova.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/nova";
|
||||
@@ -1,8 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-rhea.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/rhea";
|
||||
@@ -1,8 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-sera.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/sera";
|
||||
@@ -1,8 +0,0 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../../docs/src/**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../../src/pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../src/css/base/base.css";
|
||||
@import "../../../../src/css/basecoat-vega.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/vega";
|
||||
@@ -1,113 +0,0 @@
|
||||
---
|
||||
import vegaUrl from "../assets/styles/style-vega.css?url";
|
||||
import novaUrl from "../assets/styles/style-nova.css?url";
|
||||
import maiaUrl from "../assets/styles/style-maia.css?url";
|
||||
import lyraUrl from "../assets/styles/style-lyra.css?url";
|
||||
import miraUrl from "../assets/styles/style-mira.css?url";
|
||||
import lumaUrl from "../assets/styles/style-luma.css?url";
|
||||
import seraUrl from "../assets/styles/style-sera.css?url";
|
||||
import rheaUrl from "../assets/styles/style-rhea.css?url";
|
||||
import demoInteractionsUrl from "./demo-interactions.js?url";
|
||||
import htmxUrl from "htmx.org/dist/htmx.min.js?url";
|
||||
|
||||
const styleUrls = {
|
||||
vega: vegaUrl,
|
||||
nova: novaUrl,
|
||||
maia: maiaUrl,
|
||||
lyra: lyraUrl,
|
||||
mira: miraUrl,
|
||||
luma: lumaUrl,
|
||||
sera: seraUrl,
|
||||
rhea: rheaUrl,
|
||||
};
|
||||
---
|
||||
|
||||
<style id="docs-style-gate" is:inline>
|
||||
html[data-style-pending] body {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline define:vars={{ styleUrls }}>
|
||||
(() => {
|
||||
const styleGateId = "docs-style-gate";
|
||||
const styleLinkId = "docs-style-variant";
|
||||
const clearStylePending = () => {
|
||||
document.documentElement.removeAttribute("data-style-pending");
|
||||
document.getElementById(styleGateId)?.remove();
|
||||
};
|
||||
const getStoredStyle = () => {
|
||||
try {
|
||||
return localStorage.getItem("styleVariant") || "vega";
|
||||
} catch (_) {
|
||||
return "vega";
|
||||
}
|
||||
};
|
||||
const getStyleHref = (style) => {
|
||||
const href = styleUrls[style];
|
||||
return href?.startsWith("/docs/src/assets/styles/") ? `${href}?direct` : href;
|
||||
};
|
||||
|
||||
const style = getStoredStyle();
|
||||
const nextStyle = Object.prototype.hasOwnProperty.call(styleUrls, style) ? style : "vega";
|
||||
const href = getStyleHref(nextStyle);
|
||||
|
||||
document.documentElement.dataset.styleVariant = nextStyle;
|
||||
|
||||
if (!href) return;
|
||||
|
||||
document.documentElement.setAttribute("data-style-pending", "");
|
||||
|
||||
const existing = document.getElementById("docs-style-variant");
|
||||
if (existing?.getAttribute("href") === href) {
|
||||
clearStylePending();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existing && document.currentScript) {
|
||||
document.write(
|
||||
`<link id="${styleLinkId}" rel="stylesheet" href="${href}" onload="document.documentElement.removeAttribute('data-style-pending');document.getElementById('${styleGateId}')?.remove()" onerror="document.documentElement.removeAttribute('data-style-pending');document.getElementById('${styleGateId}')?.remove()">`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const link = existing || document.createElement("link");
|
||||
link.id = styleLinkId;
|
||||
link.rel = "stylesheet";
|
||||
link.addEventListener("load", clearStylePending, { once: true });
|
||||
link.addEventListener("error", clearStylePending, { once: true });
|
||||
link.href = href;
|
||||
if (!existing) document.head.appendChild(link);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const key = (href) => {
|
||||
const url = new URL(href, window.location.origin);
|
||||
return `${url.pathname.replace(/\/$/, "")}${url.search}`;
|
||||
};
|
||||
|
||||
const syncSidebar = () => {
|
||||
for (const link of document.querySelectorAll("#sidebar a[href]")) {
|
||||
const isCurrent = key(link.href) === key(window.location.href);
|
||||
if (isCurrent) {
|
||||
link.setAttribute("aria-current", "page");
|
||||
link.closest("details")?.setAttribute("open", "");
|
||||
} else {
|
||||
link.removeAttribute("aria-current");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", syncSidebar);
|
||||
document.addEventListener("htmx:afterSettle", () => {
|
||||
window.basecoat?.initAll?.();
|
||||
syncSidebar();
|
||||
});
|
||||
window.addEventListener("popstate", syncSidebar);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script src={htmxUrl} defer></script>
|
||||
<script type="module" src={demoInteractionsUrl}></script>
|
||||
@@ -1,12 +1,18 @@
|
||||
{
|
||||
"name": "basecoat",
|
||||
"version": "1.0.0-beta.6",
|
||||
"description": "shadcn/ui for the rest of us",
|
||||
"author": "hunvreus",
|
||||
"name": "basecoat-css",
|
||||
"version": "1.0.2",
|
||||
"description": "Tailwind CSS for Basecoat components",
|
||||
"author": {
|
||||
"name": "hunvreus",
|
||||
"url": "https://x.com/hunvreus"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
"main": "dist/basecoat.css",
|
||||
"style": "dist/basecoat.css",
|
||||
"files": [
|
||||
"dist/",
|
||||
"templates/"
|
||||
],
|
||||
"keywords": [
|
||||
"components",
|
||||
@@ -23,7 +29,6 @@
|
||||
"jinja",
|
||||
"nunjucks"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/hunvreus/basecoat.git"
|
||||
@@ -31,27 +36,104 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/hunvreus/basecoat/issues"
|
||||
},
|
||||
"homepage": "https://github.com/hunvreus/basecoat#readme",
|
||||
"homepage": "https://basecoatui.com/installation#npm",
|
||||
"exports": {
|
||||
".": "./dist/basecoat.css",
|
||||
"./css": "./dist/basecoat.css",
|
||||
"./basecoat": "./dist/js/basecoat.js",
|
||||
"./basecoat.min": "./dist/js/basecoat.min.js",
|
||||
"./all": "./dist/js/all.js",
|
||||
"./all.min": "./dist/js/all.min.js",
|
||||
"./accordion": "./dist/js/accordion.js",
|
||||
"./accordion.min": "./dist/js/accordion.min.js",
|
||||
"./chart": "./dist/js/chart.js",
|
||||
"./chart.min": "./dist/js/chart.min.js",
|
||||
"./combobox": "./dist/js/combobox.js",
|
||||
"./combobox.min": "./dist/js/combobox.min.js",
|
||||
"./command": "./dist/js/command.js",
|
||||
"./command.min": "./dist/js/command.min.js",
|
||||
"./dropdown-menu": "./dist/js/dropdown-menu.js",
|
||||
"./dropdown-menu.min": "./dist/js/dropdown-menu.min.js",
|
||||
"./drawer": "./dist/js/drawer.js",
|
||||
"./drawer.min": "./dist/js/drawer.min.js",
|
||||
"./popover": "./dist/js/popover.js",
|
||||
"./popover.min": "./dist/js/popover.min.js",
|
||||
"./range": "./dist/js/range.js",
|
||||
"./range.min": "./dist/js/range.min.js",
|
||||
"./select": "./dist/js/select.js",
|
||||
"./select.min": "./dist/js/select.min.js",
|
||||
"./sidebar": "./dist/js/sidebar.js",
|
||||
"./sidebar.min": "./dist/js/sidebar.min.js",
|
||||
"./tabs": "./dist/js/tabs.js",
|
||||
"./tabs.min": "./dist/js/tabs.min.js",
|
||||
"./toast": "./dist/js/toast.js",
|
||||
"./toast.min": "./dist/js/toast.min.js",
|
||||
"./all.css": "./dist/basecoat.all.css",
|
||||
"./base": "./dist/basecoat-base.css",
|
||||
"./base.css": "./dist/basecoat-base.css",
|
||||
"./base.cdn.css": "./dist/basecoat-base.cdn.css",
|
||||
"./compat": "./dist/basecoat-compat.css",
|
||||
"./compat.css": "./dist/basecoat-compat.css",
|
||||
"./compat.cdn.css": "./dist/basecoat-compat.cdn.css",
|
||||
"./compat/legacy.css": "./dist/compat/legacy.css",
|
||||
"./components": "./dist/basecoat-components.css",
|
||||
"./components.css": "./dist/basecoat-components.css",
|
||||
"./components/*": "./dist/components/*.css",
|
||||
"./components/*.css": "./dist/components/*.css",
|
||||
"./base/base.css": "./dist/base/base.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/vega.css",
|
||||
"./styles/nova": "./dist/styles/nova.css",
|
||||
"./styles/maia": "./dist/styles/maia.css",
|
||||
"./styles/lyra": "./dist/styles/lyra.css",
|
||||
"./styles/mira": "./dist/styles/mira.css",
|
||||
"./styles/luma": "./dist/styles/luma.css",
|
||||
"./styles/sera": "./dist/styles/sera.css",
|
||||
"./styles/rhea": "./dist/styles/rhea.css",
|
||||
"./styles/*": "./dist/styles/*.css",
|
||||
"./styles/*.css": "./dist/styles/*.css",
|
||||
"./package.json": "./package.json",
|
||||
"./styles/style-vega.css": "./dist/styles/vega.css",
|
||||
"./styles/style-nova.css": "./dist/styles/nova.css",
|
||||
"./styles/style-maia.css": "./dist/styles/maia.css",
|
||||
"./styles/style-lyra.css": "./dist/styles/lyra.css",
|
||||
"./styles/style-mira.css": "./dist/styles/mira.css",
|
||||
"./styles/style-luma.css": "./dist/styles/luma.css",
|
||||
"./styles/style-sera.css": "./dist/styles/sera.css",
|
||||
"./styles/style-rhea.css": "./dist/styles/rhea.css",
|
||||
"./styles/style-*.css": "./dist/styles/*.css",
|
||||
"./templates/nunjucks/*": "./templates/nunjucks/*",
|
||||
"./templates/jinja/*": "./templates/jinja/*"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ./scripts/build.js",
|
||||
"docs:build": "npm run build && astro build",
|
||||
"docs:dev": "astro dev",
|
||||
"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"
|
||||
"prepack": "node ./scripts/build.js",
|
||||
"docs:build": "npm run build && npm --prefix site clean-install --progress=false && npm --prefix site run build",
|
||||
"docs:dev": "npm --prefix site run dev",
|
||||
"docs:preview": "npm run docs:build && npm --prefix site run preview",
|
||||
"workers:dev": "npm run build && npm --prefix site clean-install --progress=false && npm --prefix site run workers:dev",
|
||||
"workers:deploy": "npm run build && npm --prefix site clean-install --progress=false && npm --prefix site run workers:deploy",
|
||||
"workers:preview": "npm run build && npm --prefix site clean-install --progress=false && npm --prefix site run workers:preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/geist-mono": "^5.2.8",
|
||||
"@fontsource/geist-sans": "^5.2.5",
|
||||
"@tailwindcss/cli": "^4.1.17",
|
||||
"astro": "^6.4.6",
|
||||
"htmx.org": "^2.0.10",
|
||||
"lucide-static": "^1.17.0",
|
||||
"nunjucks": "^3.2.4",
|
||||
"prettier": "^3.5.3",
|
||||
"reallysimpledocs": "^1.0.1",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"terser": "^5.42.0",
|
||||
"wrangler": "^4.50.0"
|
||||
"terser": "^5.42.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# basecoat-cli
|
||||
|
||||
This package provides a Command Line Interface (CLI) to help you quickly scaffold [Basecoat](https://basecoatui.com) components into your project.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Ensure you have Node.js and npm (or your preferred package manager) installed.
|
||||
- For the components to function correctly, your project should have:
|
||||
- [Tailwind CSS](https://tailwindcss.com/docs/installation) installed and configured.
|
||||
- The `basecoat-css` package installed and imported (or `basecoat.css` manually added).
|
||||
|
||||
## Usage
|
||||
|
||||
Use the `add` command to add components to your project:
|
||||
|
||||
```bash
|
||||
# npx
|
||||
npx basecoat-cli add [component]
|
||||
|
||||
# pnpm
|
||||
pnpm dlx basecoat-cli add [component]
|
||||
|
||||
# bun
|
||||
bunx basecoat-cli add [component]
|
||||
|
||||
# yarn
|
||||
yarn dlx basecoat-cli add [component]
|
||||
```
|
||||
|
||||
Replace `[component]` with the name of the component you wish to add (e.g., `dialog`, `select`) or leave it out if you want to pick from the list of available components
|
||||
or install all of them at once.
|
||||
|
||||
The CLI will prompt you for:
|
||||
|
||||
1. **Template Engine:** Choose between Nunjucks (`.njk`) or Jinja (`.html.jinja`).
|
||||
2. **Assets Directory:** The path where component assets (like JavaScript files) should be placed (e.g., `src/assets/js/components`).
|
||||
3. **Macros Directory:** The path where component macros should be placed (e.g., `src/_includes/macros/components`).
|
||||
|
||||
The CLI will then copy the necessary JavaScript files and Nunjucks/Jinja macros for the selected component into the specified directories.
|
||||
|
||||
### Example
|
||||
|
||||
To add the `dialog` component:
|
||||
|
||||
```bash
|
||||
npx basecoat-cli add dialog
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For more detailed information on components, their usage, and customization options, please refer to the [Basecoat documentation](https://basecoatui.com/installation/#install-cli).
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/hunvreus/basecoat/blob/main/LICENSE.md)
|
||||
@@ -1,206 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from 'commander';
|
||||
import inquirer from 'inquirer';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
// Attempt to read version from the CLI's own package.json
|
||||
let packageVersion = 'unknown';
|
||||
try {
|
||||
const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url)); // .../packages/cli/dist
|
||||
const cliPackageRootDir = path.resolve(cliScriptRunningDir, '..'); // .../packages/cli
|
||||
const packageJsonPath = path.join(cliPackageRootDir, 'package.json');
|
||||
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
packageVersion = JSON.parse(packageJsonContent).version;
|
||||
} catch (error) {
|
||||
console.warn('Could not read CLI package version:', error.message);
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
templateEngine: null,
|
||||
templateDest: './components/basecoat',
|
||||
scriptDest: './static/js/basecoat'
|
||||
};
|
||||
let config = { ...DEFAULT_CONFIG }; // User-specific config, potentially updated by prompts
|
||||
const SCRIPT_ONLY_COMPONENTS = new Set(['chart', 'range']);
|
||||
|
||||
// Discovers available components from the CLI's bundled assets.
|
||||
async function getAvailableComponents() {
|
||||
try {
|
||||
const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const assetsDir = path.resolve(cliScriptRunningDir, 'assets');
|
||||
const jsSourceDir = path.join(assetsDir, 'js');
|
||||
const nunjucksSourceDir = path.join(assetsDir, 'nunjucks');
|
||||
const jinjaSourceDir = path.join(assetsDir, 'jinja');
|
||||
|
||||
const jsFiles = await fs.readdir(jsSourceDir);
|
||||
const nunjucksFiles = await fs.readdir(nunjucksSourceDir).catch(() => []);
|
||||
const jinjaFiles = await fs.readdir(jinjaSourceDir).catch(() => []);
|
||||
const templateNames = new Set([
|
||||
...nunjucksFiles.filter(file => file.endsWith('.njk')).map(file => path.basename(file, '.njk')),
|
||||
...jinjaFiles.filter(file => file.endsWith('.html.jinja')).map(file => path.basename(file, '.html.jinja')),
|
||||
]);
|
||||
|
||||
return jsFiles
|
||||
.filter(file => file.endsWith('.js') && !file.endsWith('.min.js'))
|
||||
.map(file => path.basename(file, '.js'))
|
||||
.filter(name => name !== 'all' && name !== 'basecoat')
|
||||
.filter(name => templateNames.has(name) || SCRIPT_ONLY_COMPONENTS.has(name));
|
||||
} catch (error) {
|
||||
console.error("Error reading component source directories from CLI assets:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures necessary configuration (template engine, destination paths) is set, prompting the user if needed.
|
||||
async function ensureConfiguration() {
|
||||
if (!config.templateEngine) {
|
||||
const engineChoice = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'engine',
|
||||
message: 'Which template engine are you using?',
|
||||
choices: ['nunjucks', 'jinja'],
|
||||
}
|
||||
]);
|
||||
config.templateEngine = engineChoice.engine;
|
||||
}
|
||||
|
||||
const pathPrompts = [];
|
||||
if (!config.templateDest || config.templateDest === DEFAULT_CONFIG.templateDest) {
|
||||
pathPrompts.push({
|
||||
type: 'input',
|
||||
name: 'templateDest',
|
||||
message: 'Where should template files be placed?',
|
||||
default: DEFAULT_CONFIG.templateDest,
|
||||
});
|
||||
}
|
||||
if (!config.scriptDest || config.scriptDest === DEFAULT_CONFIG.scriptDest) {
|
||||
pathPrompts.push({
|
||||
type: 'input',
|
||||
name: 'scriptDest',
|
||||
message: 'Where should script files be placed?',
|
||||
default: DEFAULT_CONFIG.scriptDest,
|
||||
});
|
||||
}
|
||||
|
||||
if (pathPrompts.length > 0) {
|
||||
const pathChoices = await inquirer.prompt(pathPrompts);
|
||||
if (pathChoices.templateDest) config.templateDest = pathChoices.templateDest;
|
||||
if (pathChoices.scriptDest) config.scriptDest = pathChoices.scriptDest;
|
||||
}
|
||||
}
|
||||
|
||||
// Copies a component's template and script files to the user's project.
|
||||
async function addComponent(componentName) {
|
||||
console.log(`\nProcessing component: ${componentName}...`);
|
||||
|
||||
const cliScriptRunningDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const assetsDir = path.resolve(cliScriptRunningDir, 'assets');
|
||||
const templateExt = config.templateEngine === 'jinja' ? '.html.jinja' : '.njk';
|
||||
|
||||
const templateSource = path.join(assetsDir, config.templateEngine, `${componentName}${templateExt}`);
|
||||
const scriptSource = path.join(assetsDir, 'js', `${componentName}.js`);
|
||||
|
||||
const templateDestPath = path.resolve(process.cwd(), config.templateDest, `${componentName}${templateExt}`);
|
||||
const scriptDestPath = path.resolve(process.cwd(), config.scriptDest, `${componentName}.js`);
|
||||
|
||||
try {
|
||||
const templateExists = await fs.pathExists(templateSource);
|
||||
const scriptExists = await fs.pathExists(scriptSource);
|
||||
|
||||
if (!templateExists && !SCRIPT_ONLY_COMPONENTS.has(componentName)) {
|
||||
console.error(` Error: Template file for component '${componentName}' not found in CLI assets. Searched: ${templateSource}`);
|
||||
// Optionally, list available template engines or files for that component if helpful
|
||||
}
|
||||
if (!scriptExists) {
|
||||
console.error(` Error: Script file for component '${componentName}' not found in CLI assets. Searched: ${scriptSource}`);
|
||||
}
|
||||
if ((!templateExists && !SCRIPT_ONLY_COMPONENTS.has(componentName)) || !scriptExists) return; // Skip this component if sources are missing
|
||||
|
||||
await fs.ensureDir(path.dirname(templateDestPath));
|
||||
await fs.ensureDir(path.dirname(scriptDestPath));
|
||||
|
||||
if (templateExists) {
|
||||
await fs.copyFile(templateSource, templateDestPath);
|
||||
console.log(` -> Copied template to: ${templateDestPath}`);
|
||||
}
|
||||
await fs.copyFile(scriptSource, scriptDestPath);
|
||||
console.log(` -> Copied script to: ${scriptDestPath}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(` Error processing component ${componentName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup main CLI program
|
||||
program
|
||||
.name('basecoat-cli')
|
||||
.description('Add Basecoat components to your project')
|
||||
.version(packageVersion);
|
||||
|
||||
// Define the 'add' command
|
||||
program
|
||||
.command('add')
|
||||
.description('Add one or more Basecoat components to your project')
|
||||
.argument('[components...]', 'Names of components to add (e.g., dialog select)')
|
||||
.action(async (componentsArg) => {
|
||||
let componentsToAdd = []; // Initialize as an empty array
|
||||
try {
|
||||
if (!componentsArg || componentsArg.length === 0) {
|
||||
const availableComponents = await getAvailableComponents();
|
||||
if (availableComponents.length === 0) {
|
||||
console.error('Error: No components available in the CLI. Build might be corrupted.');
|
||||
return;
|
||||
}
|
||||
|
||||
const allComponentsChoice = 'All components';
|
||||
const choices = [allComponentsChoice, ...availableComponents];
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'list', // Changed from checkbox to list
|
||||
name: 'selectedComponent', // Changed name for clarity
|
||||
message: 'Which component(s) would you like to add?',
|
||||
choices: choices,
|
||||
}
|
||||
]);
|
||||
|
||||
if (answers.selectedComponent === allComponentsChoice) {
|
||||
componentsToAdd = availableComponents; // Add all
|
||||
} else {
|
||||
componentsToAdd = [answers.selectedComponent]; // Add the single selected one
|
||||
}
|
||||
} else {
|
||||
// If components were provided as arguments, use them directly
|
||||
// (This part of the logic remains, ensuring componentsArg is an array)
|
||||
componentsToAdd = Array.isArray(componentsArg) ? componentsArg : [componentsArg];
|
||||
}
|
||||
|
||||
if (!componentsToAdd || componentsToAdd.length === 0) {
|
||||
console.log('No components selected. Exiting.');
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureConfiguration(); // Confirm/gather destination paths
|
||||
|
||||
for (const componentName of componentsToAdd) {
|
||||
await addComponent(componentName);
|
||||
}
|
||||
console.log('\nComponent addition process finished.');
|
||||
|
||||
} catch (error) {
|
||||
if (error.isTtyError || error.constructor?.name === 'ExitPromptError') { // Handle inquirer cancellation
|
||||
console.log('\nOperation cancelled by user.');
|
||||
} else {
|
||||
console.error('\nAn unexpected error occurred during the add command:', error);
|
||||
}
|
||||
process.exit(1); // Exit with error code for unexpected errors
|
||||
}
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "basecoat-cli",
|
||||
"version": "1.0.0-beta.6",
|
||||
"description": "Add Basecoat components to your project",
|
||||
"author": "hunvreus",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"basecoat-cli": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"dependencies": {
|
||||
"commander": "^13.1.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"inquirer": "^12.6.0"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"components",
|
||||
"component library",
|
||||
"component system",
|
||||
"ui",
|
||||
"ui kit",
|
||||
"shadcn",
|
||||
"shadcn/ui",
|
||||
"tailwind",
|
||||
"tailwindcss",
|
||||
"css",
|
||||
"html",
|
||||
"jinja",
|
||||
"nunjucks"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/hunvreus/basecoat.git",
|
||||
"directory": "packages/cli"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/hunvreus/basecoat/issues"
|
||||
},
|
||||
"homepage": "https://basecoatui.com/installation#install-cli"
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
# basecoat-css
|
||||
|
||||
This package provides the core CSS styles for [Basecoat](https://basecoatui.com), a component library built with Tailwind CSS.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Your project must have [Tailwind CSS](https://tailwindcss.com/docs/installation) installed and configured, as Basecoat relies on Tailwind utility classes and theming.
|
||||
|
||||
## Installation
|
||||
|
||||
Install with any package manager:
|
||||
|
||||
```bash
|
||||
npm install basecoat-css # or pnpm add / yarn add / bun add
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Add it just after Tailwind in your stylesheet:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
With a build tool (ESM):
|
||||
|
||||
```js
|
||||
import 'basecoat-css/all';
|
||||
```
|
||||
|
||||
Or cherry-pick the components you need:
|
||||
|
||||
```js
|
||||
import 'basecoat-css/tabs';
|
||||
import 'basecoat-css/popover';
|
||||
```
|
||||
|
||||
Without a build tool, copy the files from `node_modules`:
|
||||
|
||||
```bash
|
||||
npx copyfiles -u 1 "node_modules/basecoat-css/dist/js/**/*" public/js/basecoat
|
||||
```
|
||||
|
||||
Then reference what you need, e.g.
|
||||
|
||||
```html
|
||||
<script src="/js/basecoat/tabs.min.js" defer></script>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For more detailed information on components, their usage, and customization options, please refer to the [Basecoat documentation](https://basecoatui.com/installation/#install-css).
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/hunvreus/basecoat/blob/main/LICENSE.md)
|
||||
@@ -1,117 +0,0 @@
|
||||
{
|
||||
"name": "basecoat-css",
|
||||
"version": "1.0.0-beta.6",
|
||||
"description": "Tailwind CSS for Basecoat components",
|
||||
"author": {
|
||||
"name": "hunvreus",
|
||||
"url": "https://x.com/hunvreus"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "dist/basecoat.css",
|
||||
"style": "dist/basecoat.css",
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"keywords": [
|
||||
"components",
|
||||
"component library",
|
||||
"component system",
|
||||
"ui",
|
||||
"ui kit",
|
||||
"shadcn",
|
||||
"shadcn/ui",
|
||||
"tailwind",
|
||||
"tailwindcss",
|
||||
"css",
|
||||
"html",
|
||||
"jinja",
|
||||
"nunjucks"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/hunvreus/basecoat.git",
|
||||
"directory": "packages/css"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/hunvreus/basecoat/issues"
|
||||
},
|
||||
"homepage": "https://basecoatui.com/installation#install-npm",
|
||||
"exports": {
|
||||
".": "./dist/basecoat.css",
|
||||
"./css": "./dist/basecoat.css",
|
||||
"./basecoat": "./dist/js/basecoat.js",
|
||||
"./basecoat.min": "./dist/js/basecoat.min.js",
|
||||
"./all": "./dist/js/all.js",
|
||||
"./all.min": "./dist/js/all.min.js",
|
||||
"./chart": "./dist/js/chart.js",
|
||||
"./chart.min": "./dist/js/chart.min.js",
|
||||
"./combobox": "./dist/js/combobox.js",
|
||||
"./combobox.min": "./dist/js/combobox.min.js",
|
||||
"./command": "./dist/js/command.js",
|
||||
"./command.min": "./dist/js/command.min.js",
|
||||
"./dropdown-menu": "./dist/js/dropdown-menu.js",
|
||||
"./dropdown-menu.min": "./dist/js/dropdown-menu.min.js",
|
||||
"./popover": "./dist/js/popover.js",
|
||||
"./popover.min": "./dist/js/popover.min.js",
|
||||
"./range": "./dist/js/range.js",
|
||||
"./range.min": "./dist/js/range.min.js",
|
||||
"./select": "./dist/js/select.js",
|
||||
"./select.min": "./dist/js/select.min.js",
|
||||
"./sidebar": "./dist/js/sidebar.js",
|
||||
"./sidebar.min": "./dist/js/sidebar.min.js",
|
||||
"./tabs": "./dist/js/tabs.js",
|
||||
"./tabs.min": "./dist/js/tabs.min.js",
|
||||
"./toast": "./dist/js/toast.js",
|
||||
"./toast.min": "./dist/js/toast.min.js",
|
||||
"./all.css": "./dist/basecoat.all.css",
|
||||
"./base": "./dist/basecoat-base.css",
|
||||
"./base.css": "./dist/basecoat-base.css",
|
||||
"./base.cdn.css": "./dist/basecoat-base.cdn.css",
|
||||
"./compat": "./dist/basecoat-compat.css",
|
||||
"./compat.css": "./dist/basecoat-compat.css",
|
||||
"./compat.cdn.css": "./dist/basecoat-compat.cdn.css",
|
||||
"./compat/legacy.css": "./dist/compat/legacy.css",
|
||||
"./components": "./dist/basecoat-components.css",
|
||||
"./components.css": "./dist/basecoat-components.css",
|
||||
"./components/*": "./dist/components/*.css",
|
||||
"./components/*.css": "./dist/components/*.css",
|
||||
"./base/base.css": "./dist/base/base.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/vega.css",
|
||||
"./styles/nova": "./dist/styles/nova.css",
|
||||
"./styles/maia": "./dist/styles/maia.css",
|
||||
"./styles/lyra": "./dist/styles/lyra.css",
|
||||
"./styles/mira": "./dist/styles/mira.css",
|
||||
"./styles/luma": "./dist/styles/luma.css",
|
||||
"./styles/sera": "./dist/styles/sera.css",
|
||||
"./styles/rhea": "./dist/styles/rhea.css",
|
||||
"./styles/*": "./dist/styles/*.css",
|
||||
"./styles/*.css": "./dist/styles/*.css",
|
||||
"./package.json": "./package.json",
|
||||
"./styles/style-vega.css": "./dist/styles/vega.css",
|
||||
"./styles/style-nova.css": "./dist/styles/nova.css",
|
||||
"./styles/style-maia.css": "./dist/styles/maia.css",
|
||||
"./styles/style-lyra.css": "./dist/styles/lyra.css",
|
||||
"./styles/style-mira.css": "./dist/styles/mira.css",
|
||||
"./styles/style-luma.css": "./dist/styles/luma.css",
|
||||
"./styles/style-sera.css": "./dist/styles/sera.css",
|
||||
"./styles/style-rhea.css": "./dist/styles/rhea.css",
|
||||
"./styles/style-*.css": "./dist/styles/*.css"
|
||||
}
|
||||
}
|
||||
@@ -64,45 +64,62 @@ async function copyDirRecursive(src, dest) {
|
||||
}
|
||||
}
|
||||
|
||||
// Main build function to prepare packages for publishing.
|
||||
// Recursively inlines relative `@import "./...";` lines into a single CSS string.
|
||||
// Each file is inlined at most once per bundle (tracked in `seen`).
|
||||
async function inlineCssImports(filePath, seen) {
|
||||
const resolved = path.resolve(filePath);
|
||||
if (seen.has(resolved)) return '';
|
||||
seen.add(resolved);
|
||||
|
||||
const source = await fs.readFile(resolved, 'utf-8');
|
||||
const lines = source.split('\n');
|
||||
const out = [];
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^\s*@import\s+["'](\.{1,2}\/[^"']+)["']\s*;\s*$/);
|
||||
if (match) {
|
||||
const inlined = await inlineCssImports(path.join(path.dirname(resolved), match[1]), seen);
|
||||
if (inlined) out.push(inlined.replace(/\n+$/, ''));
|
||||
} else if (/^\s*@import\s+["']\.{1,2}\//.test(line)) {
|
||||
throw new Error(`Unsupported relative @import (with modifiers) in ${resolved}: ${line.trim()}`);
|
||||
} else {
|
||||
out.push(line);
|
||||
}
|
||||
}
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
// Writes an uncompiled single-file bundle for a dist CSS entrypoint.
|
||||
async function writeCssBundle(entryPath, outPath, label, version) {
|
||||
const body = await inlineCssImports(entryPath, new Set());
|
||||
const header = `/* Basecoat v${version}, ${label}, single-file source. Import after tailwindcss. */\n`;
|
||||
await fs.writeFile(outPath, `${header}${body.replace(/\n+$/, '')}\n`);
|
||||
}
|
||||
|
||||
// Main build function to prepare the package for publishing.
|
||||
async function build() {
|
||||
console.log('Starting build process...');
|
||||
await generateCssEntrypoints();
|
||||
|
||||
// Define all necessary paths
|
||||
const cliPackageDir = path.join(projectRoot, 'packages', 'cli');
|
||||
const cliDistDir = path.join(cliPackageDir, 'dist');
|
||||
const cliDistAssetsDir = path.join(cliDistDir, 'assets');
|
||||
|
||||
const cssPackageDir = path.join(projectRoot, 'packages', 'css');
|
||||
const cssDistDir = path.join(cssPackageDir, 'dist');
|
||||
const cssDistDir = path.join(projectRoot, 'dist');
|
||||
const cssTemplatesDir = path.join(projectRoot, 'templates');
|
||||
|
||||
const srcDir = path.join(projectRoot, 'src');
|
||||
const srcCssDir = path.join(srcDir, 'css');
|
||||
const srcCssStylesDir = path.join(srcCssDir, 'styles');
|
||||
const srcCssCompatDir = path.join(srcCssDir, 'compat');
|
||||
const srcJsDir = path.join(srcDir, 'js');
|
||||
const srcNunjucksDir = path.join(srcDir, 'nunjucks');
|
||||
const srcJinjaDir = path.join(srcDir, 'jinja');
|
||||
const srcNunjucksDir = path.join(srcDir, 'templates', 'nunjucks');
|
||||
const srcJinjaDir = path.join(srcDir, 'templates', 'jinja');
|
||||
|
||||
// Clean previous build artifacts
|
||||
console.log('Cleaning distribution directories...');
|
||||
await cleanDir(cliDistDir);
|
||||
await cleanDir(cssDistDir);
|
||||
|
||||
// Build CLI package
|
||||
console.log('Building CLI package...');
|
||||
await ensureDir(cliDistDir); // Ensure base dist dir exists
|
||||
await ensureDir(cliDistAssetsDir); // Ensure assets dir within dist exists
|
||||
|
||||
await fs.copyFile(path.join(cliPackageDir, 'index.js'), path.join(cliDistDir, 'index.js'));
|
||||
console.log(`Copied CLI index.js to ${cliDistDir}`);
|
||||
await cleanDir(cssTemplatesDir);
|
||||
|
||||
// JS files minification and copy
|
||||
const jsFiles = await fs.readdir(srcJsDir);
|
||||
const cliJsDistDir = path.join(cliDistAssetsDir, 'js');
|
||||
const cssJsDistDir = path.join(cssDistDir, 'js');
|
||||
await ensureDir(cliJsDistDir);
|
||||
await ensureDir(cssJsDistDir);
|
||||
console.log('Copying and minifying JS files...');
|
||||
|
||||
@@ -112,20 +129,17 @@ async function build() {
|
||||
const baseName = path.basename(jsFile, '.js');
|
||||
const minifiedFileName = `${baseName}.min.js`;
|
||||
|
||||
// Copy original file to both destinations
|
||||
await fs.copyFile(srcFile, path.join(cliJsDistDir, jsFile));
|
||||
// Copy original file to destination
|
||||
await fs.copyFile(srcFile, path.join(cssJsDistDir, jsFile));
|
||||
|
||||
// Create and copy minified file to both destinations
|
||||
const cliDestMinFile = path.join(cliJsDistDir, minifiedFileName);
|
||||
await execPromise(`npx terser ${srcFile} -o ${cliDestMinFile} --compress --mangle`);
|
||||
await fs.copyFile(cliDestMinFile, path.join(cssJsDistDir, minifiedFileName));
|
||||
// Create minified file
|
||||
await execPromise(`npx terser ${srcFile} -o ${path.join(cssJsDistDir, minifiedFileName)} --compress --mangle`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create combined component files
|
||||
console.log('Creating combined component files...');
|
||||
const componentsToCombine = ['basecoat.js', 'accordion.js', 'command.js', 'combobox.js', 'dropdown-menu.js', 'popover.js', 'range.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
|
||||
const componentsToCombine = ['basecoat.js', 'accordion.js', 'command.js', 'combobox.js', 'drawer.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
|
||||
@@ -133,25 +147,18 @@ async function build() {
|
||||
for (const p of componentPaths) {
|
||||
combinedContent += await fs.readFile(p, 'utf-8') + '\n';
|
||||
}
|
||||
const allJsPath = path.join(cliJsDistDir, 'all.js');
|
||||
const allJsPath = path.join(cssJsDistDir, 'all.js');
|
||||
await fs.writeFile(allJsPath, combinedContent);
|
||||
await fs.copyFile(allJsPath, path.join(cssJsDistDir, 'all.js'));
|
||||
|
||||
// Create minified bundle
|
||||
const allMinJsPath = path.join(cliJsDistDir, 'all.min.js');
|
||||
const allMinJsPath = path.join(cssJsDistDir, 'all.min.js');
|
||||
await execPromise(`npx terser ${componentPaths.join(' ')} -o ${allMinJsPath} --compress --mangle`);
|
||||
await fs.copyFile(allMinJsPath, path.join(cssJsDistDir, 'all.min.js'));
|
||||
|
||||
console.log(`Copied and minified JS to ${cliJsDistDir} and ${cssJsDistDir}`);
|
||||
|
||||
await copyDirRecursive(srcNunjucksDir, path.join(cliDistAssetsDir, 'nunjucks'));
|
||||
console.log(`Copied Nunjucks assets to ${path.join(cliDistAssetsDir, 'nunjucks')}`);
|
||||
await copyDirRecursive(srcJinjaDir, path.join(cliDistAssetsDir, 'jinja'));
|
||||
console.log(`Copied Jinja assets to ${path.join(cliDistAssetsDir, 'jinja')}`);
|
||||
console.log(`Copied and minified JS to ${cssJsDistDir}`);
|
||||
|
||||
// Build CSS package
|
||||
console.log('Building CSS package...');
|
||||
await ensureDir(cssDistDir); // Ensure dist dir exists for css package
|
||||
await ensureDir(cssDistDir);
|
||||
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'));
|
||||
@@ -179,6 +186,24 @@ async function build() {
|
||||
await copyDirRecursive(srcCssCompatDir, cssCompatDistDir);
|
||||
console.log(`Copied split CSS folders to ${cssDistDir}`);
|
||||
|
||||
// Create uncompiled single-file bundles for Tailwind users without a package manager.
|
||||
const { version } = JSON.parse(await fs.readFile(path.join(projectRoot, 'package.json'), 'utf-8'));
|
||||
for (const style of styles) {
|
||||
await writeCssBundle(
|
||||
path.join(cssDistDir, `basecoat-${style}.css`),
|
||||
path.join(cssDistDir, `basecoat-${style}.bundle.css`),
|
||||
style,
|
||||
version,
|
||||
);
|
||||
}
|
||||
await writeCssBundle(path.join(cssDistDir, 'basecoat.css'), path.join(cssDistDir, 'basecoat.bundle.css'), 'vega', version);
|
||||
await writeCssBundle(path.join(cssDistDir, 'basecoat-base.css'), path.join(cssDistDir, 'basecoat-base.bundle.css'), 'base (no style pack)', version);
|
||||
console.log(`Generated single-file CSS bundles in ${cssDistDir}`);
|
||||
|
||||
await copyDirRecursive(srcNunjucksDir, path.join(cssTemplatesDir, 'nunjucks'));
|
||||
await copyDirRecursive(srcJinjaDir, path.join(cssTemplatesDir, 'jinja'));
|
||||
console.log(`Copied template assets to ${cssTemplatesDir}`);
|
||||
|
||||
// Create Tailwind CSS builds for the CSS package.
|
||||
const cdnEntries = ['basecoat.cdn.css', 'basecoat-base.cdn.css', 'basecoat-compat.cdn.css', ...styles.map((style) => `basecoat-${style}.cdn.css`)];
|
||||
for (const entry of cdnEntries) {
|
||||
|
||||
@@ -19,6 +19,7 @@ const componentOrder = [
|
||||
'command',
|
||||
'combobox',
|
||||
'dialog',
|
||||
'drawer',
|
||||
'dropdown-menu',
|
||||
'empty',
|
||||
'field',
|
||||
@@ -34,6 +35,7 @@ const componentOrder = [
|
||||
'range',
|
||||
'select',
|
||||
'sidebar',
|
||||
'scrollbar',
|
||||
'skeleton',
|
||||
'switch',
|
||||
'table',
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import reallySimpleDocs from 'reallysimpledocs/astro';
|
||||
|
||||
const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
||||
|
||||
const includeInSitemap = (page) => {
|
||||
const { pathname } = new URL(page);
|
||||
|
||||
return pathname === '/' || (
|
||||
pathname.endsWith('/') &&
|
||||
!pathname.startsWith('/fragments/')
|
||||
);
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
site: process.env.SITE_URL || 'https://basecoatui.com',
|
||||
vite: {
|
||||
@@ -13,11 +26,12 @@ export default defineConfig({
|
||||
},
|
||||
integrations: [
|
||||
reallySimpleDocs({
|
||||
docsDir: './docs/src',
|
||||
docsDir: './src/docs',
|
||||
routeBase: '/',
|
||||
style: 'vega',
|
||||
css: false,
|
||||
bodyAttrs: {
|
||||
class: 'antialiased',
|
||||
'hx-boost': 'true',
|
||||
'hx-target': '#content',
|
||||
'hx-select': '#content',
|
||||
@@ -25,13 +39,13 @@ export default defineConfig({
|
||||
'hx-push-url': 'true',
|
||||
},
|
||||
components: {
|
||||
Head: './docs/src/site/StyleHead.astro',
|
||||
SidebarHeader: './docs/src/site/SidebarHeader.astro',
|
||||
ContentHeader: './docs/src/site/ContentHeader.astro',
|
||||
Head: './src/components/StyleHead.astro',
|
||||
SidebarHeader: './src/components/SidebarHeader.astro',
|
||||
ContentHeader: './src/components/ContentHeader.astro',
|
||||
},
|
||||
site: {
|
||||
title: 'Basecoat',
|
||||
subtitle: 'v1.0.0-beta.4',
|
||||
subtitle: `v${packageJson.version}`,
|
||||
description: 'A components library built with Tailwind CSS that works with any web stack.',
|
||||
url: process.env.SITE_URL || 'https://basecoatui.com',
|
||||
keywords: ['components', 'component library', 'component system', 'UI', 'UI kit', 'shadcn', 'shadcn/ui', 'Tailwind CSS', 'Tailwind', 'CSS', 'HTML', 'Jinja', 'Nunjucks', 'JS', 'JavaScript', 'vanilla JS', 'vanilla JavaScript'],
|
||||
@@ -39,15 +53,16 @@ export default defineConfig({
|
||||
name: 'Ronan Berder',
|
||||
x: '@hunvreus',
|
||||
},
|
||||
assets: {
|
||||
favicon: 'favicon.svg',
|
||||
appleTouchIcon: 'apple-touch-icon.png',
|
||||
socialImage: 'social.png',
|
||||
},
|
||||
favicon: 'favicon.svg',
|
||||
appleTouchIcon: 'apple-touch-icon.png',
|
||||
socialImage: 'social.png',
|
||||
logo: {
|
||||
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>',
|
||||
},
|
||||
},
|
||||
}),
|
||||
sitemap({
|
||||
filter: includeInSitemap,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "astro build",
|
||||
"dev": "astro dev",
|
||||
"preview": "astro preview",
|
||||
"workers:dev": "npm run build && wrangler dev",
|
||||
"workers:deploy": "npm run build && wrangler deploy",
|
||||
"workers:preview": "npm run build && wrangler versions upload"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/sitemap": "^3.7.3",
|
||||
"basecoat-css": "file:..",
|
||||
"htmx.org": "^2.0.10",
|
||||
"lucide-static": "^1.17.0",
|
||||
"nunjucks": "^3.2.4",
|
||||
"reallysimpledocs": "^1.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^6.4.6",
|
||||
"wrangler": "^4.50.0"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 510 B After Width: | Height: | Size: 510 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 499 KiB |
|
After Width: | Height: | Size: 101 KiB |
@@ -1,12 +1,12 @@
|
||||
---
|
||||
import vegaUrl from "../assets/styles/style-vega.css?url";
|
||||
import novaUrl from "../assets/styles/style-nova.css?url";
|
||||
import maiaUrl from "../assets/styles/style-maia.css?url";
|
||||
import lyraUrl from "../assets/styles/style-lyra.css?url";
|
||||
import miraUrl from "../assets/styles/style-mira.css?url";
|
||||
import lumaUrl from "../assets/styles/style-luma.css?url";
|
||||
import seraUrl from "../assets/styles/style-sera.css?url";
|
||||
import rheaUrl from "../assets/styles/style-rhea.css?url";
|
||||
import vegaUrl from "../docs/assets/styles/style-vega.css?url";
|
||||
import novaUrl from "../docs/assets/styles/style-nova.css?url";
|
||||
import maiaUrl from "../docs/assets/styles/style-maia.css?url";
|
||||
import lyraUrl from "../docs/assets/styles/style-lyra.css?url";
|
||||
import miraUrl from "../docs/assets/styles/style-mira.css?url";
|
||||
import lumaUrl from "../docs/assets/styles/style-luma.css?url";
|
||||
import seraUrl from "../docs/assets/styles/style-sera.css?url";
|
||||
import rheaUrl from "../docs/assets/styles/style-rhea.css?url";
|
||||
|
||||
const styleUrls = {
|
||||
vega: vegaUrl,
|
||||
@@ -47,7 +47,7 @@ const styleUrls = {
|
||||
};
|
||||
const getStyleHref = (style) => {
|
||||
const href = styleUrls[style];
|
||||
return href?.startsWith("/docs/src/assets/styles/") ? `${href}?direct` : href;
|
||||
return href?.startsWith("/src/docs/assets/styles/") ? `${href}?direct` : href;
|
||||
};
|
||||
|
||||
const applyStyle = (style) => {
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
import vegaUrl from "../docs/assets/styles/style-vega.css?url";
|
||||
import novaUrl from "../docs/assets/styles/style-nova.css?url";
|
||||
import maiaUrl from "../docs/assets/styles/style-maia.css?url";
|
||||
import lyraUrl from "../docs/assets/styles/style-lyra.css?url";
|
||||
import miraUrl from "../docs/assets/styles/style-mira.css?url";
|
||||
import lumaUrl from "../docs/assets/styles/style-luma.css?url";
|
||||
import seraUrl from "../docs/assets/styles/style-sera.css?url";
|
||||
import rheaUrl from "../docs/assets/styles/style-rhea.css?url";
|
||||
import demoInteractionsUrl from "../scripts/demo-interactions.js?url";
|
||||
import htmxUrl from "htmx.org/dist/htmx.min.js?url";
|
||||
|
||||
const styleUrls = {
|
||||
vega: vegaUrl,
|
||||
nova: novaUrl,
|
||||
maia: maiaUrl,
|
||||
lyra: lyraUrl,
|
||||
mira: miraUrl,
|
||||
luma: lumaUrl,
|
||||
sera: seraUrl,
|
||||
rhea: rheaUrl,
|
||||
};
|
||||
---
|
||||
|
||||
<style is:inline>
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-400-normal.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-500-normal.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-600-normal.woff2") format("woff2");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Sans";
|
||||
src: url("/assets/fonts/geist/geist-sans-latin-700-normal.woff2") format("woff2");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-400-normal.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-500-normal.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-600-normal.woff2") format("woff2");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
src: url("/assets/fonts/geist/geist-mono-latin-700-normal.woff2") format("woff2");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style id="docs-style-gate" is:inline>
|
||||
html[data-style-pending] body {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline define:vars={{ styleUrls }}>
|
||||
(() => {
|
||||
const styleGateId = "docs-style-gate";
|
||||
const styleLinkId = "docs-style-variant";
|
||||
const clearStylePending = () => {
|
||||
document.documentElement.removeAttribute("data-style-pending");
|
||||
document.getElementById(styleGateId)?.remove();
|
||||
};
|
||||
const getStoredStyle = () => {
|
||||
try {
|
||||
return localStorage.getItem("styleVariant") || "vega";
|
||||
} catch (_) {
|
||||
return "vega";
|
||||
}
|
||||
};
|
||||
const getStyleHref = (style) => {
|
||||
const href = styleUrls[style];
|
||||
return href?.startsWith("/src/docs/assets/styles/") ? `${href}?direct` : href;
|
||||
};
|
||||
|
||||
const style = getStoredStyle();
|
||||
const nextStyle = Object.prototype.hasOwnProperty.call(styleUrls, style) ? style : "vega";
|
||||
const href = getStyleHref(nextStyle);
|
||||
|
||||
document.documentElement.dataset.styleVariant = nextStyle;
|
||||
|
||||
if (!href) return;
|
||||
|
||||
document.documentElement.setAttribute("data-style-pending", "");
|
||||
|
||||
const existing = document.getElementById("docs-style-variant");
|
||||
if (existing?.getAttribute("href") === href) {
|
||||
clearStylePending();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!existing && document.currentScript) {
|
||||
document.write(
|
||||
`<link id="${styleLinkId}" rel="stylesheet" href="${href}" onload="document.documentElement.removeAttribute('data-style-pending');document.getElementById('${styleGateId}')?.remove()" onerror="document.documentElement.removeAttribute('data-style-pending');document.getElementById('${styleGateId}')?.remove()">`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const link = existing || document.createElement("link");
|
||||
link.id = styleLinkId;
|
||||
link.rel = "stylesheet";
|
||||
link.addEventListener("load", clearStylePending, { once: true });
|
||||
link.addEventListener("error", clearStylePending, { once: true });
|
||||
link.href = href;
|
||||
if (!existing) document.head.appendChild(link);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const key = (href) => {
|
||||
const url = new URL(href, window.location.origin);
|
||||
return `${url.pathname.replace(/\/$/, "")}${url.search}`;
|
||||
};
|
||||
|
||||
const syncSidebar = () => {
|
||||
for (const link of document.querySelectorAll("#sidebar a[href]")) {
|
||||
const isCurrent = key(link.href) === key(window.location.href);
|
||||
if (isCurrent) {
|
||||
link.setAttribute("aria-current", "page");
|
||||
link.closest("details")?.setAttribute("open", "");
|
||||
} else {
|
||||
link.removeAttribute("aria-current");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const clearSerializedRuntimeState = (root) => {
|
||||
for (const element of root.querySelectorAll?.("*") || []) {
|
||||
delete element.dataset.basecoatComponent;
|
||||
for (const attribute of Array.from(element.attributes)) {
|
||||
if (/^data-[a-z0-9-]+-initialized$/.test(attribute.name)) {
|
||||
element.removeAttribute(attribute.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cleanHistoryItem = (event) => {
|
||||
const item = event.detail?.item;
|
||||
if (typeof item?.content !== "string") return;
|
||||
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = item.content;
|
||||
clearSerializedRuntimeState(template.content);
|
||||
item.content = template.innerHTML;
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", syncSidebar);
|
||||
document.addEventListener("htmx:historyItemCreated", cleanHistoryItem);
|
||||
document.addEventListener("htmx:historyRestore", () => {
|
||||
window.basecoat?.initAll?.({ force: true });
|
||||
syncSidebar();
|
||||
});
|
||||
document.addEventListener("htmx:afterSettle", () => {
|
||||
window.basecoat?.initAll?.();
|
||||
syncSidebar();
|
||||
});
|
||||
window.addEventListener("popstate", syncSidebar);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script src={htmxUrl} defer></script>
|
||||
<script type="module" src={demoInteractionsUrl}></script>
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../../src/css/base/base.css";
|
||||
@import "../../../../../src/css/basecoat-luma.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/luma";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../../src/css/base/base.css";
|
||||
@import "../../../../../src/css/basecoat-lyra.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/lyra";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../../src/css/base/base.css";
|
||||
@import "../../../../../src/css/basecoat-maia.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/maia";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../../src/css/base/base.css";
|
||||
@import "../../../../../src/css/basecoat-mira.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/mira";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../../src/css/base/base.css";
|
||||
@import "../../../../../src/css/basecoat-nova.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/nova";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../../src/css/base/base.css";
|
||||
@import "../../../../../src/css/basecoat-rhea.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/rhea";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../../src/css/base/base.css";
|
||||
@import "../../../../../src/css/basecoat-sera.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/sera";
|
||||
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss" source(none);
|
||||
@source "../../../**/*.{astro,html,js,md,mdx}";
|
||||
@source "../../../pages/**/*.{astro,html,js}";
|
||||
@reference "tailwindcss";
|
||||
@reference "../../../../../src/css/base/base.css";
|
||||
@import "../../../../../src/css/basecoat-vega.css";
|
||||
@import "reallysimpledocs/css";
|
||||
@import "reallysimpledocs/css/styles/vega";
|
||||
@@ -74,7 +74,7 @@ Alert Dialog uses a native `<dialog class="alert-dialog">` element. Omit the clo
|
||||
<dd>Trigger button. Basecoat uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code><dialog class="alert-dialog" id="{ DIALOG_ID }"></code></dt>
|
||||
<dd>
|
||||
Native modal dialog. Add <code>aria-labelledby</code> and <code>aria-describedby</code> when title and description are present.
|
||||
Native modal dialog. Add <code>aria-labelledby</code> and <code>aria-describedby</code> when title and description are present. Use <code>data-size="sm"</code> for the compact upstream size.
|
||||
<dl>
|
||||
<dt><code><div></code></dt>
|
||||
<dd>
|
||||
@@ -164,6 +164,27 @@ Use a destructive action button when the alert dialog confirms a destructive ope
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Small
|
||||
|
||||
Use `data-size="sm"` for a compact alert dialog. The small size keeps media, title, and description stacked and centers the action buttons in a two-column footer.
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('alert-dialog-small').showModal()" class="btn" data-variant="outline">Invite Members</button>
|
||||
<dialog id="alert-dialog-small" class="alert-dialog" data-size="sm" aria-labelledby="alert-dialog-small-title" aria-describedby="alert-dialog-small-description">
|
||||
<div>
|
||||
<header>
|
||||
<figure><svg class="lucide lucide-users" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><path d="M16 3.128a4 4 0 0 1 0 7.744" /><path d="M22 21v-2a4 4 0 0 0-3-3.87" /><circle cx="9" cy="7" r="4" /></svg></figure>
|
||||
<h2 id="alert-dialog-small-title">Invite team members?</h2>
|
||||
<p id="alert-dialog-small-description">Send an invitation link to your selected teammates.</p>
|
||||
</header>
|
||||
<footer>
|
||||
<button class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
<button class="btn" onclick="this.closest('dialog').close()">Invite</button>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Alert Dialog supports document direction. Set `dir="rtl"` on the dialog or a parent element.
|
||||
@@ -44,7 +44,7 @@ Use `alert` for default styling. Add `data-variant="destructive"` for error stat
|
||||
```html
|
||||
<div class="alert"><svg class="lucide lucide-info" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>
|
||||
<h2>Heads up!</h2>
|
||||
<section>You can add components and dependencies to your app using the cli.</section>
|
||||
<section>You can add components and dependencies to your app.</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" data-variant="outline">Enable</button>
|
||||
</footer>
|
||||
@@ -1,11 +1,11 @@
|
||||
import basecoatRuntimeSource from "../../../src/js/basecoat.js?raw"
|
||||
import chartRuntimeSource from "../../../src/js/chart.js?raw"
|
||||
import chartExamplesSource from "../site/chart-examples.js?raw"
|
||||
import basecoatRuntimeSource from "../../../../src/js/basecoat.js?raw"
|
||||
import chartRuntimeSource from "../../../../src/js/chart.js?raw"
|
||||
import chartExamplesSource from "../../scripts/chart-examples.js?raw"
|
||||
|
||||
# Chart
|
||||
|
||||
<Callout type="warning" title="Alpha API" icon="triangle-alert">
|
||||
Chart support is alpha. The JavaScript API, generated markup, and option names may change before the stable 1.0 release.
|
||||
<Callout type="warning" title="Beta API" icon="triangle-alert">
|
||||
Chart support is beta. The JavaScript API, generated markup, and option names may change before the stable 1.0 release.
|
||||
</Callout>
|
||||
|
||||
Use Chart.js with Basecoat defaults for themed colors, external tooltips, and optional generated legends.
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#combobox" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#combobox" }}>
|
||||
This component ships a `combobox()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Command
|
||||
|
||||
<Preview class="w-full max-w-sm">
|
||||
<div id="demo-command-standalone" class="command rounded-lg border" aria-label="Command menu">
|
||||
<div id="demo-command-standalone" class="command border" aria-label="Command menu">
|
||||
<header><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="demo-command-standalone-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="demo-command-standalone-menu" />
|
||||
</header>
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#command" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#command" }}>
|
||||
This component ships `command()` and `command_dialog()` macros for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -197,7 +197,7 @@ A simple command menu in a dialog.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-basic').showModal()">Open Menu</button>
|
||||
<dialog id="command-basic" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="command-basic" class="command-dialog" aria-label="Command menu" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div class="command">
|
||||
<header><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-basic-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-basic-menu" />
|
||||
@@ -218,7 +218,7 @@ A simple command menu in a dialog.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-shortcuts-dialog').showModal()">Open Menu</button>
|
||||
<dialog id="command-shortcuts-dialog" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="command-shortcuts-dialog" class="command-dialog" aria-label="Command menu" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div class="command">
|
||||
<header><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-shortcuts-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-shortcuts-menu" />
|
||||
@@ -248,7 +248,7 @@ A simple command menu in a dialog.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-groups-dialog').showModal()">Open Menu</button>
|
||||
<dialog id="command-groups-dialog" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="command-groups-dialog" class="command-dialog" aria-label="Command menu" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div class="command">
|
||||
<header><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-groups-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-groups-menu" />
|
||||
@@ -291,7 +291,7 @@ A simple command menu in a dialog.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('command-scrollable-dialog').showModal()">Open Menu</button>
|
||||
<dialog id="command-scrollable-dialog" class="command-dialog" aria-label="Command menu" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="command-scrollable-dialog" class="command-dialog" aria-label="Command menu" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div class="command">
|
||||
<header><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-icon lucide-search"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<input type="text" id="command-scrollable-input" placeholder="Type a command or search..." autocomplete="off" autocorrect="off" spellcheck="false" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-controls="command-scrollable-menu" />
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn" data-variant="outline">Open Dialog</button>
|
||||
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div class="sm:max-w-sm">
|
||||
<header>
|
||||
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dialog" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#dialog" }}>
|
||||
This component ships a `dialog()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -63,7 +63,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
|
||||
|
||||
```html
|
||||
<button type="button" onclick="document.getElementById('demo-dialog-edit-profile').showModal()" class="btn" data-variant="outline">Open Dialog</button>
|
||||
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="demo-dialog-edit-profile" class="dialog" aria-labelledby="demo-dialog-edit-profile-title" aria-describedby="demo-dialog-edit-profile-description" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div class="sm:max-w-sm">
|
||||
<header>
|
||||
<h2 id="demo-dialog-edit-profile-title">Edit profile</h2>
|
||||
@@ -134,7 +134,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-custom-close').showModal()" class="btn" data-variant="outline">Share</button>
|
||||
<dialog id="dialog-custom-close" class="dialog" aria-labelledby="dialog-custom-close-title" aria-describedby="dialog-custom-close-description" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="dialog-custom-close" class="dialog" aria-labelledby="dialog-custom-close-title" aria-describedby="dialog-custom-close-description" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div class="sm:max-w-md">
|
||||
<header>
|
||||
<h2 id="dialog-custom-close-title">Share link</h2>
|
||||
@@ -157,7 +157,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-no-close').showModal()" class="btn" data-variant="outline">No Close Button</button>
|
||||
<dialog id="dialog-no-close" class="dialog" aria-labelledby="dialog-no-close-title" aria-describedby="dialog-no-close-description" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="dialog-no-close" class="dialog" aria-labelledby="dialog-no-close-title" aria-describedby="dialog-no-close-description" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-no-close-title">No Close Button</h2>
|
||||
@@ -171,7 +171,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-sticky-footer').showModal()" class="btn" data-variant="outline">Sticky Footer</button>
|
||||
<dialog id="dialog-sticky-footer" class="dialog" aria-labelledby="dialog-sticky-footer-title" aria-describedby="dialog-sticky-footer-description" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="dialog-sticky-footer" class="dialog" aria-labelledby="dialog-sticky-footer-title" aria-describedby="dialog-sticky-footer-description" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-sticky-footer-title">Sticky Footer</h2>
|
||||
@@ -199,7 +199,7 @@ Basecoat uses the native `<dialog>` element and `showModal()`. This differs from
|
||||
|
||||
<Preview>
|
||||
<button type="button" onclick="document.getElementById('dialog-scrollable').showModal()" class="btn" data-variant="outline">Scrollable Content</button>
|
||||
<dialog id="dialog-scrollable" class="dialog" aria-labelledby="dialog-scrollable-title" aria-describedby="dialog-scrollable-description" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="dialog-scrollable" class="dialog" aria-labelledby="dialog-scrollable-title" aria-describedby="dialog-scrollable-description" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div>
|
||||
<header>
|
||||
<h2 id="dialog-scrollable-title">Scrollable Content</h2>
|
||||
@@ -229,7 +229,7 @@ Dialog positioning and close button placement use logical properties. Set `dir="
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<button type="button" onclick="document.getElementById('dialog-rtl').showModal()" class="btn" data-variant="outline">فتح الحوار</button>
|
||||
<dialog id="dialog-rtl" class="dialog" aria-labelledby="dialog-rtl-title" aria-describedby="dialog-rtl-description" onclick="if (event.target === this) this.close()">
|
||||
<dialog id="dialog-rtl" class="dialog" aria-labelledby="dialog-rtl-title" aria-describedby="dialog-rtl-description" onpointerdown="this.dataset.backdropPress = event.target === this" onclick="if (event.target === this && this.dataset.backdropPress === 'true') this.close()">
|
||||
<div class="sm:max-w-sm">
|
||||
<header>
|
||||
<h2 id="dialog-rtl-title">تعديل الملف الشخصي</h2>
|
||||
@@ -0,0 +1,312 @@
|
||||
# Drawer
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('demo-drawer').showModal()">Open Drawer</button>
|
||||
<dialog id="demo-drawer" class="drawer" aria-labelledby="demo-drawer-title" aria-describedby="demo-drawer-description">
|
||||
<article>
|
||||
<header class="mx-auto w-full max-w-sm">
|
||||
<h2 id="demo-drawer-title">Move Goal</h2>
|
||||
<p id="demo-drawer-description">Set your daily activity goal.</p>
|
||||
</header>
|
||||
<section class="px-4">
|
||||
<div class="mx-auto w-full max-w-sm">
|
||||
<div class="flex items-center justify-center gap-6">
|
||||
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Decrease"><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" aria-hidden="true"><path d="M5 12h14" /></svg></button>
|
||||
<div class="text-center">
|
||||
<div class="text-7xl font-bold">350</div>
|
||||
<div class="text-muted-foreground text-xs uppercase">Calories/day</div>
|
||||
</div>
|
||||
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Increase"><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" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="mx-auto w-full max-w-sm">
|
||||
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
<Step title="Include CSS">
|
||||
|
||||
Import Tailwind and one full Basecoat style bundle.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/vega.css";
|
||||
```
|
||||
|
||||
Or import only the base CSS, Drawer component CSS, and one style pack.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base.css";
|
||||
@import "basecoat-css/components/drawer.css";
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Include JavaScript">
|
||||
|
||||
Copy or serve the full Basecoat JavaScript bundle.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/all.min.js" defer></script>
|
||||
```
|
||||
|
||||
Or copy or serve the Basecoat runtime and Drawer script.
|
||||
|
||||
```html
|
||||
<script src="/assets/js/basecoat.min.js" defer></script>
|
||||
<script src="/assets/js/drawer.min.js" defer></script>
|
||||
```
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
</Step>
|
||||
<Step title="Add your drawer HTML">
|
||||
|
||||
Basecoat maps shadcn/ui's Vaul Drawer to a native `<dialog>` element. This keeps modality, focus handling, and inert page content in the browser platform. The Drawer script adds safe backdrop-click close and lets the exit animation finish before calling the native close. Drag gestures, snap points, and the drag handle are intentionally not included.
|
||||
|
||||
```html
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('demo-drawer').showModal()">Open Drawer</button>
|
||||
<dialog id="demo-drawer" class="drawer" aria-labelledby="demo-drawer-title" aria-describedby="demo-drawer-description">
|
||||
<article>
|
||||
<header class="mx-auto w-full max-w-sm">
|
||||
<h2 id="demo-drawer-title">Move Goal</h2>
|
||||
<p id="demo-drawer-description">Set your daily activity goal.</p>
|
||||
</header>
|
||||
<section class="px-4">
|
||||
<div class="mx-auto w-full max-w-sm">
|
||||
<div class="flex items-center justify-center gap-6">
|
||||
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Decrease">...</button>
|
||||
<div class="text-center">
|
||||
<div class="text-7xl font-bold">350</div>
|
||||
<div class="text-muted-foreground text-xs uppercase">Calories/day</div>
|
||||
</div>
|
||||
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="Increase">...</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="mx-auto w-full max-w-sm">
|
||||
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
```
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### HTML structure
|
||||
|
||||
<Callout type="warning" title="No drag handle" icon="triangle-alert">
|
||||
Basecoat does not implement Vaul's drag-to-close gesture, snap points, or drag handle. Drawers close with outside click, Escape, or explicit close controls.
|
||||
</Callout>
|
||||
|
||||
<dl>
|
||||
<dt><code><button type="button" onclick="drawer.showModal()"></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Trigger button. Basecoat intentionally uses the native <code>HTMLDialogElement.showModal()</code> method.</dd>
|
||||
<dt><code><dialog class="drawer" data-side="bottom|top|left|right"></code></dt>
|
||||
<dd>
|
||||
Native modal drawer. Omit <code>data-side</code> for the default bottom drawer. Add <code>aria-labelledby</code> and <code>aria-describedby</code> when title and description are present. JavaScript adds the <code>data-drawer-initialized</code> flag and manages animated close behavior.
|
||||
<dl>
|
||||
<dt><code><article></code></dt>
|
||||
<dd>
|
||||
Drawer content surface.
|
||||
<dl>
|
||||
<dt><code><header></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Drawer header with a title and optional description.</dd>
|
||||
<dt><code><section></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Scrollable body/content area.</dd>
|
||||
<dt><code><footer></code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Action area pinned after the drawer body.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Examples
|
||||
|
||||
### Sides
|
||||
|
||||
Use `data-side` to set the viewport side. Available values are `top`, `right`, `bottom`, and `left`.
|
||||
|
||||
<Preview>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-top').showModal()">Top</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-right').showModal()">Right</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-bottom').showModal()">Bottom</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-side-left').showModal()">Left</button>
|
||||
</div>
|
||||
<dialog id="drawer-side-top" class="drawer" data-side="top" aria-labelledby="drawer-side-top-title" aria-describedby="drawer-side-top-description">
|
||||
<article class="max-h-[50vh]">
|
||||
<header>
|
||||
<h2 id="drawer-side-top-title">Move Goal</h2>
|
||||
<p id="drawer-side-top-description">Set your daily activity goal.</p>
|
||||
</header>
|
||||
<section class="px-4">
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
<dialog id="drawer-side-right" class="drawer" data-side="right" aria-labelledby="drawer-side-right-title" aria-describedby="drawer-side-right-description">
|
||||
<article>
|
||||
<header>
|
||||
<h2 id="drawer-side-right-title">Move Goal</h2>
|
||||
<p id="drawer-side-right-description">Set your daily activity goal.</p>
|
||||
</header>
|
||||
<section class="px-4">
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
<dialog id="drawer-side-bottom" class="drawer" data-side="bottom" aria-labelledby="drawer-side-bottom-title" aria-describedby="drawer-side-bottom-description">
|
||||
<article class="max-h-[50vh]">
|
||||
<header>
|
||||
<h2 id="drawer-side-bottom-title">Move Goal</h2>
|
||||
<p id="drawer-side-bottom-description">Set your daily activity goal.</p>
|
||||
</header>
|
||||
<section class="px-4">
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
<dialog id="drawer-side-left" class="drawer" data-side="left" aria-labelledby="drawer-side-left-title" aria-describedby="drawer-side-left-description">
|
||||
<article>
|
||||
<header>
|
||||
<h2 id="drawer-side-left-title">Move Goal</h2>
|
||||
<p id="drawer-side-left-description">Set your daily activity goal.</p>
|
||||
</header>
|
||||
<section class="px-4">
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### Scrollable content
|
||||
|
||||
Keep actions visible while the drawer body scrolls.
|
||||
|
||||
<Preview>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-scrollable').showModal()">Scrollable Content</button>
|
||||
<dialog id="drawer-scrollable" class="drawer" data-side="right" aria-labelledby="drawer-scrollable-title" aria-describedby="drawer-scrollable-description">
|
||||
<article>
|
||||
<header>
|
||||
<h2 id="drawer-scrollable-title">Move Goal</h2>
|
||||
<p id="drawer-scrollable-description">Set your daily activity goal.</p>
|
||||
</header>
|
||||
<section class="px-4">
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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 class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p class="mb-4 leading-normal">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 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>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn" onclick="this.closest('dialog').close()">Submit</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">Cancel</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
</Preview>
|
||||
|
||||
### RTL
|
||||
|
||||
Drawer sides are physical viewport sides. Content spacing and text alignment follow document direction.
|
||||
|
||||
<Preview>
|
||||
<div dir="rtl">
|
||||
<button type="button" class="btn" data-variant="outline" onclick="document.getElementById('drawer-rtl').showModal()">فتح الدرج</button>
|
||||
<dialog id="drawer-rtl" class="drawer" aria-labelledby="drawer-rtl-title" aria-describedby="drawer-rtl-description">
|
||||
<article>
|
||||
<header class="mx-auto w-full max-w-sm">
|
||||
<h2 id="drawer-rtl-title">نقل الهدف</h2>
|
||||
<p id="drawer-rtl-description">حدد هدف نشاطك اليومي.</p>
|
||||
</header>
|
||||
<section class="px-4">
|
||||
<div class="mx-auto w-full max-w-sm">
|
||||
<div class="flex items-center justify-center gap-6">
|
||||
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="تقليل"><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" aria-hidden="true"><path d="M5 12h14" /></svg></button>
|
||||
<div class="text-center">
|
||||
<div class="text-7xl font-bold">٣٥٠</div>
|
||||
<div class="text-muted-foreground text-xs uppercase">سعرات حرارية/يوم</div>
|
||||
</div>
|
||||
<button type="button" class="btn rounded-full" data-variant="outline" data-size="icon-sm" aria-label="زيادة"><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" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="mx-auto w-full max-w-sm">
|
||||
<button type="button" class="btn" onclick="this.closest('dialog').close()">إرسال</button>
|
||||
<button type="button" class="btn" data-variant="outline" onclick="this.closest('dialog').close()">إلغاء</button>
|
||||
</footer>
|
||||
</article>
|
||||
</dialog>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#dropdown_menu" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#dropdown_menu" }}>
|
||||
This component ships a `dropdown_menu()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#popover" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#popover" }}>
|
||||
This component ships a `popover()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
# Scroll Area
|
||||
|
||||
<Preview class="w-full max-w-42">
|
||||
<div class="scrollbar h-72 overflow-y-auto rounded-md border p-4">
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm leading-none font-medium">Tags</h4>
|
||||
<div class="text-sm">
|
||||
<div class="border-b py-2">v1.2.0-beta.50</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.49</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.48</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.47</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.46</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.45</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.44</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.43</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.42</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.41</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.40</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.39</div>
|
||||
<div class="py-2">v1.2.0-beta.38</div>
|
||||
<div class="card h-72 p-0">
|
||||
<div class="scrollbar h-full overflow-y-auto p-4">
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm leading-none font-medium">Tags</h4>
|
||||
<div class="text-sm">
|
||||
<div class="border-b py-2">v1.2.0-beta.50</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.49</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.48</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.47</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.46</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.45</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.44</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.43</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.42</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.41</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.40</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.39</div>
|
||||
<div class="py-2">v1.2.0-beta.38</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,7 +45,7 @@ Or import only the base CSS and one style pack.
|
||||
@import "basecoat-css/styles/vega.css";
|
||||
```
|
||||
|
||||
Scroll Area uses Basecoat base scrollbar utilities and has no dedicated component CSS file.
|
||||
Scroll Area uses Basecoat scrollbar component classes.
|
||||
|
||||
Using CDN or bundler imports? See the [Installation page](/installation).
|
||||
|
||||
@@ -63,29 +65,33 @@ Use `scrollbar` or `scrollbar-sm` on an element that already scrolls with `overf
|
||||
|
||||
Use `scrollbar` or `scrollbar-sm` on an element that already scrolls with `overflow-auto`, `overflow-y-auto`, or `overflow-x-auto`.
|
||||
|
||||
The utility augments native scrolling for cross-browser styling. It does not wrap content, replace browser scroll behavior, or require JavaScript.
|
||||
The component class augments native scrolling for cross-browser styling. It does not wrap content, replace browser scroll behavior, or require JavaScript.
|
||||
|
||||
## Examples
|
||||
|
||||
These examples use Card as the framed surface so border and radius come from the active style pack.
|
||||
|
||||
### Horizontal
|
||||
|
||||
Use `scrollbar` with `overflow-x-auto` for horizontal scrolling.
|
||||
|
||||
<Preview>
|
||||
<div class="scrollbar w-full max-w-sm overflow-x-auto rounded-md border">
|
||||
<div class="flex w-max gap-4 p-4">
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Ornella Binni</figcaption>
|
||||
</figure>
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Tom Byrom</figcaption>
|
||||
</figure>
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Vladimir Malyavko</figcaption>
|
||||
</figure>
|
||||
<div class="card w-full max-w-sm p-0">
|
||||
<div class="scrollbar overflow-x-auto">
|
||||
<div class="flex w-max gap-4 p-4">
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Ornella Binni</figcaption>
|
||||
</figure>
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Tom Byrom</figcaption>
|
||||
</figure>
|
||||
<figure class="shrink-0">
|
||||
<div class="aspect-[3/4] w-40 rounded-md bg-muted"></div>
|
||||
<figcaption class="text-muted-foreground pt-2 text-xs">Photo by Vladimir Malyavko</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Preview>
|
||||
@@ -95,7 +101,7 @@ Use `scrollbar` with `overflow-x-auto` for horizontal scrolling.
|
||||
Use `scrollbar-sm` for compact scrollable regions like menus, listboxes, sidebars, and command results.
|
||||
|
||||
<Preview class="w-full max-w-64">
|
||||
<div class="w-full max-w-sm rounded-md border p-1">
|
||||
<div class="card w-full max-w-sm p-1">
|
||||
<div class="px-2 py-1.5 text-xs font-medium text-muted-foreground">Recent files</div>
|
||||
<div class="scrollbar-sm max-h-48 overflow-y-auto">
|
||||
<button type="button" class="flex w-full items-center justify-between rounded-sm px-2 py-1.5 text-sm hover:bg-accent">
|
||||
@@ -135,23 +141,25 @@ Use `scrollbar-sm` for compact scrollable regions like menus, listboxes, sidebar
|
||||
Set `dir="rtl"` on the scrollable element or one of its ancestors to use right-to-left layout.
|
||||
|
||||
<Preview class="w-full max-w-42">
|
||||
<div dir="rtl" class="scrollbar h-72 overflow-y-auto rounded-md border p-4">
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm leading-none font-medium">العلامات</h4>
|
||||
<div class="text-sm">
|
||||
<div class="border-b py-2">v1.2.0-beta.50</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.49</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.48</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.47</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.46</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.45</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.44</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.43</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.42</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.41</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.40</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.39</div>
|
||||
<div class="py-2">v1.2.0-beta.38</div>
|
||||
<div class="card h-72 p-0">
|
||||
<div dir="rtl" class="scrollbar h-full overflow-y-auto p-4">
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-sm leading-none font-medium">العلامات</h4>
|
||||
<div class="text-sm">
|
||||
<div class="border-b py-2">v1.2.0-beta.50</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.49</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.48</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.47</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.46</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.45</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.44</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.43</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.42</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.41</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.40</div>
|
||||
<div class="border-b py-2">v1.2.0-beta.39</div>
|
||||
<div class="py-2">v1.2.0-beta.38</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#select" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#select" }}>
|
||||
This component ships a `select()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#sidebar" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#sidebar" }}>
|
||||
This component ships a `sidebar()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#tabs" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#tabs" }}>
|
||||
This component ships a `tabs()` macro for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
hx-get="/fragments/toast/success"
|
||||
hx-target="#toaster"
|
||||
hx-swap="beforeend"
|
||||
hx-push-url="false"
|
||||
>Toast from backend (with HTMX)</button>
|
||||
</Preview>
|
||||
|
||||
@@ -29,7 +30,7 @@
|
||||
|
||||
## Usage
|
||||
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "Read more", href: "/cli#toast" }}>
|
||||
<Callout title="Template macros available" icon="code" action={{ label: "More", href: "/templates#toast" }}>
|
||||
This component ships `toaster()` and `toast()` macros for Jinja and Nunjucks.
|
||||
</Callout>
|
||||
|
||||
@@ -163,6 +164,10 @@ Append server-rendered toast markup to the toaster, or create one from JavaScrip
|
||||
|
||||
### Config object
|
||||
|
||||
<Callout type="warning" title="Text is escaped by default" icon="triangle-alert">
|
||||
`title`, `description`, and action labels are inserted as plain text. With `html: true` they are inserted as HTML, so never pass untrusted user input with it. `onclick` is JavaScript code and must never contain user input. `icon` is always inserted as raw HTML (it is meant for SVG markup).
|
||||
</Callout>
|
||||
|
||||
<dl>
|
||||
<dt><code>duration</code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Timeout in milliseconds. Defaults to <code>3000</code>, or <code>5000</code> for error toasts. Use <code>-1</code> to keep it open.</dd>
|
||||
@@ -176,4 +181,10 @@ Append server-rendered toast markup to the toaster, or create one from JavaScrip
|
||||
<dd>Primary action. Supports <code>label</code> with either <code>href</code> or <code>onclick</code>.</dd>
|
||||
<dt><code>cancel</code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Secondary action. Supports <code>label</code> and optional <code>onclick</code>.</dd>
|
||||
<dt><code>icon</code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Custom icon markup, usually an SVG. Inserted as raw HTML. Defaults to the category icon.</dd>
|
||||
<dt><code>html</code> <span class="badge" data-variant="secondary">Optional</span></dt>
|
||||
<dd>Set to <code>true</code> to insert <code>title</code>, <code>description</code>, and action labels as HTML instead of plain text. Defaults to <code>false</code>. Only use it with trusted content.</dd>
|
||||
</dl>
|
||||
|
||||
The Jinja and Nunjucks `toast()` macros render on your server, so your template engine's autoescaping applies to `title`, `description`, and labels there when it is enabled. Nunjucks enables it by default. With Jinja, make sure your environment's `autoescape` setting covers the Basecoat template files (for example `.html.jinja`). `icon` is output with `safe`, and `onclick` is output as an attribute, so neither should contain user input.
|
||||