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

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- **Lightweight**: no runtime JS, just CSS and tiny Alpine.js scripts for the more interactive components.
|
||||
- **Easy to use**: add classes like `btn` or `input` and you're done.
|
||||
- **Framework-agnostic**: works with any backend or frontend stack.
|
||||
- **Accessible**: components follow accessibility best practices.
|
||||
- **Dark mode ready**: respects your Tailwind config.
|
||||
- **Extendable**: tweak styles with Tailwind or CSS variables.
|
||||
- **Themable**: fully compatible with shadcn/ui themes.
|
||||
- **Readable**: no class soup, just clean markup.
|
||||
- **Free and open source**: MIT licensed.
|
||||
- Semantic HTML-first components.
|
||||
- Tailwind CSS v4 source files and generated CSS bundles.
|
||||
- Small vanilla JavaScript for components that need behavior.
|
||||
- Standalone style packs: Vega, Nova, Maia, Lyra, Mira, Luma, Sera, and Rhea.
|
||||
- Compatible with shadcn/ui themes.
|
||||
- Dark mode.
|
||||
- CDN, npm, and template usage paths.
|
||||
- Nunjucks and Jinja templates.
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit [basecoatui.com](https://basecoatui.com).
|
||||
- Website: [basecoatui.com](https://basecoatui.com)
|
||||
- Installation: [basecoatui.com/installation](https://basecoatui.com/installation)
|
||||
- Customization: [basecoatui.com/customization](https://basecoatui.com/customization)
|
||||
|
||||
## Installation
|
||||
## Install
|
||||
|
||||
Visit [basecoatui.com/installation](https://basecoatui.com/installation)
|
||||
```bash
|
||||
npm install basecoat-css
|
||||
```
|
||||
|
||||
Use the default bundle:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
```
|
||||
|
||||
Use a specific style bundle:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/nova";
|
||||
```
|
||||
|
||||
Use the styleless base plus a custom style file:
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "basecoat-css/base";
|
||||
@import "./style-acme.css";
|
||||
```
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```text
|
||||
.
|
||||
├── dist/ Generated package output
|
||||
├── scripts/ Build and generation scripts
|
||||
├── 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
|
||||
|
||||
Basecoat separates structure from style:
|
||||
|
||||
- `src/css/base/base.css`: shared tokens and semantic utilities.
|
||||
- `src/css/components/*.css`: component layout, structure, accessibility selectors, and behavior hooks.
|
||||
- `src/css/styles/*.css`: style-pack visuals such as color, radius, shadow, typography, spacing, variants, and state styles.
|
||||
|
||||
Generated source entrypoints are committed for transparency and package imports:
|
||||
|
||||
- `src/css/basecoat.css`: default backward-compatible Vega bundle.
|
||||
- `src/css/basecoat-base.css`: base plus components, no style pack.
|
||||
- `src/css/basecoat-components.css`: component imports only.
|
||||
- `src/css/basecoat-<style>.css`: base plus one style pack.
|
||||
- `src/css/basecoat-<style>.cdn.css`: CDN-compatible wrapper.
|
||||
|
||||
These entrypoints are generated by `scripts/generate-css-entrypoints.js` and by the build scripts.
|
||||
|
||||
## JavaScript Lifecycle
|
||||
|
||||
- `window.basecoat.init(name)` initializes uninitialized components for one registered component.
|
||||
- `window.basecoat.initAll()` initializes all uninitialized registered components.
|
||||
- `window.basecoat.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.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies.
|
||||
npm i
|
||||
npm --prefix site i
|
||||
|
||||
# Run the ReallySimpleDocs/Astro site.
|
||||
npm run docs:dev
|
||||
|
||||
# Build package assets.
|
||||
npm run build
|
||||
|
||||
# Build the static docs site.
|
||||
npm run docs:build
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[MIT](/LICENSE.md)
|
||||
[MIT](LICENSE.md)
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
## Now
|
||||
|
||||
- [ ] Add Drawer component
|
||||
- [ ] Make utility components (e.g., Item) into proper components (i.e., .item)
|
||||
- [ ] Split CSS into components
|
||||
|
||||
## Next
|
||||
|
||||
- [ ] Make Command load dynamic items (e.g. search results from remote endpoint)
|
||||
- [ ] Make Combobox load dynamic items (e.g. search results from remote endpoint)
|
||||
- [ ] Add Toggle component
|
||||
- [ ] Add Carousel component
|
||||
- [ ] Add Sheet component
|
||||
- [ ] Add Separator component
|
||||
- [ ] Adding Menubar component
|
||||
|
||||
## Later
|
||||
|
||||
- [ ] Add Calendar component (https://flatpickr.js.org/)
|
||||
- [ ] Finish Chart component (https://www.chartjs.org/)
|
||||
@@ -1,76 +0,0 @@
|
||||
/* HTMX */
|
||||
.htmx-indicator {
|
||||
@apply invisible opacity-0 transition-discrete transition-all;
|
||||
}
|
||||
.htmx-indicator:not(.htmx-request) {
|
||||
@apply hidden;
|
||||
}
|
||||
.htmx-request,
|
||||
.htmx-request.htmx-indicator {
|
||||
@apply visible opacity-100;
|
||||
}
|
||||
|
||||
.content {
|
||||
> h2 {
|
||||
@apply mt-16 mb-6 scroll-m-22 border-b pb-4 text-xl font-semibold tracking-tight first:mt-0;
|
||||
}
|
||||
> h3 {
|
||||
@apply mt-8 mb-6 scroll-m-22 text-lg font-semibold tracking-tight;
|
||||
}
|
||||
> h4 {
|
||||
@apply my-6 scroll-m-22 font-semibold tracking-tight;
|
||||
}
|
||||
.prose {
|
||||
@apply my-6;
|
||||
|
||||
a {
|
||||
@apply font-medium underline underline-offset-4;
|
||||
}
|
||||
code:not(pre > code) {
|
||||
@apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm;
|
||||
}
|
||||
blockquote {
|
||||
@apply border-l-4 border-border pl-4;
|
||||
}
|
||||
ol {
|
||||
@apply list-decimal ml-6;
|
||||
}
|
||||
ul {
|
||||
@apply list-disc ml-6;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
li {
|
||||
@apply mt-2;
|
||||
}
|
||||
}
|
||||
b,
|
||||
strong {
|
||||
@apply font-semibold;
|
||||
}
|
||||
> p,
|
||||
> blockquote,
|
||||
> ul,
|
||||
> ol,
|
||||
> dl,
|
||||
> table,
|
||||
> pre,
|
||||
> p {
|
||||
@apply my-6;
|
||||
}
|
||||
> *:first-child {
|
||||
@apply !mt-0;
|
||||
}
|
||||
> *:last-child {
|
||||
@apply !mb-0;
|
||||
}
|
||||
}
|
||||
.step {
|
||||
counter-increment: step;
|
||||
}
|
||||
.step:before {
|
||||
@apply md:absolute w-8 h-8 md:w-9 md:h-9 bg-muted rounded-full font-mono font-medium text-center text-base inline-flex items-center justify-center -indent-px border-4 mr-2 border-background;
|
||||
@apply md:ml-[-50px] md:mt-[-4px];
|
||||
content: counter(step);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Geist+Mono&display=swap");
|
||||
|
||||
@import "tailwindcss";
|
||||
@import "../../src/css/basecoat.css";
|
||||
@import "./custom.css";
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Geist';
|
||||
--font-mono: 'Geist Mono';
|
||||
}
|
||||
|
||||
@import "./themes/claude.css";
|
||||
@import "./themes/doom-64.css";
|
||||
@import "./themes/supabase.css";
|
||||
@@ -1,95 +0,0 @@
|
||||
.theme-claude {
|
||||
&:root {
|
||||
--background: oklch(0.98 0.01 95.10);
|
||||
--foreground: oklch(0.34 0.03 95.72);
|
||||
--card: oklch(0.98 0.01 95.10);
|
||||
--card-foreground: oklch(0.19 0.00 106.59);
|
||||
--popover: oklch(1.00 0 0);
|
||||
--popover-foreground: oklch(0.27 0.02 98.94);
|
||||
--primary: oklch(0.62 0.14 39.04);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.92 0.01 92.99);
|
||||
--secondary-foreground: oklch(0.43 0.02 98.60);
|
||||
--muted: oklch(0.93 0.02 90.24);
|
||||
--muted-foreground: oklch(0.61 0.01 97.42);
|
||||
--accent: oklch(0.92 0.01 92.99);
|
||||
--accent-foreground: oklch(0.27 0.02 98.94);
|
||||
--destructive: oklch(0.19 0.00 106.59);
|
||||
--destructive-foreground: oklch(1.00 0 0);
|
||||
--border: oklch(0.88 0.01 97.36);
|
||||
--input: oklch(0.76 0.02 98.35);
|
||||
--ring: oklch(0.59 0.17 253.06);
|
||||
--chart-1: oklch(0.56 0.13 43.00);
|
||||
--chart-2: oklch(0.69 0.16 290.41);
|
||||
--chart-3: oklch(0.88 0.03 93.13);
|
||||
--chart-4: oklch(0.88 0.04 298.18);
|
||||
--chart-5: oklch(0.56 0.13 42.06);
|
||||
--sidebar: oklch(0.97 0.01 98.88);
|
||||
--sidebar-foreground: oklch(0.36 0.01 106.65);
|
||||
--sidebar-primary: oklch(0.62 0.14 39.04);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.92 0.01 92.99);
|
||||
--sidebar-accent-foreground: oklch(0.33 0 0);
|
||||
--sidebar-border: oklch(0.94 0 0);
|
||||
--sidebar-ring: oklch(0.77 0 0);
|
||||
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
--background: oklch(0.27 0.00 106.64);
|
||||
--foreground: oklch(0.81 0.01 93.01);
|
||||
--card: oklch(0.27 0.00 106.64);
|
||||
--card-foreground: oklch(0.98 0.01 95.10);
|
||||
--popover: oklch(0.31 0.00 106.60);
|
||||
--popover-foreground: oklch(0.92 0.00 106.48);
|
||||
--primary: oklch(0.67 0.13 38.76);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.98 0.01 95.10);
|
||||
--secondary-foreground: oklch(0.31 0.00 106.60);
|
||||
--muted: oklch(0.22 0.00 106.71);
|
||||
--muted-foreground: oklch(0.77 0.02 99.07);
|
||||
--accent: oklch(0.21 0.01 95.42);
|
||||
--accent-foreground: oklch(0.97 0.01 98.88);
|
||||
--destructive: oklch(0.64 0.21 25.33);
|
||||
--destructive-foreground: oklch(1.00 0 0);
|
||||
--border: oklch(0.36 0.01 106.89);
|
||||
--input: oklch(0.43 0.01 100.22);
|
||||
--ring: oklch(0.59 0.17 253.06);
|
||||
--chart-1: oklch(0.56 0.13 43.00);
|
||||
--chart-2: oklch(0.69 0.16 290.41);
|
||||
--chart-3: oklch(0.21 0.01 95.42);
|
||||
--chart-4: oklch(0.31 0.05 289.32);
|
||||
--chart-5: oklch(0.56 0.13 42.06);
|
||||
--sidebar: oklch(0.24 0.00 67.71);
|
||||
--sidebar-foreground: oklch(0.81 0.01 93.01);
|
||||
--sidebar-primary: oklch(0.33 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.17 0.00 106.62);
|
||||
--sidebar-accent-foreground: oklch(0.81 0.01 93.01);
|
||||
--sidebar-border: oklch(0.94 0 0);
|
||||
--sidebar-ring: oklch(0.77 0 0);
|
||||
--font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
.theme-doom-64 {
|
||||
&:root {
|
||||
--background: oklch(0.85 0 0);
|
||||
--foreground: oklch(0.24 0 0);
|
||||
--card: oklch(0.76 0 0);
|
||||
--card-foreground: oklch(0.24 0 0);
|
||||
--popover: oklch(0.76 0 0);
|
||||
--popover-foreground: oklch(0.24 0 0);
|
||||
--primary: oklch(0.50 0.19 27.48);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.50 0.09 126.19);
|
||||
--secondary-foreground: oklch(1.00 0 0);
|
||||
--muted: oklch(0.78 0 0);
|
||||
--muted-foreground: oklch(0.41 0 0);
|
||||
--accent: oklch(0.59 0.10 245.74);
|
||||
--accent-foreground: oklch(1.00 0 0);
|
||||
--destructive: oklch(0.71 0.20 46.46);
|
||||
--destructive-foreground: oklch(0 0 0);
|
||||
--border: oklch(0.43 0 0);
|
||||
--input: oklch(0.43 0 0);
|
||||
--ring: oklch(0.50 0.19 27.48);
|
||||
--chart-1: oklch(0.50 0.19 27.48);
|
||||
--chart-2: oklch(0.50 0.09 126.19);
|
||||
--chart-3: oklch(0.59 0.10 245.74);
|
||||
--chart-4: oklch(0.71 0.20 46.46);
|
||||
--chart-5: oklch(0.57 0.04 40.43);
|
||||
--sidebar: oklch(0.76 0 0);
|
||||
--sidebar-foreground: oklch(0.24 0 0);
|
||||
--sidebar-primary: oklch(0.50 0.19 27.48);
|
||||
--sidebar-primary-foreground: oklch(1.00 0 0);
|
||||
--sidebar-accent: oklch(0.59 0.10 245.74);
|
||||
--sidebar-accent-foreground: oklch(1.00 0 0);
|
||||
--sidebar-border: oklch(0.43 0 0);
|
||||
--sidebar-ring: oklch(0.50 0.19 27.48);
|
||||
--font-sans: "Oxanium", sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: "Source Code Pro", monospace;
|
||||
--radius: 0px;
|
||||
--shadow-2xs: 0px 2px 4px 0px hsl(0 0% 0% / 0.20);
|
||||
--shadow-xs: 0px 2px 4px 0px hsl(0 0% 0% / 0.20);
|
||||
--shadow-sm: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 1px 2px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 1px 2px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow-md: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 2px 4px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow-lg: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 4px 6px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow-xl: 0px 2px 4px 0px hsl(0 0% 0% / 0.40), 0px 8px 10px -1px hsl(0 0% 0% / 0.40);
|
||||
--shadow-2xl: 0px 2px 4px 0px hsl(0 0% 0% / 1.00);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
--background: oklch(0.22 0 0);
|
||||
--foreground: oklch(0.91 0 0);
|
||||
--card: oklch(0.29 0 0);
|
||||
--card-foreground: oklch(0.91 0 0);
|
||||
--popover: oklch(0.29 0 0);
|
||||
--popover-foreground: oklch(0.91 0 0);
|
||||
--primary: oklch(0.61 0.21 27.03);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.64 0.15 133.01);
|
||||
--secondary-foreground: oklch(0 0 0);
|
||||
--muted: oklch(0.26 0 0);
|
||||
--muted-foreground: oklch(0.71 0 0);
|
||||
--accent: oklch(0.75 0.12 244.75);
|
||||
--accent-foreground: oklch(0 0 0);
|
||||
--destructive: oklch(0.78 0.17 68.09);
|
||||
--destructive-foreground: oklch(0 0 0);
|
||||
--border: oklch(0.41 0 0);
|
||||
--input: oklch(0.41 0 0);
|
||||
--ring: oklch(0.61 0.21 27.03);
|
||||
--chart-1: oklch(0.61 0.21 27.03);
|
||||
--chart-2: oklch(0.64 0.15 133.01);
|
||||
--chart-3: oklch(0.75 0.12 244.75);
|
||||
--chart-4: oklch(0.78 0.17 68.09);
|
||||
--chart-5: oklch(0.65 0.03 40.80);
|
||||
--sidebar: oklch(0.19 0 0);
|
||||
--sidebar-foreground: oklch(0.91 0 0);
|
||||
--sidebar-primary: oklch(0.61 0.21 27.03);
|
||||
--sidebar-primary-foreground: oklch(1.00 0 0);
|
||||
--sidebar-accent: oklch(0.75 0.12 244.75);
|
||||
--sidebar-accent-foreground: oklch(0 0 0);
|
||||
--sidebar-border: oklch(0.41 0 0);
|
||||
--sidebar-ring: oklch(0.61 0.21 27.03);
|
||||
--font-sans: "Oxanium", sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: "Source Code Pro", monospace;
|
||||
--radius: 0px;
|
||||
--shadow-2xs: 0px 2px 5px 0px hsl(0 0% 0% / 0.30);
|
||||
--shadow-xs: 0px 2px 5px 0px hsl(0 0% 0% / 0.30);
|
||||
--shadow-sm: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 1px 2px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 1px 2px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow-md: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 2px 4px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow-lg: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 4px 6px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow-xl: 0px 2px 5px 0px hsl(0 0% 0% / 0.60), 0px 8px 10px -1px hsl(0 0% 0% / 0.60);
|
||||
--shadow-2xl: 0px 2px 5px 0px hsl(0 0% 0% / 1.50);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
.theme-supabase {
|
||||
&:root {
|
||||
--background: oklch(0.99 0 0);
|
||||
--foreground: oklch(0.20 0 0);
|
||||
--card: oklch(0.99 0 0);
|
||||
--card-foreground: oklch(0.20 0 0);
|
||||
--popover: oklch(0.99 0 0);
|
||||
--popover-foreground: oklch(0.44 0 0);
|
||||
--primary: oklch(0.83 0.13 160.91);
|
||||
--primary-foreground: oklch(0.26 0.01 166.46);
|
||||
--secondary: oklch(0.99 0 0);
|
||||
--secondary-foreground: oklch(0.20 0 0);
|
||||
--muted: oklch(0.95 0 0);
|
||||
--muted-foreground: oklch(0.24 0 0);
|
||||
--accent: oklch(0.95 0 0);
|
||||
--accent-foreground: oklch(0.24 0 0);
|
||||
--destructive: oklch(0.55 0.19 32.73);
|
||||
--destructive-foreground: oklch(0.99 0.00 17.21);
|
||||
--border: oklch(0.90 0 0);
|
||||
--input: oklch(0.90 0 0);
|
||||
--ring: oklch(0.83 0.13 160.91);
|
||||
--chart-1: oklch(0.83 0.13 160.91);
|
||||
--chart-2: oklch(0.62 0.19 259.81);
|
||||
--chart-3: oklch(0.61 0.22 292.72);
|
||||
--chart-4: oklch(0.77 0.16 70.08);
|
||||
--chart-5: oklch(0.70 0.15 162.48);
|
||||
--sidebar: oklch(0.99 0 0);
|
||||
--sidebar-foreground: oklch(0.55 0 0);
|
||||
--sidebar-primary: oklch(0.83 0.13 160.91);
|
||||
--sidebar-primary-foreground: oklch(0.26 0.01 166.46);
|
||||
--sidebar-accent: oklch(0.95 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.24 0 0);
|
||||
--sidebar-border: oklch(0.90 0 0);
|
||||
--sidebar-ring: oklch(0.83 0.13 160.91);
|
||||
--font-sans: Outfit, sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.43);
|
||||
--tracking-normal: 0.025em;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
--background: oklch(0.18 0 0);
|
||||
--foreground: oklch(0.93 0.01 255.51);
|
||||
--card: oklch(0.20 0 0);
|
||||
--card-foreground: oklch(0.93 0.01 255.51);
|
||||
--popover: oklch(0.26 0 0);
|
||||
--popover-foreground: oklch(0.73 0 0);
|
||||
--primary: oklch(0.44 0.10 156.76);
|
||||
--primary-foreground: oklch(0.92 0.01 167.16);
|
||||
--secondary: oklch(0.26 0 0);
|
||||
--secondary-foreground: oklch(0.99 0 0);
|
||||
--muted: oklch(0.24 0 0);
|
||||
--muted-foreground: oklch(0.71 0 0);
|
||||
--accent: oklch(0.31 0 0);
|
||||
--accent-foreground: oklch(0.99 0 0);
|
||||
--destructive: oklch(0.31 0.09 29.79);
|
||||
--destructive-foreground: oklch(0.94 0.00 34.31);
|
||||
--border: oklch(0.28 0 0);
|
||||
--input: oklch(0.28 0 0);
|
||||
--ring: oklch(0.80 0.18 151.71);
|
||||
--chart-1: oklch(0.80 0.18 151.71);
|
||||
--chart-2: oklch(0.71 0.14 254.62);
|
||||
--chart-3: oklch(0.71 0.16 293.54);
|
||||
--chart-4: oklch(0.84 0.16 84.43);
|
||||
--chart-5: oklch(0.78 0.13 181.91);
|
||||
--sidebar: oklch(0.18 0 0);
|
||||
--sidebar-foreground: oklch(0.63 0 0);
|
||||
--sidebar-primary: oklch(0.44 0.10 156.76);
|
||||
--sidebar-primary-foreground: oklch(0.92 0.01 167.16);
|
||||
--sidebar-accent: oklch(0.31 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.99 0 0);
|
||||
--sidebar-border: oklch(0.28 0 0);
|
||||
--sidebar-ring: oklch(0.80 0.18 151.71);
|
||||
--font-sans: Outfit, sans-serif;
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: monospace;
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-xs: 0px 1px 3px 0px hsl(0 0% 0% / 0.09);
|
||||
--shadow-sm: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 1px 2px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-md: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 2px 4px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-lg: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 4px 6px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.17), 0px 8px 10px -1px hsl(0 0% 0% / 0.17);
|
||||
--shadow-2xl: 0px 1px 3px 0px hsl(0 0% 0% / 0.43);
|
||||
}
|
||||
|
||||
body {
|
||||
letter-spacing: var(--tracking-normal);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export default {
|
||||
title: "Basecoat",
|
||||
description: "A components library built with Tailwind CSS that works with any web stack.",
|
||||
keywords: ["components", "component library", "component system", "UI", "UI kit", "shadcn", "shadcn/ui", "Tailwind CSS", "Tailwind", "CSS", "HTML", "Jinja", "Nunjucks", "Alpine.js"],
|
||||
author: {
|
||||
name: "Ronan Berder",
|
||||
x: "@hunvreus"
|
||||
},
|
||||
url: process.env.ELEVENTY_ENV === 'development' ? "http://localhost:8080" : "https://basecoatui.com"
|
||||
};
|
||||
@@ -1,122 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Theme -->
|
||||
<script>
|
||||
function isDark() {
|
||||
return localStorage.getItem('darkMode') === 'dark' || (!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
}
|
||||
|
||||
if (isDark()) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
|
||||
if (localStorage.getItem('themeVariant')) {
|
||||
document.documentElement.classList.add(`theme-${localStorage.getItem('themeVariant')}`);
|
||||
}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('darkMode', {
|
||||
isDark: isDark(),
|
||||
toggle() {
|
||||
this.isDark = document.documentElement.classList.toggle('dark');
|
||||
localStorage.setItem('darkMode', this.isDark ? 'dark' : 'light');
|
||||
}
|
||||
});
|
||||
|
||||
Alpine.store('themeVariant', {
|
||||
name: localStorage.getItem('themeVariant'),
|
||||
setThemeVariant(name) {
|
||||
this.name = name;
|
||||
localStorage.setItem('themeVariant', name);
|
||||
document.documentElement.classList.forEach(className => {
|
||||
if (className.startsWith('theme-')) {
|
||||
document.documentElement.classList.remove(className);
|
||||
}
|
||||
});
|
||||
document.documentElement.classList.add(`theme-${name}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Language" content="en">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Title & Description -->
|
||||
{% set meta_title = title ~ ' | ' ~ site.title if title else site.title %}
|
||||
{% set meta_description = description or site.description %}
|
||||
<title>{{ meta_title }}</title>
|
||||
<meta name="description" content="{{ meta_description }}"/>
|
||||
<meta name="title" content="{{ meta_title }}">
|
||||
{% if site.keywords %}<meta name="keywords" content="{{ site.keywords | join(',') }}" />{% endif %}
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
|
||||
<meta property="og:title" content="{{ meta_title }}">
|
||||
<meta property="og:description" content="{{ meta_description }}">
|
||||
<meta property="og:image" content="{{ site.url }}/assets/social-screenshot.png">
|
||||
<meta property="og:site_name" content="{{ site.title }}">
|
||||
<meta property="og:locale" content="en_US">
|
||||
<meta property="og:author" content="{{ site.author.name }}">
|
||||
|
||||
<!-- X/Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:url" content="{{ site.url }}{{ page.url }}">
|
||||
<meta name="twitter:title" content="{{ title if title else site.title }}">
|
||||
<meta name="twitter:description" content="{{ meta_description }}">
|
||||
<meta name="twitter:image" content="{{ site.url }}/assets/social-screenshot.png">
|
||||
<meta name="twitter:creator" content="{{ site.author.x }}">
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="/assets/js/dialog.js" defer></script>
|
||||
<script src="/assets/js/dropdown-menu.js" defer></script>
|
||||
<script src="/assets/js/popover.js" defer></script>
|
||||
<script src="/assets/js/select.js" defer></script>
|
||||
<script src="/assets/js/sidebar.js" defer></script>
|
||||
<script src="/assets/js/tabs.js" defer></script>
|
||||
<script src="/assets/js/toast.js" defer></script>
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerDialog(Alpine);
|
||||
window.basecoat.registerDropdownMenu(Alpine);
|
||||
window.basecoat.registerPopover(Alpine);
|
||||
window.basecoat.registerSelect(Alpine);
|
||||
window.basecoat.registerSidebar(Alpine);
|
||||
window.basecoat.registerTabs(Alpine);
|
||||
window.basecoat.registerToast(Alpine);
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/django.min.js"></script>
|
||||
<script>
|
||||
const applyHighlighting = () => { if (window.hljs) hljs.highlightAll(); };
|
||||
|
||||
if (!window._hljsInitialized) {
|
||||
window._hljsInitialized = true;
|
||||
document.addEventListener('DOMContentLoaded', applyHighlighting);
|
||||
document.addEventListener('htmx:afterSwap', applyHighlighting);
|
||||
}
|
||||
</script>
|
||||
|
||||
{% if head %}{{ head | safe }}{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
{{ content | safe }}
|
||||
|
||||
{% from "toast.njk" import toaster %}
|
||||
{{ toaster() }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
---
|
||||
|
||||
{% include "partials/sidebar.njk" %}
|
||||
|
||||
<main id="content">
|
||||
<header class="bg-background sticky inset-x-0 top-0 isolate flex shrink-0 items-center gap-2 border-b z-10">
|
||||
{% include "partials/header.njk" %}
|
||||
</header>
|
||||
|
||||
<div class="p-4 md:p-6 xl:p-12">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</main>
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
layout: layouts/layout.njk
|
||||
---
|
||||
|
||||
<main class="mx-auto relative flex w-full max-w-screen-lg gap-10">
|
||||
<div class="mx-auto w-full max-w-screen-md">
|
||||
<header class="space-y-2">
|
||||
<h1 class="text-3xl font-bold tracking-tight">{{ title }}</h1>
|
||||
<p class="text-muted-foreground">{{ description }}</p>
|
||||
</header>
|
||||
<article class="pb-12 mt-8 content">
|
||||
{{ content | safe }}
|
||||
</article>
|
||||
</div>
|
||||
{% if toc %}
|
||||
<div class="hidden text-sm xl:block w-full max-w-[300px]">
|
||||
{% from "macros/toc.njk" import toc_nav %}
|
||||
{{ toc_nav(toc) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
@@ -1,28 +0,0 @@
|
||||
{% macro code_block(code, language="html", class="my-4") %}
|
||||
<div
|
||||
class="relative {{ class }}"
|
||||
x-data="{
|
||||
copied: false,
|
||||
copy() {
|
||||
const codeElement = $refs.code;
|
||||
if (!codeElement) return;
|
||||
navigator.clipboard.writeText(codeElement.textContent || '').then(() => {
|
||||
this.copied = true;
|
||||
setTimeout(() => { this.copied = false }, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
}
|
||||
}"
|
||||
>
|
||||
<pre x-ref="code" class="text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-neutral-900 p-0">{{- code | escape -}}</code></pre>
|
||||
|
||||
<button
|
||||
@click="copy()"
|
||||
class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:h-3 [&_svg]:w-3 absolute right-3 top-3"
|
||||
>
|
||||
{% lucide "clipboard", { "class": "size-4", "x-show": "!copied" } %}
|
||||
{% lucide "check", { "class": "size-4", "x-show": "copied" } %}
|
||||
</button>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,28 +0,0 @@
|
||||
{% macro code_preview(id, code, class=None, default_code=false) %}
|
||||
{% from "./code_block.njk" import code_block %}
|
||||
{% set code_panel = code_block(code, class="") %}
|
||||
{% set preview_panel %}
|
||||
<div class="ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 relative rounded-md border">
|
||||
<div class="preview flex min-h-[350px] w-full justify-center p-10 items-center">
|
||||
<div {% if class %}class="{{ class }}"{% endif %}>
|
||||
{{ code | safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
<div class="relative my-6">
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "Preview", panel: preview_panel },
|
||||
{ tab: "Code", panel: code_panel }
|
||||
] %}
|
||||
|
||||
{{ tabs(
|
||||
id="code-block-{{ id }}",
|
||||
tabsets=tabsets,
|
||||
default_tab_index=(2 if default_code else 1),
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,20 +0,0 @@
|
||||
{% macro toc_nav(items=[]) %}
|
||||
<nav class="sticky top-22 space-y-2 [&_ul]:m-0 [&_ul]:list-none [&_ul_ul]:pl-4 [&_li]:mt-0 [&_li]:pt-2 [&_a]:inline-block [&_a]:no-underline [&_a]:transition-colors [&_a]:hover:text-foreground [&_a]:text-muted-foreground">
|
||||
<h4 class="font-medium">On This Page</h4>
|
||||
{% macro toc_list(items=[]) %}
|
||||
{% if items and items.length %}
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li>
|
||||
<a href="#{{ item.id }}">{{ item.label }}</a>
|
||||
{% if item.children and item.children.length %}
|
||||
{{ toc_list(item.children) }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{{ toc_list(items) }}
|
||||
</nav>
|
||||
{% endmacro %}
|
||||
@@ -1,53 +0,0 @@
|
||||
<div class="flex h-14 w-full items-center gap-2 px-4">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Hide sidebar"
|
||||
data-tooltip="Hide sidebar"
|
||||
:data-tooltip="sidebarOpen ? 'Hide sidebar' : 'Show sidebar'"
|
||||
data-side="bottom"
|
||||
data-align="start"
|
||||
class="btn-sm-icon-ghost mr-auto size-7 -ml-1.5"
|
||||
x-init
|
||||
x-data="{ sidebarOpen: true }"
|
||||
@click="
|
||||
sidebarOpen = !sidebarOpen;
|
||||
$dispatch('sidebar:toggle');
|
||||
"
|
||||
>
|
||||
{% lucide "panel-left" %}
|
||||
</button>
|
||||
<select
|
||||
class="select h-8 leading-none"
|
||||
x-init
|
||||
@change="$store.themeVariant.setThemeVariant($event.target.value)"
|
||||
:value="$store.themeVariant.name"
|
||||
>
|
||||
<option value="">Default</option>
|
||||
<option value="claude">Claude</option>
|
||||
<option value="doom-64">Doom 64</option>
|
||||
<option value="supabase">Supabase</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="$store.darkMode.isDark ? 'Switch to light mode' : 'Switch to dark mode'"
|
||||
:data-tooltip="$store.darkMode.isDark ? 'Light mode' : 'Dark mode'"
|
||||
data-side="bottom"
|
||||
class="btn-icon-outline size-8"
|
||||
x-init
|
||||
@click="$store.darkMode.toggle()"
|
||||
>
|
||||
<span x-show="$store.darkMode.isDark">{% lucide "sun" %}</span>
|
||||
<span x-show="!$store.darkMode.isDark">{% lucide "moon" %}</span>
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/hunvreus/basecoat"
|
||||
class="btn-icon size-8"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
data-tooltip="GitHub repository"
|
||||
data-side="bottom"
|
||||
data-align="end"
|
||||
>
|
||||
{% lucide "github" %}
|
||||
</a>
|
||||
</div>
|
||||
@@ -1,97 +0,0 @@
|
||||
<section id="accordion" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Accordion</h2>
|
||||
<a href="/components/accordion" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="group grid w-full max-w-xl gap-4">
|
||||
<section
|
||||
x-init
|
||||
@click="
|
||||
const clickedSummary = $event.target.closest('summary');
|
||||
if (!clickedSummary) return;
|
||||
const clickedDetails = clickedSummary.closest('details');
|
||||
if (!clickedDetails) return;
|
||||
$el.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== clickedDetails) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
"
|
||||
>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it accessible?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It adheres to the WAI-ARIA design pattern.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it styled?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It comes with default styles that matches the other components' aesthetic.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it animated?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm">Yes. It's animated by default, but you can disable it if you prefer.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<section
|
||||
x-init
|
||||
@click="
|
||||
const clickedSummary = $event.target.closest('summary');
|
||||
if (!clickedSummary) return;
|
||||
const clickedDetails = clickedSummary.closest('details');
|
||||
if (!clickedDetails) return;
|
||||
$el.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== clickedDetails) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
"
|
||||
>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
What are the key considerations when implementing a comprehensive enterprise-level authentication system?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Implementing a robust enterprise authentication system requires careful consideration of multiple factors. This includes secure password hashing and storage, multi-factor authentication (MFA) implementation, session management, OAuth2 and SSO integration, regular security audits, rate limiting to prevent brute force attacks, and maintaining detailed audit logs. Additionally, you'll need to consider scalability, performance impact, and compliance with relevant data protection regulations such as GDPR or HIPAA.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
How does modern distributed system architecture handle eventual consistency and data synchronization across multiple regions?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm">Modern distributed systems employ various strategies to maintain data consistency across regions. This often involves using techniques like CRDT (Conflict-Free Replicated Data Types), vector clocks, and gossip protocols. Systems might implement event sourcing patterns, utilize message queues for asynchronous updates, and employ sophisticated conflict resolution strategies. Popular solutions like Amazon's DynamoDB and Google's Spanner demonstrate different approaches to solving these challenges, balancing between consistency, availability, and partition tolerance as described in the CAP theorem.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,28 +0,0 @@
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
<section id="alert-dialog" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Alert Dialog</h2>
|
||||
<a href="/components/alert-dialog" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn-primary" @click="open = false">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="alert-dialog-demo",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=false,
|
||||
close_on_overlay_click=false
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,72 +0,0 @@
|
||||
<section id="alert" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Alert</h2>
|
||||
<a href="/components/alert" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="grid max-w-xl items-start gap-4">
|
||||
<div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "bookmark-check" %}
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
<section>This one has a description only. No title. No icon.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "popcorn" %}
|
||||
<h2>Let's try one with icon and title.</h2>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "shield-alert" %}
|
||||
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "gift" %}
|
||||
<section>This is a very long alert description that demonstrates how the component handles extended text content and potentially wraps across multiple lines</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This is an extremely long alert title that spans multiple lines to demonstrate how the component handles very lengthy headings while maintaining readability and proper text wrapping behavior</h2>
|
||||
<section>This is an equally long description that contains detailed information about the alert. It shows how the component can accommodate extensive content while preserving proper spacing, alignment, and readability across different screen sizes and viewport widths. This helps ensure the user experience remains consistent regardless of the content length.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>
|
||||
<p>Please verify your billing information and try again.</p>
|
||||
<ul>
|
||||
<li>Check your card details</li>
|
||||
<li>Ensure sufficient funds</li>
|
||||
<li>Verify billing address</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="alert border-amber-50 bg-amber-50 text-amber-900 dark:border-amber-950 dark:bg-amber-950 dark:text-amber-100">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Plot Twist: This Alert is Actually Amber!</h2>
|
||||
<section>This one has custom colors for light and dark mode.</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,37 +0,0 @@
|
||||
<section id="avatar" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Avatar</h2>
|
||||
<a href="/components/avatar" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-row flex-wrap items-center gap-4">
|
||||
<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
|
||||
<span class="size-8 shrink-0 bg-muted flex items-center justify-center rounded-full">CN</span>
|
||||
|
||||
<img class="size-12 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
|
||||
<img class="size-8 shrink-0 object-cover rounded-lg" alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-12 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
|
||||
<div class="flex -space-x-2 hover:space-x-1 [&_img]:ring-background [&_img]:size-12 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full [&_img]:ring-2 [&_img]:grayscale [&_img]:transition-all [&_img]:ease-in-out [&_img]:duration-300">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,47 +0,0 @@
|
||||
<section id="badge" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Badge</h2>
|
||||
<a href="/components/badge" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<span class="badge">Primary</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "check" %}
|
||||
Badge
|
||||
</span>
|
||||
<span class="badge-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
Alert
|
||||
</span>
|
||||
<span class="badge rounded-full min-w-5 px-1">8</span>
|
||||
<span class="badge-destructive rounded-full min-w-5 px-1">99</span>
|
||||
<span class="badge-outline rounded-full min-w-5 px-1 font-mono tabular-nums">20+</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<a href="#" class="badge">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-secondary">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-destructive">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-outline">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,42 +0,0 @@
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
<section id="breadcrumb" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Breadcrumb</h2>
|
||||
<a href="/components/breadcrumb" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
id="demo-breadcrumb-menu",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,101 +0,0 @@
|
||||
<section id="button" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Button</h2>
|
||||
<a href="/components/button" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-primary">Primary</button>
|
||||
<button class="btn-outline">Outline</button>
|
||||
<button class="btn-ghost">Ghost</button>
|
||||
<button class="btn-destructive">
|
||||
{% lucide "send" %}
|
||||
Danger
|
||||
</button>
|
||||
<button class="btn-secondary">Secondary</button>
|
||||
<button class="btn-link">Link</button>
|
||||
<button class="btn-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-sm-primary">Primary</button>
|
||||
<button class="btn-sm-outline">Outline</button>
|
||||
<button class="btn-sm-ghost">Ghost</button>
|
||||
<button class="btn-sm-destructive">Danger</button>
|
||||
<button class="btn-sm-secondary">Secondary</button>
|
||||
<button class="btn-sm-link">Link</button>
|
||||
<button class="btn-sm-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-sm-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-sm-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-lg-primary">Primary</button>
|
||||
<button class="btn-lg-outline">Outline</button>
|
||||
<button class="btn-lg-ghost">Ghost</button>
|
||||
<button class="btn-lg-destructive">
|
||||
{% lucide "send" %}
|
||||
Danger
|
||||
</button>
|
||||
<button class="btn-lg-secondary">Secondary</button>
|
||||
<button class="btn-lg-link">Link</button>
|
||||
<button class="btn-lg-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-lg-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-lg-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-icon-primary">
|
||||
{% lucide "download" %}
|
||||
</button>
|
||||
<button class="btn-icon-secondary">
|
||||
{% lucide "upload" %}
|
||||
</button>
|
||||
<button class="btn-icon-outline">
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-icon-ghost">
|
||||
{% lucide "ellipsis" %}
|
||||
</button>
|
||||
<button class="btn-icon-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
</button>
|
||||
<button class="btn-icon-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,147 +0,0 @@
|
||||
<section id="card" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Card</h2>
|
||||
<a href="/components/card" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col items-start gap-4">
|
||||
<div class="card w-full max-w-sm">
|
||||
<header>
|
||||
<h2>Login to your account</h2>
|
||||
<p>Enter your details below to login to your account</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-card-form-email">Email</label>
|
||||
<input type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ml-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input type="password" id="demo-card-form-password">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer class="flex flex-col items-center gap-2">
|
||||
<button type="button" class="btn w-full">Login</button>
|
||||
<button type="button" class="btn-outline w-full">Login with Google</button>
|
||||
<p class="mt-4 text-center text-sm">Don't have an account? <a href="#" class="underline-offset-4 hover:underline">Sign up</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Meeting Notes</h2>
|
||||
<p>Transcript from the meeting with the client.</p>
|
||||
</header>
|
||||
<section class="text-sm">
|
||||
<p>Client requested dashboard redesign with focus on mobile responsiveness.</p>
|
||||
<ol class="mt-4 flex list-decimal flex-col gap-2 pl-6">
|
||||
<li>New analytics widgets for daily/weekly metrics</li>
|
||||
<li>Simplified navigation menu</li>
|
||||
<li>Dark mode support</li>
|
||||
<li>Timeline: 6 weeks</li>
|
||||
<li>Follow-up meeting scheduled for next Tuesday</li>
|
||||
</ol>
|
||||
</section>
|
||||
<footer class="flex items-center">
|
||||
<div class="flex -space-x-2 [&_img]:ring-background [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Is this an image?</h2>
|
||||
<p>This is a card with an image.</p>
|
||||
</header>
|
||||
<section class="px-0">
|
||||
<img
|
||||
alt="Photo by Drew Beamer"
|
||||
loading="lazy"
|
||||
width="500"
|
||||
height="500"
|
||||
class="aspect-video object-cover" style="color:transparent"
|
||||
srcset="
|
||||
https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=640&q=75 1x,
|
||||
https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=1080&q=75 2x
|
||||
"
|
||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=1080&q=75"
|
||||
/>
|
||||
</section>
|
||||
<footer class="flex items-center gap-2">
|
||||
<span class="badge-outline">
|
||||
{% lucide "bed" %}
|
||||
1
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "bath" %}
|
||||
2
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "land-plot" %}
|
||||
350m²
|
||||
</span>
|
||||
<span class="ml-auto font-medium tabular-nums">
|
||||
$135,000
|
||||
</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full flex-wrap items-start gap-8 md:*:[.card]:basis-1/4">
|
||||
<div class="card">
|
||||
<section>Content Only</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Header Only</h2>
|
||||
<p>This is a card with a header and a description.</p>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Header and Content</h2>
|
||||
<p>This is a card with a header and a content.</p>
|
||||
</header>
|
||||
<section>Content only.</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<footer>Footer Only</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Header + Footer</h2>
|
||||
<p>This is a card with a header and a footer.</p>
|
||||
</header>
|
||||
<footer>Footer</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<section>Content</section>
|
||||
<footer>Footer</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Header + Content + Footer</h2>
|
||||
<p>This is a card with a header, content and footer.</p>
|
||||
</header>
|
||||
<section>Content</section>
|
||||
<footer>Footer</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,34 +0,0 @@
|
||||
<section id="checkbox" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Checkbox</h2>
|
||||
<a href="/components/checkbox" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6 max-w-lg">
|
||||
<label class="label gap-3">
|
||||
<input type="checkbox" class="input">
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
<div class="flex items-start gap-3">
|
||||
<input type="checkbox" id="demo-checkbox-label-and-description" class="input">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-checkbox-label-and-description" class="label">Accept terms and conditions</label>
|
||||
<p class="text-muted-foreground text-sm">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</div>
|
||||
<label class="label gap-3">
|
||||
<input type="checkbox" class="input" disabled>
|
||||
Enable notifications
|
||||
</label>
|
||||
<label class="flex items-start gap-3 border p-3 hover:bg-accent/50 rounded-lg has-[input[type='checkbox']:checked]:border-blue-600 has-[input[type='checkbox']:checked]:bg-blue-50 dark:has-[input[type='checkbox']:checked]:border-blue-900 dark:has-[input[type='checkbox']:checked]:bg-blue-950">
|
||||
<input type="checkbox" class="input checked:bg-blue-600 checked:border-blue-600 dark:checked:bg-blue-700 dark:checked:border-blue-700 checked:after:bg-white" checked>
|
||||
<div class="grid gap-2">
|
||||
<h2 class="text-sm leading-none font-medium">Enable notifications</h2>
|
||||
<p class="text-muted-foreground text-sm">You can enable or disable notifications at any time.</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,83 +0,0 @@
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
<section id="combobox" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Combobox</h2>
|
||||
<a href="/components/combobox" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% call select(
|
||||
content_attrs={"class": "w-48"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
|
||||
{% set options_timezones = [
|
||||
{
|
||||
label: "Americas",
|
||||
timezones: [
|
||||
{ value: "America/New_York", label: "(GMT-5) New York" },
|
||||
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
||||
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
||||
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
||||
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
||||
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Europe",
|
||||
timezones: [
|
||||
{ value: "Europe/London", label: "(GMT+0) London" },
|
||||
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
||||
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
||||
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
||||
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
||||
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Asia/Pacific",
|
||||
timezones: [
|
||||
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
||||
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
||||
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
||||
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
||||
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
||||
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" }
|
||||
]
|
||||
}
|
||||
] %}
|
||||
{% call select(
|
||||
content_attrs={"class": "w-72"},
|
||||
listbox_attrs={"data-empty": "No timezone found."},
|
||||
is_combobox=true
|
||||
) %}
|
||||
<div class="max-h-64 overflow-y-auto scrollbar">
|
||||
{% for group in options_timezones %}
|
||||
<div role="group" aria-labelledby="demo-combobox-timezones-group-{{ loop.index0 }}">
|
||||
<span id="demo-combobox-timezones-group-{{ loop.index0 }}" role="heading">{{ group.label }}</span>
|
||||
{% for timezone in group.timezones %}
|
||||
<div role="option" data-value="{{ timezone.value}}">
|
||||
{{ timezone.label }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="option">
|
||||
{% lucide "circle-plus" %}
|
||||
Create timezone
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,76 +0,0 @@
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
<section id="dialog" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Dialog</h2>
|
||||
<a href="/components/dialog" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn" @click="open = false">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-example",
|
||||
title="Scrollable Content",
|
||||
description="This is a dialog with scrollable content.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
content_body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
<button
|
||||
x-init
|
||||
@click="$dispatch('dialog:open', { id: 'dialog-example' })"
|
||||
type="button"
|
||||
class="btn-outline"
|
||||
>
|
||||
Trigger dialog:open
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,292 +0,0 @@
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
<section id="dropdown-menu" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Dropdown Menu</h2>
|
||||
<a href="/components/dropdown-menu" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">My Account</span>
|
||||
<button type="button" role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
<button type="button" role="menuitem">Support</button>
|
||||
<button type="button" role="menuitem" disabled>API</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
{% endcall %}
|
||||
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu-checkboxes",
|
||||
trigger="Checkboxes",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</span>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "settings" %}
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="appearance-options">
|
||||
<span id="appearance-options" role="heading">Appearance</span>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
x-data="{ checked: true }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
disabled
|
||||
x-data="{ checked: false }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
x-data="{ checked: false }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
{% endcall %}
|
||||
|
||||
|
||||
|
||||
{% set profile_menu %}
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "star" %}
|
||||
Upgrade to Pro
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Account
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "bell" %}
|
||||
Notifications
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</button>
|
||||
{% endset %}
|
||||
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu-radio-group",
|
||||
trigger="Radio Group",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
|
||||
<span id="position-options" role="heading">Panel Position</span>
|
||||
<hr role="separator">
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'top'"
|
||||
:aria-checked="checked == 'top'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'bottom'"
|
||||
:aria-checked="checked == 'bottom'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'right'"
|
||||
:aria-checked="checked == 'right'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down", { "class": "text-muted-foreground ml-auto" } %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-outline h-12 justify-start px-2 md:max-w-[200px]"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost rounded-full size-8"},
|
||||
content_attrs={"data-align": "end"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost"},
|
||||
content_attrs={"class": "min-w-32"}
|
||||
) %}
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "pencil" %}
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "share" %}
|
||||
Share
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Delete
|
||||
</button>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,106 +0,0 @@
|
||||
<section id="form" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Form</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<form class="form grid w-full max-w-sm gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-text">Username</label>
|
||||
<input type="text" id="demo-form-text" placeholder="hunvreus">
|
||||
<p class="text-muted-foreground text-sm">This is your public display name.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-select">Email</label>
|
||||
<select id="demo-form-select">
|
||||
<option value="bob@example.com">m@example.com</option>
|
||||
<option value="alice@example.com">m@google.com</option>
|
||||
<option value="john@example.com">m@support.com</option>
|
||||
</select>
|
||||
<p class="text-muted-foreground text-sm">You can manage email addresses in your email settings.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-text">Bio</label>
|
||||
<textarea id="demo-form-textarea" placeholder="I like to..." rows="3"></textarea>
|
||||
<p class="text-muted-foreground text-sm">You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<label for="demo-form-radio">Notify me about...</label>
|
||||
<fieldset id="demo-form-radio" class="grid gap-3">
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="1">All new messages</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="2">Direct messages and mentions</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="3" >Nothing</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
|
||||
<input type="checkbox" id="demo-form-checkbox">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="demo-form-checkbox" class="leading-snug">Use different settings for my mobile devices</label>
|
||||
<p class="text-muted-foreground text-sm leading-snug">You can manage your mobile notifications in the mobile settings page.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<header>
|
||||
<label for="demo-form-checkboxes" class="text-base leading-normal">Sidebar</label>
|
||||
<p class="text-muted-foreground text-sm">Select the items you want to display in the sidebar.</p>
|
||||
</header>
|
||||
<fieldset id="demo-form-checkboxes" class="flex flex-col gap-2">
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="1" checked>
|
||||
Recents
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="2" checked>
|
||||
Home
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="3">
|
||||
Applications
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="4">
|
||||
Desktop
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="5">
|
||||
Download
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="6">
|
||||
Documents
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-date">Date of birth</label>
|
||||
<input type="date" id="demo-form-date">
|
||||
<p class="text-muted-foreground text-sm">Your date of birth is used to calculate your age.</p>
|
||||
</div>
|
||||
|
||||
<section class="grid gap-4">
|
||||
<h3 class="text-lg font-medium">Email Notifications</h3>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label for="demo-form-switch" class="leading-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch" role="switch">
|
||||
</div>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5 opacity-60">
|
||||
<label for="demo-form-switch-disabled" class="leading-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch-disabled" role="switch" disabled>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,27 +0,0 @@
|
||||
<section id="input" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Input</h2>
|
||||
<a href="/components/input" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<input type="text" class="input" placeholder="Text">
|
||||
<input type="text" class="input" disabled placeholder="Disabled">
|
||||
<input type="text" class="input" aria-invalid="true" placeholder="Error">
|
||||
<input type="email" class="input" placeholder="Email">
|
||||
<input type="password" class="input" placeholder="Password">
|
||||
<input type="number" class="input" placeholder="Number">
|
||||
<input type="file" class="input">
|
||||
<input type="tel" class="input" placeholder="Tel">
|
||||
<input type="url" class="input" placeholder="URL">
|
||||
<input type="search" class="input" placeholder="Search">
|
||||
<input type="date" class="input">
|
||||
<input type="datetime-local" class="input">
|
||||
<input type="month" class="input">
|
||||
<input type="week" class="input">
|
||||
<input type="time" class="input">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,30 +0,0 @@
|
||||
<section id="label" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Label</h2>
|
||||
<a href="/components/label" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<div class="grid w-full max-w-sm gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" id="label-demo-terms" class="input">
|
||||
<label for="label-demo-terms" class="label">Accept terms and conditions</label>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-text" class="label">Username</label>
|
||||
<input type="text" id="label-demo-text" class="input" placeholder="Username">
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-disabled" class="label">Disabled</label>
|
||||
<input type="text" id="label-demo-disabled" class="peer input" placeholder="Disabled" disabled>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-textarea" class="label">Message</label>
|
||||
<textarea id="label-demo-textarea" class="textarea" placeholder="Message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,34 +0,0 @@
|
||||
<section id="pagination" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Pagination</h2>
|
||||
<a href="/components/pagination" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="inline-flex">
|
||||
<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
|
||||
<ul class="flex flex-row items-center gap-1">
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">{% lucide "chevron-left" %} Previous</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost size-9">1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-outline size-9">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost size-9">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">{% lucide "ellipsis" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">Next {% lucide "chevron-right" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,43 +0,0 @@
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
<section id="popover" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Popover</h2>
|
||||
<a href="/components/popover" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
{% call popover(
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-80 p-4"}
|
||||
) %}
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
<h4 class="leading-none font-medium">Dimensions</h4>
|
||||
<p class="text-muted-foreground text-sm">Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="form grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-width">Width</label>
|
||||
<input type="text" id="demo-popover-width" value="100%" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-width">Max. width</label>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,33 +0,0 @@
|
||||
<section id="radio-group" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Radio Group</h2>
|
||||
<a href="/components/radio-group" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<fieldset class="grid gap-3">
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="default" class="input">Default</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="comfortable" class="input">Comfortable</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="compact" class="input">Compact</label>
|
||||
</fieldset>
|
||||
<fieldset class="grid gap-3 max-w-sm">
|
||||
<label class="label gap-3 items-start hover:bg-accent/50 rounded-lg border p-4 has-[input[type='radio']:checked]:border-green-600 has-[input[type='radio']:checked]:bg-green-50 dark:has-[input[type='radio']:checked]:border-green-900 dark:has-[input[type='radio']:checked]:bg-green-950">
|
||||
<input type="radio" name="demo-radio-group-styled" value="no" class="input checked:bg-green-600 checked:border-green-600 dark:checked:bg-input/30 checked:before:bg-background dark:checked:before:bg-primary">
|
||||
<div class="grid gap-1 font-normal">
|
||||
<h2 class="font-medium">Starter Plan</h2>
|
||||
<p class="text-muted-foreground leading-snug">Perfect for small businesses getting started with our platform</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label gap-3 items-start hover:bg-accent/50 rounded-lg border p-4 has-[input[type='radio']:checked]:border-green-600 has-[input[type='radio']:checked]:bg-green-50 dark:has-[input[type='radio']:checked]:border-green-900 dark:has-[input[type='radio']:checked]:bg-green-950">
|
||||
<input type="radio" name="demo-radio-group-styled" value="no" class="input checked:bg-green-600 checked:border-green-600 dark:checked:bg-input/30 checked:before:bg-background dark:checked:before:bg-primary">
|
||||
<div class="grid gap-1 font-normal">
|
||||
<h2 class="font-medium">Pro Plan</h2>
|
||||
<p class="text-muted-foreground leading-snug">Advanced features for growing businesses with higher demands</p>
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,76 +0,0 @@
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
<section id="select" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Select</h2>
|
||||
<a href="/components/select" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4 gap">
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<select class="select w-[180px]">
|
||||
<optgroup label="Fruits">
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
<option>Blueberry</option>
|
||||
</optgroup>
|
||||
<optgroup label="Grapes">
|
||||
<option>Pineapple</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select class="select w-[180px]" disabled>
|
||||
<option>Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
{% call select(trigger_attrs={"class": "w-[180px]"}) %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
</div>
|
||||
<div role="group" aria-labelledby="vegetable-options-label">
|
||||
<span id="vegetable-options-label" role="heading">Grapes</span>
|
||||
<div role="option" data-value="pineapple">Pineapple</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"}
|
||||
) %}
|
||||
{% for i in range(0, 99) %}
|
||||
<div role="option" data-value="item-{{ i }}">Item {{ i }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
|
||||
{% call select(trigger_attrs={"disabled": "disabled", "class": "w-[180px]"}) %}
|
||||
<div role="option" data-value="disabled">Disabled</div>
|
||||
{% endcall %}
|
||||
|
||||
|
||||
{% call select(name="just-testing", trigger_attrs={'class': 'w-[180px]'}) %}
|
||||
<div type="button" role="option" data-value="bar">
|
||||
{% lucide "chart-bar", {"class": "text-muted-foreground"} %}
|
||||
Bar
|
||||
</div>
|
||||
<div type="button" role="option" data-value="line">
|
||||
{% lucide "chart-line", {"class": "text-muted-foreground"} %}
|
||||
Line
|
||||
</div>
|
||||
<div type="button" role="option" data-value="pie">
|
||||
{% lucide "chart-pie", {"class": "text-muted-foreground"} %}
|
||||
Pie
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,40 +0,0 @@
|
||||
<section id="skeleton" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Skeleton</h2>
|
||||
<a href="/components/skeleton" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="bg-accent animate-pulse size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-[150px]"></div>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex max-sm:flex-col gap-4 w-full">
|
||||
<div class="card w-full @md:w-auto @md:min-w-sm">
|
||||
<header>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-2/3"></div>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-1/2"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="bg-accent animate-pulse rounded-md aspect-square w-full"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="card w-full @md:w-auto @md:min-w-sm">
|
||||
<header>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-2/3"></div>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-1/2"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="bg-accent animate-pulse rounded-md aspect-square w-full"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,30 +0,0 @@
|
||||
<section id="slider" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Slider</h2>
|
||||
<a href="/components/slider" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="max-w-sm">
|
||||
<input
|
||||
type="range"
|
||||
class="input w-full"
|
||||
min="0"
|
||||
max="27"
|
||||
value="12"
|
||||
x-data="{
|
||||
updatePercent(el) {
|
||||
const min = parseFloat(el.min || 0);
|
||||
const max = parseFloat(el.max || 100);
|
||||
const value = parseFloat(el.value);
|
||||
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
|
||||
el.style.setProperty('--slider-value', `${percent}%`);
|
||||
}
|
||||
}"
|
||||
x-init="updatePercent($el)"
|
||||
@input="updatePercent($el)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,27 +0,0 @@
|
||||
<section id="switch" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Switch</h2>
|
||||
<a href="/components/switch" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="inline-flex flex-col gap-y-6">
|
||||
<label class="label">
|
||||
<input type="checkbox" name="switch" role="switch" class="input">
|
||||
Airplane Mode
|
||||
</label>
|
||||
<label class="label">
|
||||
<input type="checkbox" name="switch" role="switch" class="input checked:bg-blue-500 dark:checked:bg-blue-600" checked>
|
||||
Bluetooth
|
||||
</label>
|
||||
<label class="label gap-6 leading-none border rounded-lg p-4 has-[input[type='checkbox']:checked]:border-blue-600">
|
||||
<div class="grid gap-1">
|
||||
<h2 class="font-medium text-sm">Share across devices</h2>
|
||||
<p class="text-muted-foreground text-sm">Focus is shared across devices, and turns off when you leave the app.</p>
|
||||
</div>
|
||||
<input type="checkbox" name="switch" role="switch" class="input checked:bg-blue-500 dark:checked:bg-blue-600">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,74 +0,0 @@
|
||||
<section id="table" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Table</h2>
|
||||
<a href="/components/table" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
|
||||
<div class="relative w-full overflow-x-auto">
|
||||
<table class="table">
|
||||
<caption>A list of your recent invoices.</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice</th>
|
||||
<th>Status</th>
|
||||
<th>Method</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-medium">INV001</td>
|
||||
<td>Paid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$250.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV002</td>
|
||||
<td>Pending</td>
|
||||
<td>PayPal</td>
|
||||
<td class="text-right">$150.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV003</td>
|
||||
<td>Unpaid</td>
|
||||
<td>Bank Transfer</td>
|
||||
<td class="text-right">$350.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV004</td>
|
||||
<td>Paid</td>
|
||||
<td>Paypal</td>
|
||||
<td class="text-right">$450.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV005</td>
|
||||
<td>Paid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$550.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV006</td>
|
||||
<td>Pending</td>
|
||||
<td>Bank Transfer</td>
|
||||
<td class="text-right">$200.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV007</td>
|
||||
<td>Unpaid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$300.00</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3">Total</td>
|
||||
<td class="text-right">$2,500.00</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,105 +0,0 @@
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
<section id="tabs" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Tabs</h2>
|
||||
<a href="/components/tabs" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
{% set account_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set password_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save Password</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set tabsets = [
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets,
|
||||
main_attrs={ "class": "max-w-[300px]" },
|
||||
tablist_attrs={ "class": "w-full" }
|
||||
) }}
|
||||
|
||||
{% set tabsets = [
|
||||
{ tab: "Home" },
|
||||
{ tab: "Settings" }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-without-panels',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
|
||||
{% set tabsets = [
|
||||
{ tab: "Home" },
|
||||
{ tab: "Disabled", tab_attrs: { "disabled": "disabled" } }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-disabled',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
|
||||
{% set label_preview %}
|
||||
{% lucide 'app-window' %}
|
||||
Preview
|
||||
{% endset %}
|
||||
{% set label_code %}
|
||||
{% lucide 'code' %}
|
||||
Code
|
||||
{% endset %}
|
||||
{% set tabsets = [
|
||||
{ tab: label_preview },
|
||||
{ tab: label_code }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-icons',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,27 +0,0 @@
|
||||
<section id="textarea" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Textarea</h2>
|
||||
<a href="/components/textarea" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-10">
|
||||
<textarea class="textarea" placeholder="Type your message here"></textarea>
|
||||
<textarea class="textarea" placeholder="Type your message here" aria-invalid="true"></textarea>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-label" class="label">Label</label>
|
||||
<textarea id="textarea-demo-label" class="textarea" placeholder="Type your message here"></textarea>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-label-and-description" class="label">With label and description</label>
|
||||
<textarea id="textarea-demo-label-and-description" class="textarea" placeholder="Type your message here"></textarea>
|
||||
<p class="text-muted-foreground text-sm">Type your message and press enter to send.</p>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-disabled" class="label">Disabled</label>
|
||||
<textarea id="textarea-demo-disabled" class="textarea" placeholder="Type your message here" disabled></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,25 +0,0 @@
|
||||
<section id="toast" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Toast</h2>
|
||||
<a href="/components/toast" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/success" hx-target="#toaster" hx-swap="beforeend">
|
||||
Success
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/error" hx-target="#toaster" hx-swap="beforeend">
|
||||
Error
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/info" hx-target="#toaster" hx-swap="beforeend">
|
||||
Info
|
||||
</button>
|
||||
<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/warning" hx-target="#toaster" hx-swap="beforeend">
|
||||
Warning
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,24 +0,0 @@
|
||||
<section id="tooltip" class="w-full rounded-lg border scroll-mt-14">
|
||||
<header class="border-b px-4 py-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium">Tooltip</h2>
|
||||
<a href="/components/tooltip" class="text-muted-foreground hover:text-foreground" data-tooltip="See documentation" data-side="left">
|
||||
{% lucide "book-open", { "class": "size-4" } %}
|
||||
</a>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<button type="button" class="btn-outline" data-tooltip="Top tooltip">
|
||||
Top
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Right tooltip" data-side="right">
|
||||
Right
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Bottom tooltip" data-side="bottom">
|
||||
Bottom
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Left tooltip" data-side="left">
|
||||
Left
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,146 +0,0 @@
|
||||
{% from "sidebar.njk" import sidebar %}
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
{% set footer %}
|
||||
{% set trigger %}
|
||||
<img src="https://github.com/hunvreus.png" class="rounded-lg shrink-0 size-8">
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Ronan Berder</span>
|
||||
<span class="truncate text-xs">@hunvreus</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-ghost p-2 h-12 w-full flex items-center justify-start"},
|
||||
content_attrs={
|
||||
"data-side": "right",
|
||||
"data-align": "end",
|
||||
"class": "w-80",
|
||||
"x-init": "
|
||||
if (window.innerWidth < 768) {
|
||||
$el.setAttribute('data-side', 'top');
|
||||
$el.setAttribute('data-align', 'start');
|
||||
$el.classList.add('w-full');
|
||||
}
|
||||
"
|
||||
}
|
||||
) %}
|
||||
<div class="grid gap-4 p-4">
|
||||
<header class="grid gap-1.5">
|
||||
<h2 class="font-semibold">I hope you like Basecoat...</h2>
|
||||
<p class="text-muted-foreground text-sm">My name is <a href="https://ronanberder.com" target="_blank">Ronan</a> and I made this (and <a class="underline underline-offset-4" href="https://pagescms.org" target="_target">other things</a>). If you find it useful, please consider sponsoring me on GitHub or following me on X.</p>
|
||||
</header>
|
||||
<footer class="grid gap-2">
|
||||
<a href="https://github.com/sponsors/hunvreus" class="btn-sm" target="_blank">Sponsor me on GitHub</a>
|
||||
<a href="https://x.com/hunvreus" class="btn-sm-outline" target="_blank">Follow me on X</a>
|
||||
</footer>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set header %}
|
||||
<a href="/" class="btn-ghost p-2 h-12 w-full justify-start">
|
||||
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Basecoat</span>
|
||||
<span class="truncate text-xs">v{{ pkg.version }}</span>
|
||||
</div>
|
||||
</a>
|
||||
{# <div class="flex flex-col gap-2 px-2 text-sm">
|
||||
<form class="form relative">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
|
||||
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
|
||||
</form>
|
||||
</div> #}
|
||||
{% endset %}
|
||||
|
||||
{% set link_attrs = {
|
||||
"hx-boost": "true",
|
||||
"hx-select": "#content",
|
||||
"hx-target": "#content",
|
||||
"hx-swap": "outerHTML",
|
||||
"@click": "if (window.innerWidth < 768) { open = false }"
|
||||
} %}
|
||||
|
||||
{% set icon_home %}{% lucide "house" %}{% endset %}
|
||||
{% set icon_info %}{% lucide "info" %}{% endset %}
|
||||
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
|
||||
{% set icon_layout_dashboard %}{% lucide "layout-dashboard" %}{% endset %}
|
||||
{% set icon_github %}{% lucide "github" %}{% endset %}
|
||||
|
||||
{% set menu = [
|
||||
{ type: "group", label: "Getting started", items: [
|
||||
{ label: "Introduction", url: "/introduction", icon: icon_info, attrs: link_attrs },
|
||||
{ label: "Installation", url: "/installation", icon: icon_square_terminal, attrs: link_attrs },
|
||||
{ label: "Kitchen sink", url: "/kitchen-sink", icon: icon_layout_dashboard, attrs: link_attrs },
|
||||
{ label: "GitHub", url: "https://github.com/hunvreus/basecoat", icon: icon_github, attrs: {
|
||||
"target": "_blank",
|
||||
"@click": "if (window.innerWidth < 768) { open = false }"
|
||||
}
|
||||
}
|
||||
]},
|
||||
{ type: 'group', label: 'Components', items: [
|
||||
{ label: "Accordion", url: "/components/accordion", attrs: link_attrs },
|
||||
{ label: "Alert", url: "/components/alert", attrs: link_attrs },
|
||||
{ label: "Alert Dialog", url: "/components/alert-dialog", attrs: link_attrs },
|
||||
{ label: "Avatar", url: "/components/avatar", attrs: link_attrs },
|
||||
{ label: "Badge", url: "/components/badge", attrs: link_attrs },
|
||||
{ label: "Breadcrumb", url: "/components/breadcrumb", attrs: link_attrs },
|
||||
{ label: "Button", url: "/components/button", attrs: link_attrs },
|
||||
{ label: "Card", url: "/components/card", attrs: link_attrs },
|
||||
{ label: "Checkbox", url: "/components/checkbox", attrs: link_attrs },
|
||||
{ label: "Combobox", url: "/components/combobox", attrs: link_attrs },
|
||||
{ label: "Dialog", url: "/components/dialog", attrs: link_attrs },
|
||||
{ label: "Dropdown Menu", url: "/components/dropdown-menu", attrs: link_attrs },
|
||||
{ label: "Form", url: "/components/form", attrs: link_attrs },
|
||||
{ label: "Input", url: "/components/input", attrs: link_attrs },
|
||||
{ label: "Label", url: "/components/label", attrs: link_attrs },
|
||||
{ label: "Pagination", url: "/components/pagination", attrs: link_attrs },
|
||||
{ label: "Popover", url: "/components/popover", attrs: link_attrs },
|
||||
{ label: "Radio Group", url: "/components/radio-group", attrs: link_attrs },
|
||||
{ label: "Select", url: "/components/select", attrs: link_attrs },
|
||||
{ label: "Skeleton", url: "/components/skeleton", attrs: link_attrs },
|
||||
{ label: "Sidebar", url: "/components/sidebar", attrs: link_attrs },
|
||||
{ label: "Slider", url: "/components/slider", attrs: link_attrs },
|
||||
{ label: "Switch", url: "/components/switch", attrs: link_attrs },
|
||||
{ label: "Table", url: "/components/table", attrs: link_attrs },
|
||||
{ label: "Tabs", url: "/components/tabs", attrs: link_attrs },
|
||||
{ label: "Textarea", url: "/components/textarea", attrs: link_attrs },
|
||||
{ label: "Toast", url: "/components/toast", attrs: link_attrs },
|
||||
{ label: "Tooltip", url: "/components/tooltip", attrs: link_attrs }
|
||||
]}
|
||||
] %}
|
||||
|
||||
|
||||
{{ sidebar(
|
||||
label="Sidebar navigation",
|
||||
header=header,
|
||||
footer=footer,
|
||||
content_attrs={
|
||||
"x-data": "{
|
||||
updateCurrentPage() {
|
||||
$el.querySelectorAll('a').forEach(link => {
|
||||
normalizedCurrentPathname = window.location.pathname.endsWith('/') ?
|
||||
window.location.pathname.slice(0, -1) :
|
||||
window.location.pathname;
|
||||
normalizedLinkPathname = link.pathname.endsWith('/') ?
|
||||
link.pathname.slice(0, -1) :
|
||||
link.pathname;
|
||||
if (normalizedCurrentPathname === normalizedLinkPathname) {
|
||||
link.setAttribute('aria-current', 'page');
|
||||
} else {
|
||||
link.setAttribute('aria-current', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
}",
|
||||
"class": "scrollbar",
|
||||
"x-init": "updateCurrentPage()",
|
||||
"@htmx:pushed-into-history.window": "updateCurrentPage()"
|
||||
},
|
||||
menu=menu
|
||||
) }}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 146 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,70 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Accordion
|
||||
description: A vertically stacked set of interactive headings that each reveal a section of content.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated accordion component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<section
|
||||
x-init
|
||||
@click="
|
||||
const clickedSummary = $event.target.closest('summary');
|
||||
if (!clickedSummary) return;
|
||||
const clickedDetails = clickedSummary.closest('details');
|
||||
if (!clickedDetails) return;
|
||||
$el.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== clickedDetails) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
"
|
||||
>
|
||||
<details class="group border-b last:border-b-0 ">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it accessible?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It adheres to the WAI-ARIA design pattern.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it styled?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section class="pb-4">
|
||||
<p class="text-sm">Yes. It comes with default styles that matches the other components' aesthetic.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] transition-all outline-none rounded-md">
|
||||
<h2 class="flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium hover:underline ">
|
||||
Is it animated?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm whitespace-pre-wrap">Yes. It's animated by default, but you can disable it if you prefer.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
{% endset %}
|
||||
{{ code_preview("accordion", code | prettyHtml, "w-full max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Basecoat animates <code><details></code> elements by default. The example above uses Alpine.js to handle the open/close state along with some Tailwind CSS classes.</p>
|
||||
@@ -1,122 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Alert Dialog
|
||||
description: A modal dialog that interrupts the user with important content and expects a response.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + Javascript
|
||||
id: usage-default
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
{% set code_html %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn-primary" @click="open = false">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="alert-dialog",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open alert dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=false,
|
||||
close_on_overlay_click=false,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/dialog.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerDialog(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("alert-dialog", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<section class="prose">
|
||||
<p>The Alert Dialog component uses the same Alpine.js code as the <a href="/components/dialog" class="font-medium underline underline-offset-4">Dialog</a> component with one small difference: we set <code>initialCloseOnOverlayClick</code> to <code>false</code> to prevent the dialog from closing when the overlay is clicked.</p>
|
||||
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/dialog.js" target="_blank">
|
||||
Individual JS file (dialog.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="alert-dialog", tabsets=tabsets) }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dialog()</code> Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set <code>close_on_overlay_click</code> and <code>close_button</code> to <code>False</code> (or <code>false</code> for Nunjucks).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/dialog.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn-primary" @click="open = false">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="demo-alert-dialog",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open Alert Dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=False,
|
||||
close_on_overlay_click=False
|
||||
) }}{% endraw %}
|
||||
{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Alert
|
||||
description: Displays a callout for user attention.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: No description
|
||||
id: example-no-description
|
||||
- label: No icon
|
||||
id: example-no-icon
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use one of the two classes: <code>alert</code> or <code>alert-dialog</code> with any of the following children:</p>
|
||||
<ul>
|
||||
<li>A svg icon,</li>
|
||||
<li>A <code><h2></code> for the title.</li>
|
||||
<li>A <code><section></code> for the description. This can include paragraphs, lists, etc.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-destructive", code) }}
|
||||
|
||||
<h3 id="example-no-description"><a href="#example-no-description">No description</a></h3>
|
||||
|
||||
{% set code %}
|
||||
<div class="alert">
|
||||
{% lucide "shield-alert" %}
|
||||
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-no-description", code) }}
|
||||
|
||||
<h3 id="example-no-icon"><a href="#example-no-icon">No icon</a></h3>
|
||||
|
||||
{% set code %}<div class="alert">
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("alert-no-icon", code) }}
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Avatar
|
||||
description: An image element with a fallback for representing the user.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated avatar component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">{% endset %}
|
||||
{{ code_preview("avatar", code, "max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Avatars are fairly simple to implement with Tailwind. The example above shows how to implement one with Tailwind and an <code><img></code> element. More examples can be found in the <a href="/kitchen-sink#avatar">kitchen sink</a>.</p>
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Badge
|
||||
description: Displays a badge or a component that looks like a badge.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Primary
|
||||
id: example-primary
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: With icon
|
||||
id: example-with-icon
|
||||
- label: Link
|
||||
id: example-link
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<span class="badge">Badge</span>{% endset %}
|
||||
{{ code_preview("badge", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Use one of the badge classes: <code>badge</code>, <code>badge-primary</code>, <code>badge-secondary</code>, <code>badge-destructive</code> or <code>badge-outline</code>.
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-primary"><a href="#example-primary">Primary</a></h3>
|
||||
|
||||
{% set code %}<span class="badge">Primary</span>{% endset %}
|
||||
{{ code_preview("badge-primary", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-secondary">Secondary</span>{% endset %}
|
||||
{{ code_preview("badge-secondary", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-destructive">Destructive</span>{% endset %}
|
||||
{{ code_preview("badge-destructive", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-outline">Outline</span>{% endset %}
|
||||
{{ code_preview("badge-outline", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
With icon
|
||||
</span>{% endset %}
|
||||
{{ code_preview("badge-with-icon", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<a href="#" class="badge-outline">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>{% endset %}
|
||||
{{ code_preview("badge-link", code) }}
|
||||
@@ -1,98 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Breadcrumb
|
||||
description: Displays the path to the current resource using a hierarchy of links.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated breadcrumb component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/dropdown-menu.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerDropdownMenu(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{% set code_html %}
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="demo-breadcrumb-menu",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<section class="prose">
|
||||
<p>This example uses some simple Tailwind CSS styles along with the <a href="/components/dropdown-menu">Dropdown Menu</a>.</p>
|
||||
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
|
||||
Individual JS file (select.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="alert-dialog", tabsets=tabsets) }}
|
||||
@@ -1,141 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Button
|
||||
description: Displays a button or a component that looks like a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Primary
|
||||
id: example-primary
|
||||
- label: Secondary
|
||||
id: example-secondary
|
||||
- label: Destructive
|
||||
id: example-destructive
|
||||
- label: Outline
|
||||
id: example-outline
|
||||
- label: Ghost
|
||||
id: example-ghost
|
||||
- label: Link
|
||||
id: example-link
|
||||
- label: Icon
|
||||
id: example-icon
|
||||
- label: With Icon
|
||||
id: example-with-icon
|
||||
- label: Loading
|
||||
id: example-loading
|
||||
- label: Large
|
||||
id: example-large
|
||||
- label: Small
|
||||
id: example-small
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<button class="btn">Button</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can use one of the <code>btn</code> classes to style any element as a button:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variants</b>:
|
||||
<ul>
|
||||
<li><code>btn</code> or <code>btn-primary</code> for primary
|
||||
buttons,</li>
|
||||
<li><code>btn-secondary</code> for secondary buttons.</li>
|
||||
<li><code>btn-destructive</code> for destructive buttons.</li>
|
||||
<li><code>btn-outline</code> for outline buttons.</li>
|
||||
<li><code>btn-ghost</code> for ghost buttons.</li>
|
||||
<li><code>btn-link</code> for link buttons.</li>
|
||||
<li><code>btn-icon</code> for icon buttons. This can be combined with other variants, for example <code>btn-icon-destructive</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Sizes</b>:
|
||||
<ul>
|
||||
<li><code>btn-sm</code> for small buttons.</li>
|
||||
<li><code>btn-lg</code> for large buttons.</li>
|
||||
<li>You can combine sizes with any variant, for example <code>btn-lg-destructive</code> or <code>btn-sm-icon-outline</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-primary"><a href="#example-primary">Primary</a></h3>
|
||||
|
||||
{% set code %}<button class="btn">Primary</button>{% endset %}
|
||||
{{ code_preview("button-primary", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-secondary">Button</button>{% endset %}
|
||||
{{ code_preview("button-secondary", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-destructive">Destructive</button>{% endset %}
|
||||
{{ code_preview("button-destructive", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline">Outline</button>{% endset %}
|
||||
{{ code_preview("button-outline", code) }}
|
||||
|
||||
<h3 id="example-ghost"><a href="#example-ghost">Ghost</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-ghost">Ghost</button>{% endset %}
|
||||
{{ code_preview("button-ghost", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-link">Link</button>{% endset %}
|
||||
{{ code_preview("button-link", code) }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-icon-outline">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-icon", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With Icon</a></h3>
|
||||
|
||||
{% set code %}<button class="btn">
|
||||
{% lucide "send" %}
|
||||
Send email
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-with-icon", code) }}
|
||||
|
||||
<h3 id="example-loading"><a href="#example-loading">Loading</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-loading", code) }}
|
||||
|
||||
<h3 id="example-large"><a href="#example-large">Large</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-lg">
|
||||
{% lucide "shopping-cart" %}
|
||||
Buy
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-large", code) }}
|
||||
|
||||
<h3 id="example-small"><a href="#example-small">Small</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-sm-icon-destructive">
|
||||
{% lucide "trash" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-small", code) }}
|
||||
@@ -1,141 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Card
|
||||
description: Displays a card with header, content, and footer.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: With content and footer
|
||||
id: example-with-content-and-footer
|
||||
- label: With image
|
||||
id: example-with-image
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<div class="card w-full">
|
||||
<header>
|
||||
<h2>Login to your account</h2>
|
||||
<p>Enter your details below to login to your account</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-card-form-email">Email</label>
|
||||
<input type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ml-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input type="password" id="demo-card-form-password">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer class="flex flex-col items-center gap-2">
|
||||
<button type="button" class="btn w-full">Login</button>
|
||||
<button type="button" class="btn-outline w-full">Login with Google</button>
|
||||
<p class="mt-4 text-center text-sm">Don't have an account? <a href="#" class="underline-offset-4 hover:underline">Sign up</a></p>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>card</code> class to any element and then define any of the children elements:</p>
|
||||
<ul>
|
||||
<li>A <code><header></code> for the card header, which can contain a <code><h2></code> for the title and a <code><p></code> for the description.</li>
|
||||
<li>A <code><section></code> for the card content.</li>
|
||||
<li>A <code><footer></code> for the card footer.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<header>
|
||||
<h2>Card Title</h2>
|
||||
<p>Card Description</p>
|
||||
</header>
|
||||
<section>
|
||||
<p>Card Content</p>
|
||||
</section>
|
||||
<footer>
|
||||
<p>Card Footer</p>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-with-content-and-footer"><a href="#example-with-content-and-footer">With content and footer</a></h3>
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<header>
|
||||
<h2>Meeting Notes</h2>
|
||||
<p>Transcript from the meeting with the client.</p>
|
||||
</header>
|
||||
<section class="text-sm">
|
||||
<p>Client requested dashboard redesign with focus on mobile responsiveness.</p>
|
||||
<ol class="mt-4 flex list-decimal flex-col gap-2 pl-6">
|
||||
<li>New analytics widgets for daily/weekly metrics</li>
|
||||
<li>Simplified navigation menu</li>
|
||||
<li>Dark mode support</li>
|
||||
<li>Timeline: 6 weeks</li>
|
||||
<li>Follow-up meeting scheduled for next Tuesday</li>
|
||||
</ol>
|
||||
</section>
|
||||
<footer class="flex items-center">
|
||||
<div class="flex -space-x-2 [&_img]:ring-card [&_img]:ring-2 [&_img]:grayscale [&_img]:size-8 [&_img]:shrink-0 [&_img]:object-cover [&_img]:rounded-full">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png">
|
||||
<img alt="@shadcn" src="https://github.com/shadcn.png">
|
||||
<img alt="@adamwathan" src="https://github.com/adamwathan.png">
|
||||
</div>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-with-content-and-footer", code) }}
|
||||
|
||||
<h3 id="example-with-image"><a href="#example-with-image">With image</a></h3>
|
||||
|
||||
{% set code %}<div class="card">
|
||||
<header>
|
||||
<h2>Is this an image?</h2>
|
||||
<p>This is a card with an image.</p>
|
||||
</header>
|
||||
<section class="px-0">
|
||||
<img
|
||||
alt="Photo by Drew Beamer"
|
||||
loading="lazy"
|
||||
width="500"
|
||||
height="500"
|
||||
class="aspect-video object-cover" style="color:transparent"
|
||||
srcset="
|
||||
https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=640&q=75 1x,
|
||||
https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=1080&q=75 2x
|
||||
"
|
||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80&w=1080&q=75"
|
||||
/>
|
||||
</section>
|
||||
<footer class="flex items-center gap-2">
|
||||
<span class="badge-outline">
|
||||
{% lucide "bed" %}
|
||||
1
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "bath" %}
|
||||
2
|
||||
</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "land-plot" %}
|
||||
350m²
|
||||
</span>
|
||||
<span class="ml-auto font-medium tabular-nums">
|
||||
$135,000
|
||||
</span>
|
||||
</footer>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("card-with-image", code) }}
|
||||
@@ -1,101 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Checkbox
|
||||
description: A control that allows the user to toggle between checked and not checked.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: With text
|
||||
id: example-with-text
|
||||
- label: Form
|
||||
id: example-form
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input">
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{{ code_preview("checkbox", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Add the <code>input</code> class to your <code><input type="radio"></code> elements or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). You can also set the <code>aria-invalid</code> attribute to <code>true</code> to make the input invalid.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input" disabled>
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{{ code_preview("checkbox-disabled", code) }}
|
||||
|
||||
<h3 id="example-with-text"><a href="#example-with-text">With text</a></h3>
|
||||
|
||||
{% set code %}<div class="flex items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-with-text" class="input">
|
||||
<div class="grid gap-2">
|
||||
<label for="checkbox-with-text" class="label">Accept terms and conditions</label>
|
||||
<p class="text-muted-foreground text-sm">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-with-text", code) }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
|
||||
<input type="checkbox" id="checkbox-form-1">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="checkbox-form-1" class="leading-snug">Use different settings for my mobile devices</label>
|
||||
<p class="text-muted-foreground text-sm leading-snug">You can manage your mobile notifications in the mobile settings page.</p>
|
||||
</div>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code) }}
|
||||
|
||||
{% set code %}<form class="form flex flex-col gap-4">
|
||||
<header>
|
||||
<label for="demo-form-checkboxes" class="text-base leading-normal">Sidebar</label>
|
||||
<p class="text-muted-foreground text-sm">Select the items you want to display in the sidebar.</p>
|
||||
</header>
|
||||
<fieldset id="demo-form-checkboxes" class="flex flex-col gap-2">
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="1" checked>
|
||||
Recents
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="2" checked>
|
||||
Home
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="3">
|
||||
Applications
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="4">
|
||||
Desktop
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="5">
|
||||
Download
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="6">
|
||||
Documents
|
||||
</label>
|
||||
</fieldset>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code) }}
|
||||
@@ -1,214 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Combobox
|
||||
description: Autocomplete input and command palette with a list of suggestions.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + Javascript
|
||||
id: usage-default
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Scrollable
|
||||
id: example-scrollable
|
||||
- label: Top side
|
||||
id: example-top
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% set code_html %}
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search framework...",
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/select.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerSelect(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("combobox", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<div class="prose">
|
||||
<p>Combobox uses the same Alpine.js code and markup as the <a href="/components/select">Select</a> component, the only difference being the search box at the top of the listbox.</p>
|
||||
<p>You can set up a few additional options on the <code><div data-popover></code> element:</p>
|
||||
<ul>
|
||||
<li><code>data-side</code> can be set to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the popover.</li>
|
||||
<li><code>data-align</code> can be set to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the popover.</li>
|
||||
<li><code>data-empty</code> to set up the message when the search is empty.</li>
|
||||
</ul>
|
||||
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
|
||||
Individual JS file (select.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="combobox", tabsets=tabsets) }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">select()</code> Nunjucks or Jinja macro for this component. If you use one of the macros, make sure to set <code>is_combobox</code> to <code>True</code> (or <code>true</code> for Nunjucks).</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/select.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/select.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call select(
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true
|
||||
) %}
|
||||
<div role="option" data-value="nextjs">Next.js</div>
|
||||
<div role="option" data-value="sveltekit">SvelteKit</div>
|
||||
<div role="option" data-value="nuxtjs">Nuxt.js</div>
|
||||
<div role="option" data-value="remix">Remix</div>
|
||||
<div role="option" data-value="astro">Astro</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% set options_timezones = [
|
||||
{
|
||||
label: "Americas",
|
||||
timezones: [
|
||||
{ value: "America/New_York", label: "(GMT-5) New York" },
|
||||
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
||||
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
||||
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
||||
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
||||
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Europe",
|
||||
timezones: [
|
||||
{ value: "Europe/London", label: "(GMT+0) London" },
|
||||
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
||||
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
||||
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
||||
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
||||
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Asia/Pacific",
|
||||
timezones: [
|
||||
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
||||
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
||||
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
||||
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
||||
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
||||
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" }
|
||||
]
|
||||
}
|
||||
] %}
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
content_attrs={"class": "w-72"},
|
||||
listbox_attrs={"data-empty": "No timezone found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search timezone...",
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div class="max-h-64 overflow-y-auto scrollbar">
|
||||
{% for group in options_timezones %}
|
||||
<div role="group" aria-labelledby="demo-combobox-timezones-group-{{ loop.index0 }}">
|
||||
<span id="demo-combobox-timezones-group-{{ loop.index0 }}" role="heading">{{ group.label }}</span>
|
||||
{% for timezone in group.timezones %}
|
||||
<div role="option" data-value="{{ timezone.value}}">
|
||||
{{ timezone.label }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="option">
|
||||
{% lucide "circle-plus" %}
|
||||
Create timezone
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("combobox-with-groups", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-top"><a href="#example-top">Top side</a></h3>
|
||||
|
||||
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search framework...",
|
||||
register_on=["alpine:init", "htmx:afterSwap"],
|
||||
content_attrs={"data-side": "top"}
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}{% endset %}
|
||||
{{ code_preview("combobox", code | prettyHtml) }}
|
||||
@@ -1,206 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Dialog
|
||||
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + Javascript
|
||||
id: usage-default
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Scrollable content
|
||||
id: example-scrollable-content
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dialog.njk" import dialog %}
|
||||
|
||||
{% set code_html %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn" @click="open = false">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" x-ref="focusOnOpen" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/dialog.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerDialog(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dialog", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component is structured as such:</p>
|
||||
<ul>
|
||||
<li>A <code><div class="dialog"></code> which wraps around the entire component and holds it state (e.g. open/close).</li>
|
||||
<li>A <code><button></code> that acts as the trigger to open or close the popover.</li>
|
||||
<li>A <code><div role="dialog"></code> that holds the dialog itself. It also acts as the backdrop for the dialog.</li>
|
||||
<li>Inside of it, we have:
|
||||
<ul>
|
||||
<li>A <code><header></code> that contains the title (<code><h2></code>) and description (<code><p></code>) of the dialog.</li>
|
||||
<li>A <code><section></code> for the content.</li>
|
||||
<li>A <code><footer></code> that usually contains actions.</li>
|
||||
<li>A close <code><button></code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/dialog.js" target="_blank">
|
||||
Individual JS file (dialog.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="combobox", tabsets=tabsets) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component will dispatch <code>dialog:opened</code> and <code>dialog:closed</code> events when the dialog is opened or closed. You can also dispatch <code>dialog:open</code> and <code>dialog:close</code> events to open or close the dialog programmatically. You will need the element to have an <code>id</code> to be able to target it (e.g. <code><div id="my-dialog" class="dialog"></code>):</p>
|
||||
</section>
|
||||
|
||||
{% set code_dispatch %}<button type="button" x-init @click="$dispatch('dialog:open', { id: 'my-dialog' })">
|
||||
Open #my-dialog
|
||||
</button>{% endset %}
|
||||
{{ code_block(code_dispatch | prettyHtml, "html") }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dialog()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/dialog.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn" @click="open = false">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-username">Username</label>
|
||||
<input type="text" value="@peduarte" id="demo-dialog-edit-profile-username" />
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-scrollable-content"><a href="#example-scrollable-content">Scrollable content</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-example",
|
||||
title="Scrollable Content",
|
||||
description="This is a dialog with scrollable content.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
content_body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div class="space-y-4 text-sm">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
{{ code_preview("dialog-scrollable-content", code | prettyHtml) }}
|
||||
@@ -1,366 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Dropdown Menu
|
||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + Javascript
|
||||
id: usage-default
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Checkboxes
|
||||
id: example-checkboxes
|
||||
- label: Radio Group
|
||||
id: example-radio-group
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
{% set code_html %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">My Account</span>
|
||||
<button type="button" role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
GitHub
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Support
|
||||
</button>
|
||||
<button type="button" role="menuitem" disabled>
|
||||
API
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/dropdown-menu.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerDropdownMenu(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("dropdown-menu", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component is structured as such:</p>
|
||||
<ul>
|
||||
<li>A <code><div class="popover"></code> which wraps around the entire component and holds it state (e.g. open/close).</li>
|
||||
<li>A <code><button></code> that acts as the trigger to open or close the popover.</li>
|
||||
<li>A <code><div data-popover></code> that holds the content of the popover.</li>
|
||||
<li>Inside of the <code><div data-popover></code> is a <code><nav role="menu"></code> that contains a combination of:
|
||||
<ul>
|
||||
<li><code><button role="menuitem"></code> for menu items.</li>
|
||||
<li><code><button role="menuitemcheckbox"></code> for menu items with checkboxes.</li>
|
||||
<li><code><button role="menuitemradio"></code> for menu items with radio buttons.</li>
|
||||
<li><code><hr role="separator"></code> to display a horizontal separator.</li>
|
||||
<li><code><div role="group"></code> to group options.</li>
|
||||
<li><code><span role="heading"></code> for group headings.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
<p>As with the <a href="/components/popover">Popover</a> component, you can set up a few additional options on the <code><div data-popover></code> element:</p>
|
||||
<ul>
|
||||
<li><code>data-side</code> can be set to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the popover.</li>
|
||||
<li><code>data-align</code> can be set to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the popover.</li>
|
||||
</ul>
|
||||
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/dropdown-menu.js" target="_blank">
|
||||
Individual JS file (dropdown-menu.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="dropdown-menu", tabsets=tabsets) }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dropdown_menu()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/dropdown-menu.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dropdown-menu.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">My Account</span>
|
||||
<button type="button" role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
GitHub
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Support
|
||||
</button>
|
||||
<button type="button" role="menuitem" disabled>
|
||||
API
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
{% endcall %}{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-checkboxes"><a href="#example-checkboxes">Checkboxes</a></h3>
|
||||
|
||||
{% set code %}{% call dropdown_menu(
|
||||
id="dropdown-menu-checkboxes",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</span>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "settings" %}
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="appearance-options">
|
||||
<span id="appearance-options" role="heading">Appearance</span>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
x-data="{ checked: true }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
disabled
|
||||
x-data="{ checked: false }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
x-data="{ checked: false }"
|
||||
@click="checked = !checked"
|
||||
:aria-checked="checked"
|
||||
>
|
||||
{% lucide "check", {
|
||||
"class": "invisible group-aria-checked:visible",
|
||||
"aria-hidden": "true",
|
||||
"focusable": "false"
|
||||
} %}
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
{% endcall %}{% endset %}
|
||||
{{ code_preview("dropdown-menu-checkboxes", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-radio-group"><a href="#example-radio-group">Radio Group</a></h3>
|
||||
|
||||
{% set code %}{% call dropdown_menu(
|
||||
id="dropdown-menu-radio-group",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
|
||||
<span id="position-options" role="heading">Panel Position</span>
|
||||
<hr role="separator">
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'top'"
|
||||
:aria-checked="checked == 'top'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'bottom'"
|
||||
:aria-checked="checked == 'bottom'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@click="checked = 'right'"
|
||||
:aria-checked="checked == 'right'"
|
||||
>
|
||||
<div class="size-4 flex items-center justify-center">
|
||||
<div
|
||||
class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible"
|
||||
aria-hidden="true",
|
||||
focusable="false"
|
||||
></div>
|
||||
</div>
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}{% endset %}
|
||||
{{ code_preview("dropdown-menu", code | prettyHtml) }}
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Input
|
||||
description: Displays a form input field or a component that looks like an input field.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<form class="form grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-text">Username</label>
|
||||
<input type="text" id="demo-form-text" placeholder="hunvreus">
|
||||
<p class="text-muted-foreground text-sm">This is your public display name.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-select">Email</label>
|
||||
<select id="demo-form-select">
|
||||
<option value="bob@example.com">m@example.com</option>
|
||||
<option value="alice@example.com">m@google.com</option>
|
||||
<option value="john@example.com">m@support.com</option>
|
||||
</select>
|
||||
<p class="text-muted-foreground text-sm">You can manage email addresses in your email settings.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-text">Bio</label>
|
||||
<textarea id="demo-form-textarea" placeholder="I like to..." rows="3"></textarea>
|
||||
<p class="text-muted-foreground text-sm">You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-date">Date of birth</label>
|
||||
<input type="date" id="demo-form-date">
|
||||
<p class="text-muted-foreground text-sm">Your date of birth is used to calculate your age.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<label for="demo-form-radio">Notify me about...</label>
|
||||
<fieldset id="demo-form-radio" class="grid gap-3">
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="1" checked>All new messages</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="2">Direct messages and mentions</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="3" >Nothing</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<section class="grid gap-4">
|
||||
<h3 class="text-lg font-medium">Email Notifications</h3>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label for="demo-form-switch" class="leading-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch" role="switch">
|
||||
</div>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5 opacity-60">
|
||||
<label for="demo-form-switch-disabled" class="leading-normal">Security emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about your account security.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch-disabled" role="switch" disabled>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Adding the <code>form</code> class to the parent of any form element (<code><label></code>, <code><input></code>, <code><textarea></code>, <code><select></code>) will automatically apply the style you would get from adding <code>label</code>, <code>input</code>, <code>textarea</code>, or <code>select</code> classes. Read more about <a href="/components/label">Label</a>, <a href="/components/input">Input</a>, <a href="/components/textarea">Textarea</a>, and <a href="/components/select">Select</a>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
@@ -1,90 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Input
|
||||
description: Displays a form input field or a component that looks like an input field.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: With label
|
||||
id: example-with-label
|
||||
- label: With text
|
||||
id: example-with-text
|
||||
- label: With button
|
||||
id: example-with-button
|
||||
- label: Form
|
||||
id: example-form
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<input class="input" type="email" placeholder="Email">{% endset %}
|
||||
{{ code_preview("input", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>input</code> class to your <code><input></code> element or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). You can also set the <code>aria-invalid</code> attribute to <code>true</code> to make the input invalid.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
{{ code_preview("input-default", code) }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<input class="input" type="email" placeholder="Email" aria-invalid="true">{% endset %}
|
||||
{{ code_preview("input-invalid", code) }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<input class="input" type="email" placeholder="Email" disabled>{% endset %}
|
||||
{{ code_preview("input-disabled", code) }}
|
||||
|
||||
<h3 id="example-with-label"><a href="#example-with-label">With label</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label for="input-with-label" class="label">Label</label>
|
||||
<input class="input" id="input-with-label" type="email" placeholder="Email">
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-with-label", code) }}
|
||||
|
||||
<h3 id="example-with-text"><a href="#example-with-text">With text</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label for="input-with-text" class="label">Label</label>
|
||||
<input class="input" id="input-with-text" type="email" placeholder="Email">
|
||||
<p class="text-muted-foreground text-sm">Fill in your email address.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("input-with-text", code) }}
|
||||
|
||||
<h3 id="example-with-button"><a href="#example-with-button">With button</a></h3>
|
||||
|
||||
{% set code %}<div class="flex items-center space-x-2">
|
||||
<input class="input" type="email" placeholder="Email">
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-form", code) }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form space-y-6 w-full">
|
||||
<div class="grid gap-3">
|
||||
<label for="input-form" class="label">Username</label>
|
||||
<input class="input" id="input-form" type="text" placeholder="hunvreus">
|
||||
<p class="text-muted-foreground text-sm">This is your public display name.</p>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code, "w-full max-w-sm") }}
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Label
|
||||
description: Renders an accessible label associated with controls.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input">
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{{ code_preview("label", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>label</code> class to your <code><label></code> element or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). Labels' style may be affected by the presence of a disabled peer or child element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<label class="label" for="email">Your email address</label>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label class="label" for="email">Email</label>
|
||||
<input class="input" id="email" type="email" placeholder="Email" disabled>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("label-disabled", code) }}
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Pagination
|
||||
description: Pagination with page navigation, next and previous links.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated pagination component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
|
||||
<ul class="flex flex-row items-center gap-1">
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">{% lucide "chevron-left" %} Previous</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-outline">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="size-9 flex items-center justify-center">{% lucide "ellipsis", { "class": "size-4 shrink-0" } %}</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">Next {% lucide "chevron-right" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>{% endset %}
|
||||
{{ code_preview("pagination", code, "max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>A pagination is fairly simple to implement with Tailwind using the Basecoat button classes:</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
@@ -1,169 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Popover
|
||||
description: Displays rich content in a portal, triggered by a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + Javascript
|
||||
id: usage-default
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
{% set code_html %}
|
||||
{% call popover(
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-80 p-4"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
<h4 class="leading-none font-medium">Dimensions</h4>
|
||||
<p class="text-muted-foreground text-sm">Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="form grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-width">Width</label>
|
||||
<input type="text" id="demo-popover-width" value="100%" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-width">Max. width</label>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/popover.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerPopover(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("popover", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component is structured as such:</p>
|
||||
|
||||
<ul>
|
||||
<li>A <code><div class="popover"></code> which wraps around the entire component and holds it state (e.g. open/close).</li>
|
||||
<li>A <code><button></code> that acts as the trigger to open or close the popover.</li>
|
||||
<li>A <code><div data-popover></code> that holds the content of the popover.</li>
|
||||
</ul>
|
||||
|
||||
<p>You can set up a few additional options on the <code><div data-popover></code> element:</p>
|
||||
|
||||
<ul>
|
||||
<li><code>data-side</code> can be set to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the popover.</li>
|
||||
<li><code>data-align</code> can be set to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the popover.</li>
|
||||
</ul>
|
||||
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/popover.js" target="_blank">
|
||||
Individual JS file (popover.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="popover", tabsets=tabsets) }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">popover()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/popover.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/popover.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call popover(
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-80 p-4"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
<h4 class="leading-none font-medium">Dimensions</h4>
|
||||
<p class="text-muted-foreground text-sm">Set the dimensions for the layer.</p>
|
||||
</header>
|
||||
<form class="form grid gap-2">
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-width">Width</label>
|
||||
<input type="text" id="demo-popover-width" value="100%" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-width">Max. width</label>
|
||||
<input type="text" id="demo-popover-max-width" value="300px" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-height">Height</label>
|
||||
<input type="text" id="demo-popover-height" value="25px" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 items-center gap-4">
|
||||
<label for="demo-popover-max-height">Max. height</label>
|
||||
<input type="text" id="demo-popover-max-height" value="none" class="col-span-2 h-8"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Radio Group
|
||||
description: A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Form
|
||||
id: example-form
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<fieldset class="grid gap-3">
|
||||
<label class="label"><input type="radio" name="radio-group" value="default" class="input">Default</label>
|
||||
<label class="label"><input type="radio" name="radio-group" value="comfortable" class="input" checked>Comfortable</label>
|
||||
<label class="label"><input type="radio" name="radio-group" value="compact" class="input">Compact</label>
|
||||
</fieldset>{% endset %}
|
||||
{{ code_preview("radio-group", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>input</code> class to your <code><input type="radio"></code> elements or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). You can also set the <code>aria-invalid</code> attribute to <code>true</code> to make the input invalid.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form space-y-6 w-full">
|
||||
<div class="flex flex-col gap-3">
|
||||
<label for="demo-form-radio">Notify me about...</label>
|
||||
<fieldset id="demo-form-radio" class="grid gap-3">
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="1">All new messages</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="2">Direct messages and mentions</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="3" >Nothing</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("radio-group-form", code, "w-full max-w-sm") }}
|
||||
@@ -1,220 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Select
|
||||
description: Displays a list of options for the user to pick from—triggered by a button.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML
|
||||
id: usage-html
|
||||
- label: HTML + Javascript
|
||||
id: usage-js
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Scrollable
|
||||
id: example-scrollable
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: With icon
|
||||
id: example-with-icon
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
{% set code_native %}<select class="select w-[180px]">
|
||||
<optgroup label="Fruits">
|
||||
<option>Apple</option>
|
||||
<option>Banana</option>
|
||||
<option>Blueberry</option>
|
||||
<option>Grapes</option>
|
||||
<option>Pineapple</option>
|
||||
</select>{% endset %}
|
||||
{{ code_preview("select-native", code_native) }}
|
||||
|
||||
{% set code_html %}
|
||||
{% call select(
|
||||
name="select-js",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
<div role="option" data-value="pineapple">Grapes</div>
|
||||
<div role="option" data-value="pineapple">Pineapple</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/select.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerSelect(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select", code_script | prettyHtml ~ code_html | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-html"><a href="#usage-html">HTML</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>If you use a <code><select></code> element, just add the <code>select</code> class to it or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>).</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code_native) }}
|
||||
|
||||
<h3 id="usage-js"><a href="#usage-js">HTML + Javascript</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>If you need to do more than what <code><select></code> allows (e.g. HTML options), you can use this component. It is structured as such:</p>
|
||||
|
||||
<ul>
|
||||
<li>A <code><div class="popover"></code> which wraps around the entire component and holds it state (e.g. open/close).</li>
|
||||
<li>A <code><button></code> that acts as the trigger to open or close the popover.</li>
|
||||
<li>A <code><div data-popover></code> that holds the content of the popover.</li>
|
||||
<li>Inside of the <code><div data-popover></code> is a <code><nav role="listbox"></code> that contains a combination of:
|
||||
<ul>
|
||||
<li><code><div role="option"></code> for the options with a <code>data-value</code> attribute.</li>
|
||||
<li><code><hr role="separator"></code> to display a horizontal separator.</li>
|
||||
<li><code><div role="group"></code> to group options.</li>
|
||||
<li><code><span role="heading"></code> for group headings.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>An <code><input type="hidden"></code> to hold the value of the field (if needed).</li>
|
||||
</ul>
|
||||
|
||||
<p>As with the <a href="/components/popover">Popover</a> component, you can set up a few additional options on the <code><div data-popover></code> element:</p>
|
||||
|
||||
<ul>
|
||||
<li><code>data-side</code> can be set to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the popover.</li>
|
||||
<li><code>data-align</code> can be set to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the popover.</li>
|
||||
</ul>
|
||||
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank">
|
||||
Individual JS file (select.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="select", tabsets=tabsets) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The component will dispatch a <code>select:change</code> event when a value is selected, along with the value and label of the selected option in the <code>detail</code> object.</p>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">dropdown_menu()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/dialog.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/dialog.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% call select(name="select-js") %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
<div role="option" data-value="pineapple">Grapes</div>
|
||||
<div role="option" data-value="pineapple">Pineapple</div>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-scrollable"><a href="#example-scrollable">Scrollable</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
{% for i in range(0, 99) %}
|
||||
<div role="option" data-value="item-{{ i }}">Item {{ i }}</div>
|
||||
{% endfor %}
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-scrollable", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={"disabled": "disabled"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="option" data-value="disabled">Disabled</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-disabled", code | prettyHtml) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={'class': 'w-[180px]'},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div type="button" role="option" data-value="bar">
|
||||
{% lucide "chart-bar", {"class": "text-muted-foreground"} %}
|
||||
Bar
|
||||
</div>
|
||||
<div type="button" role="option" data-value="line">
|
||||
{% lucide "chart-line", {"class": "text-muted-foreground"} %}
|
||||
Line
|
||||
</div>
|
||||
<div type="button" role="option" data-value="pie">
|
||||
{% lucide "chart-pie", {"class": "text-muted-foreground"} %}
|
||||
Pie
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("select-scrollable", code | prettyHtml) }}
|
||||
@@ -1,151 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Sidebar
|
||||
description: A composable, themeable and customizable sidebar component.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + Javascript
|
||||
id: usage-default
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "sidebar.njk" import sidebar %}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<section class="prose">
|
||||
<p>To use a sidebar, you will need the sidebar itself and a sibling container for the content of the page (e.g. <code><main></code>), with the following structure for the sidebar:</p>
|
||||
|
||||
<ul>
|
||||
<li>A <code><div class="sidebar"></code> which wraps around the entire component and holds it state (e.g. open/close). It accepts an optional <code>data-side</code> set to <code>left</code>, or <code>right</code> to specify the side of the sidebar (defaults to <code>left</code>).</li>
|
||||
<li>A <code><nav></code> that contains the actual sidebar with the following children:
|
||||
<ul>
|
||||
<li>A <code><header></code> and <code><footer></code> for the header and footer of the sidebar (fixed position).</li>
|
||||
<li>A <code><section></code> for the main navigation list. To add links, you <b>must</b> wrap them in a <code><div role="group"></code> first. These groups can contain <code><h3"></code> for group headings and lists (i.e. <code><ul></code>) of links or buttons. You can also use <code><details></code> to wrap around collapsbile sections.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Any elememt on the page can toggle, open or close the sidebar by dispatching the <code>sidebar:toggle</code>, <code>sidebar:open</code> or <code>sidebar:close</code> events.</p>
|
||||
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/sidebar.js" target="_blank">
|
||||
Individual JS file (sidebar.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set code_html %}
|
||||
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
|
||||
{% set icon_bot %}{% lucide "bot" %}{% endset %}
|
||||
{% set icon_settings %}{% lucide "settings" %}{% endset %}
|
||||
|
||||
{% set menu = [
|
||||
{ type: "group", label: "Getting started", items: [
|
||||
{ label: "Playground", url: "#", icon: icon_square_terminal },
|
||||
{ label: "Models", url: "#", icon: icon_bot },
|
||||
{ label: "Settings", type: "submenu", icon: icon_settings, items: [
|
||||
{ label: "General", url: "#" },
|
||||
{ label: "Team", url: "#" },
|
||||
{ label: "Billing", url: "#" },
|
||||
{ label: "Limits", url: "#" }
|
||||
] }
|
||||
]}
|
||||
] %}
|
||||
|
||||
{{ sidebar(
|
||||
label="Sidebar navigation",
|
||||
content_attrs={
|
||||
"class": "scrollbar"
|
||||
},
|
||||
menu=menu
|
||||
) }}
|
||||
<main>
|
||||
<button type="button" @click="$dispatch('sidebar:toggle')">Toggle sidebar</button>
|
||||
<h1>Content</h1>
|
||||
</main>
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/sidebar.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerSidebar(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="sidebar", tabsets=tabsets) }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">sidebar()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/sidebar.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/sidebar.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set menu = [
|
||||
{ type: "group", label: "Getting started", items: [
|
||||
{ label: "Playground", url: "#" },
|
||||
{ label: "Models", url: "#" },
|
||||
{ label: "Settings", type: "submenu", items: [
|
||||
{ label: "General", url: "#" },
|
||||
{ label: "Team", url: "#" },
|
||||
{ label: "Billing", url: "#" },
|
||||
{ label: "Limits", url: "#" }
|
||||
] }
|
||||
]}
|
||||
] %}
|
||||
|
||||
{{ sidebar(
|
||||
label="Sidebar navigation",
|
||||
menu=menu
|
||||
) }}
|
||||
<main>
|
||||
<h1>Content</h1>
|
||||
</main>
|
||||
{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Skeleton
|
||||
description: Used to show a placeholder while content is loading.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Card
|
||||
id: example-card
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated skeleton component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}<div class="flex items-center gap-4">
|
||||
<div class="bg-accent animate-pulse size-10 shrink-0 rounded-full"></div>
|
||||
<div class="grid gap-2">
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-[150px]"></div>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-[100px]"></div>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton", code | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<p class="prose">Simply use the <code>animate-pulse</code> class to create a skeleton.</p>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-card"><a href="#example-card">Card</a></h3>
|
||||
|
||||
{% set code %}<div class="card w-full @md:w-auto @md:min-w-sm">
|
||||
<header>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-2/3"></div>
|
||||
<div class="bg-accent animate-pulse rounded-md h-4 w-1/2"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="bg-accent animate-pulse rounded-md aspect-square w-full"></div>
|
||||
</section>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("skeleton-card", code | prettyHtml, "w-full max-w-md") }}
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Slider
|
||||
description: An input where the user selects a value from within a given range.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<input
|
||||
type="range"
|
||||
class="input w-full"
|
||||
min="0"
|
||||
max="100"
|
||||
value="50"
|
||||
x-data="{
|
||||
updatePercent(el) {
|
||||
const min = parseFloat(el.min || 0);
|
||||
const max = parseFloat(el.max || 100);
|
||||
const value = parseFloat(el.value);
|
||||
const percent = (max === min) ? 0 : ((value - min) / (max - min)) * 100;
|
||||
el.style.setProperty('--slider-value', `${percent}%`);
|
||||
}
|
||||
}"
|
||||
x-init="updatePercent($el)"
|
||||
@input="updatePercent($el)"
|
||||
>{% endset %}
|
||||
{{ code_preview("slider", code, "w-full max-w-sm") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can style an <code><input type="range"></code> element by either adding the <code>input</code> class or having a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). However, the range input also requries some Javacript code to be able to paint the left side of the slider.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Switch
|
||||
description: A control that allows the user to toggle between checked and not checked.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Form
|
||||
id: example-form
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<label class="label">
|
||||
<input type="checkbox" name="switch" role="switch" class="input">
|
||||
Airplane Mode
|
||||
</label>{% endset %}
|
||||
{{ code_preview("switch", code) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>You can style a <code><input type="checkbox"></code> element as a switch by adding a <code>role="switch"</code> attribute, and adding the <code>input</code> class or having a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>).</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<input type="checkbox" role="switch" class="input">{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form grid gap-4">
|
||||
<h3 class="text-lg font-medium">Email Notifications</h3>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label for="demo-form-switch" class="leading-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch" role="switch">
|
||||
</div>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5 opacity-60">
|
||||
<label for="demo-form-switch-disabled" class="leading-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch-disabled" role="switch" disabled>
|
||||
</div>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("switch-form", code, "w-full") }}
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Table
|
||||
description: A responsive table component.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<table class="table">
|
||||
<caption>A list of your recent invoices.</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Invoice</th>
|
||||
<th>Status</th>
|
||||
<th>Method</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-medium">INV001</td>
|
||||
<td>Paid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$250.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV002</td>
|
||||
<td>Pending</td>
|
||||
<td>PayPal</td>
|
||||
<td class="text-right">$150.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV003</td>
|
||||
<td>Unpaid</td>
|
||||
<td>Bank Transfer</td>
|
||||
<td class="text-right">$350.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV004</td>
|
||||
<td>Paid</td>
|
||||
<td>Paypal</td>
|
||||
<td class="text-right">$450.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV005</td>
|
||||
<td>Paid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$550.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV006</td>
|
||||
<td>Pending</td>
|
||||
<td>Bank Transfer</td>
|
||||
<td class="text-right">$200.00</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium">INV007</td>
|
||||
<td>Unpaid</td>
|
||||
<td>Credit Card</td>
|
||||
<td class="text-right">$300.00</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3">Total</td>
|
||||
<td class="text-right">$2,500.00</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>{% endset %}
|
||||
{{ code_preview("table", code, "w-full") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>table</code> class to your table element.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<table class="table">
|
||||
<!-- Content of your table -->
|
||||
</table>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
@@ -1,215 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Tabs
|
||||
description: A set of layered sections of content—known as tab panels—that are displayed one at a time.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + Javascript
|
||||
id: usage-default
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
{% set code_html %}
|
||||
{% set account_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set password_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save Password</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set tabsets_demo = [
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets_demo,
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "class": "w-full" },
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/tabs.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerTabs(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{{ code_preview("tabs", code_script | prettyHtml ~ code_html | prettyHtml, "max-w-[300px] w-full") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<section class="prose">
|
||||
<p>The component is structured as such:</p>
|
||||
|
||||
<ul>
|
||||
<li>A <code><div class="tabs"></code> which wraps around the entire component and holds it state (e.g. open/close).</li>
|
||||
<li>A <code><nav role="tablist"></code> that holds the tab buttons (<code><button role="tab"></code>).</li>
|
||||
<li>A series of <code><div role="tabpanel"></code> that holds the tab panels corresponding to each tab button.</li>
|
||||
</ul>
|
||||
|
||||
<p>You can include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/tabs.js" target="_blank">
|
||||
Individual JS file (tabs.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="combobox", tabsets=tabsets) }}
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">tabs()</code> Nunjucks or Jinja macro for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/tabs.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/tabs.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code %}{% raw %}{% set account_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% set password_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save Password</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% set tabsets_demo = [
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
] %}
|
||||
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets_demo,
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "class": "w-full" },
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) }}{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
@@ -1,90 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Teaxtarea
|
||||
description: Displays a form textarea or a component that looks like a textarea.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Invalid
|
||||
id: example-invalid
|
||||
- label: Disabled
|
||||
id: example-disabled
|
||||
- label: With label
|
||||
id: example-with-label
|
||||
- label: With text
|
||||
id: example-with-text
|
||||
- label: With button
|
||||
id: example-with-button
|
||||
- label: Form
|
||||
id: example-form
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<textarea class="textarea" placeholder="Type your message here"></textarea>{% endset %}
|
||||
{{ code_preview("button", code, "w-full") }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>Simply add the <code>textarea</code> class to your <code><textarea></code> element or have a parent with the <code>form</code> class (<a href="/components/form">read more about form</a>). You can also set the <code>aria-invalid</code> attribute to <code>true</code> to make the textarea invalid.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
{{ code_preview("textarea-default", code, "w-full") }}
|
||||
|
||||
<h3 id="example-invalid"><a href="#example-invalid">Invalid</a></h3>
|
||||
|
||||
{% set code %}<textarea class="textarea" placeholder="Type your message here" aria-invalid="true"></textarea>{% endset %}
|
||||
{{ code_preview("textarea-invalid", code, "w-full") }}
|
||||
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<textarea class="textarea" placeholder="Type your message here" disabled></textarea>{% endset %}
|
||||
{{ code_preview("textarea-disabled", code, "w-full") }}
|
||||
|
||||
<h3 id="example-with-label"><a href="#example-with-label">With label</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label for="textarea-with-label" class="label">Label</label>
|
||||
<textarea id="textarea-with-label" class="textarea" placeholder="Type your message here"></textarea>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-with-label", code, "w-full") }}
|
||||
|
||||
<h3 id="example-with-text"><a href="#example-with-text">With text</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<label for="textarea-with-label" class="label">Label</label>
|
||||
<textarea id="textarea-with-label" class="textarea" placeholder="Type your message here"></textarea>
|
||||
<p class="text-muted-foreground text-sm">Type your message and press enter to send.</p>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-with-text", code, "w-full") }}
|
||||
|
||||
<h3 id="example-with-button"><a href="#example-with-button">With button</a></h3>
|
||||
|
||||
{% set code %}<div class="grid gap-3">
|
||||
<textarea id="textarea-with-label" class="textarea" placeholder="Type your message here"></textarea>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("textarea-form", code, "w-full") }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form space-y-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-form" class="label">Bio</label>
|
||||
<textarea id="textarea-form" placeholder="Tell us a bit about yourself"></textarea>
|
||||
<p class="text-muted-foreground text-sm">You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("textarea-form", code, "w-full max-w-sm") }}
|
||||
@@ -1,253 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Toast
|
||||
description: A succinct message that is displayed temporarily.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
children:
|
||||
- label: HTML + Javascript
|
||||
id: usage-default
|
||||
children:
|
||||
- label: Append a toast
|
||||
id: usage-append-toast
|
||||
- label: Use $toast()
|
||||
id: usage-use-magic-method
|
||||
- label: Jinja and Nunjucks
|
||||
id: usage-macro
|
||||
---
|
||||
|
||||
{% from "toast.njk" import toaster, toast %}
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code_htmx %}<button
|
||||
class="btn-outline"
|
||||
hx-trigger="click"
|
||||
hx-get="/fragments/toast/success"
|
||||
hx-target="#toaster"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
Toast from backend (with HTMX)
|
||||
</button>{% endset %}
|
||||
{{ code_preview("toast-htmx", code_htmx, "max-w-md") }}
|
||||
|
||||
{% set code_alpine %}<button
|
||||
class="btn-outline"
|
||||
@click='
|
||||
$toast({
|
||||
category: "success",
|
||||
title: "Success",
|
||||
description: "A success toast called from the front-end.",
|
||||
action: {
|
||||
label: "Dismiss",
|
||||
click: "open =false"
|
||||
}
|
||||
})
|
||||
'>
|
||||
Toast from front-end
|
||||
</button>
|
||||
{% endset %}
|
||||
{{ code_preview("toast-alpine", code_alpine, "max-w-md") }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">HTML + Javascript</a></h3>
|
||||
|
||||
<div class="alert my-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This component requires Javascript.</h2>
|
||||
</div>
|
||||
|
||||
<section class="prose">
|
||||
<p>To use toasts, you will first need to set up the toaster code to the end of your <code><body></code> (see HTML below).</p>
|
||||
|
||||
<p>The <code><template id="toast-template"></code> bit is only required if you plan on using the <code>$toast()</code> method (see below).</p>
|
||||
|
||||
<p>You will also need to include the Javascript code provided below, load it as an individual file or use the CLI. Some Alpine.js properties are also required on certain elements (e.g. <code class="text-xs">x-bind</code>, <code class="text-xs">x-data</code>, <code class="text-xs">@click</code>).</p>
|
||||
</section>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-js">
|
||||
Components with Javascript
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="/installation/#install-cli">
|
||||
Use the CLI
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/js/toast.js" target="_blank">
|
||||
Individual JS file (toast.js)
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
{% set code_script %}
|
||||
<script>
|
||||
{% fetchCode "src/js/popover.js" %}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerPopover(Alpine);
|
||||
});
|
||||
</script>
|
||||
{% endset %}
|
||||
|
||||
{% set code_html_toaster %}{{ toaster() }}{% endset %}
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "HTML", panel: code_block(code_html_toaster | prettyHtml, class="") },
|
||||
{ tab: "Javascipt (Alpine.js)", panel: code_block(code_script | prettyHtml, class="") }
|
||||
] %}
|
||||
{{ tabs(id="popover", tabsets=tabsets) }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Once that's in place, we can do one of two things:</p>
|
||||
|
||||
<ul>
|
||||
<li>Append a <code><div class="toast"></code> to the toaster.</li>
|
||||
<li>Use the <code>$toast()</code> Alpine.js "magic method".</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h4 id="usage-append-toast"><a href="#usage-append-toast">Append a toast</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>To display a toast, just append the following to the toaster:</p>
|
||||
</section>
|
||||
|
||||
{% set code_toast_simple %}<div class="toast" x-data="toast" x-bind="$toastBindings">
|
||||
<div class="toast-content">
|
||||
<p>This is a toast.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
|
||||
{{ code_block(code_toast_simple | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>You can implement the layout you want within the toast, but here is the standard format used in the examples above:</p>
|
||||
</section>
|
||||
|
||||
{% set code_toast %}{{ toast(
|
||||
category="success",
|
||||
title="Success",
|
||||
description="A success toast called from the front-end.",
|
||||
action={
|
||||
label: "Dismiss",
|
||||
click: "close()"
|
||||
}
|
||||
) }}{% endset %}
|
||||
{{ code_block(code_toast | prettyHtml, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>When setting up <code>x-data</code>, you can pass a config object with `category` (e.g. "success", "error", "info", "warning") and `duration` (in milliseconds). By default, the duration will be 3 seconds, and 5 seconds for errors.</p>
|
||||
|
||||
<p>This approach is used in the first example with HTMX: when the button is clicked, we retrieve the a fragment that contains the markup for the toast and append it to <code>#toaster</code>.</p>
|
||||
</section>
|
||||
|
||||
{% set code_htmx %}<button
|
||||
class="btn-outline"
|
||||
hx-trigger="click"
|
||||
hx-get="/fragments/toast/success"
|
||||
hx-target="#toaster"
|
||||
hx-swap="beforeend"
|
||||
>
|
||||
Toast from backend (with HTMX)
|
||||
</button>{% endset %}
|
||||
|
||||
<h4 id="usage-use-magic-method"><a href="#usage-use-magic-method">Use $toast()</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>If you want to display a toast directly from the front-end, you can use the <code>$toast()</code> Alpine.js magic method. This is what we're doing in the second example above:</p>
|
||||
</section>
|
||||
|
||||
{% set code_magic_method %}<button
|
||||
class="btn-outline"
|
||||
@click='
|
||||
$toast({
|
||||
category: "success",
|
||||
title: "Success",
|
||||
description: "A success toast called from the front-end.",
|
||||
action: {
|
||||
label: "Dismiss",
|
||||
click: "open =false"
|
||||
}
|
||||
})
|
||||
'>
|
||||
Toast from front-end
|
||||
</button>
|
||||
{% endset %}
|
||||
{{ code_block(code_magic_method, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>The <code>$toast()</code> method accepts a config object with the following properties:</p>
|
||||
|
||||
<ul>
|
||||
<li><code>duration</code> (optional): the duration of the toast in milliseconds. By default, the duration will be 3 seconds, or 5 seconds if <code>category</code> is "error".</li>
|
||||
<li><code>category</code> (optional): "success", "error", "info", "warning" or any arbitrary string.</li>
|
||||
<li><code>title</code> (optional): the title of the toast.</li>
|
||||
<li><code>description</code> (optional): the description of the toast.</li>
|
||||
<li><code>action</code> (optional): an object to define an action button:
|
||||
<ul>
|
||||
<li><code>label</code>: the label of the action button.</li>
|
||||
<li><code>click</code> (optional): a string of Javascript to execute when the button is clicked.</li>
|
||||
<li><code>url</code> (optional): a URL to link to when the button is clicked.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>cancel</code> (optional): an object to define a cancel button (similar structure to action).</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h3 id="usage-macro"><a href="#usage-macro">Jinja and Nunjucks</a></h3>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can use the <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">toaster()</code> and <code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs">toast()</code> Nunjucks or Jinja macros for this component.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="/installation/#install-macros" target="_blank">
|
||||
Use Nunjucks or Jinja macros
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/jinja/toast.html.jinja" target="_blank">
|
||||
Jinja macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a class="badge-outline" href="https://github.com/hunvreus/basecoat/blob/main/src/nunjucks/toast.njk" target="_blank">
|
||||
Nunjucks macro
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set raw_code_toaster %}{% raw %}{% from "toast.njk" import toaster %}
|
||||
{{ toaster(
|
||||
toasts=[
|
||||
{
|
||||
type: "success",
|
||||
title: "Success",
|
||||
description: "A success toast called from the front-end.",
|
||||
action: { label: "Dismiss", click: "close()" }
|
||||
},
|
||||
{
|
||||
type: "info",
|
||||
title: "Info",
|
||||
description: "An info toast called from the front-end.",
|
||||
action: { label: "Dismiss", click: "close()" }
|
||||
}
|
||||
]
|
||||
) }}{% endraw %}{% endset%}
|
||||
|
||||
{% set raw_code_toast %}{% raw %}{% from "toast.njk" import toast %}
|
||||
{{ toast(
|
||||
title="Event has been created",
|
||||
description="Sunday, December 03, 2023 at 9:00 AM",
|
||||
cancel={ label: "Undo", click: "close()" }
|
||||
) }}{% endraw %}{% endset %}
|
||||
|
||||
{% from "tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "toaster()", panel: code_block(raw_code_toaster, "jinja", class="") },
|
||||
{ tab: "toast()", panel: code_block(raw_code_toast, "jinja", class="") }
|
||||
] %}
|
||||
{{ tabs(id="macros", tabsets=tabsets) }}
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Tooltip
|
||||
description: A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
- label: Examples
|
||||
id: examples
|
||||
children:
|
||||
- label: Default
|
||||
id: example-default
|
||||
- label: Right
|
||||
id: example-right
|
||||
- label: Bottom + Start
|
||||
id: example-bottom-start
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Add to library">Hover</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose">
|
||||
<p>On any element, set the value of the <code>data-tooltip</code> attribute to the text you want to display in the tooltip. Additionally, you can set:</p>
|
||||
<ul>
|
||||
<li><code>data-side</code> attribute to <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> to change the side of the tooltip.</li>
|
||||
<li><code>data-align</code> attribute to <code>start</code>, <code>center</code>, or <code>end</code> to change the alignment of the tooltip.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Tooltip text" data-side="bottom" data-align="center">Bottom left</button>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Default tooltip">Default</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
|
||||
<h3 id="example-right"><a href="#example-right">Right</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Right side tooltip" data-side="right">Right</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
|
||||
<h3 id="example-bottom-start"><a href="#example-bottom-start">Bottom + Start</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" data-tooltip="Bottom side and start aligned tooltip" data-side="bottom" data-align="start">Bottom + Start</button>{% endset %}
|
||||
{{ code_preview("button", code) }}
|
||||
@@ -1,326 +0,0 @@
|
||||
---
|
||||
layout: layouts/layout.njk
|
||||
---
|
||||
|
||||
{% from "select.njk" import select %}
|
||||
|
||||
<div class="max-w-screen-lg mx-auto">
|
||||
<header class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-2xl font-bold tracking-tight sm:text-3xl md:text-4xl">All of the shadcn/ui magic, none of the React</h1>
|
||||
<p class="sm:text-lg text-muted-foreground">A components library built with Tailwind CSS that works with any web stack.</p>
|
||||
</div>
|
||||
<div class="flex w-full items-center justify-start gap-2 pt-2">
|
||||
<a class="btn" href="/installation">Get Started</a>
|
||||
<a class="btn-outline" href="/introduction">Learn more</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-8">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Team Members</h2>
|
||||
<p>Invite your team members to collaborate.</p>
|
||||
</header>
|
||||
<section>
|
||||
<ul class="grid gap-4">
|
||||
<li class="flex items-center gap-4">
|
||||
<img src="/assets/images/avatar-1.png" alt="Sofia Davis" class="w-10 h-10 rounded-full" />
|
||||
<div class="flex flex-col gap-1 mr-auto">
|
||||
<h3 class="text-sm font-medium leading-none">Sofia Davis</h3>
|
||||
<p class="text-sm text-muted-foreground">m@example.com</p>
|
||||
</div>
|
||||
|
||||
{% call select(
|
||||
is_combobox=true,
|
||||
selected="owner",
|
||||
content_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="option" data-value="viewer">Viewer</div>
|
||||
<div role="option" data-value="developer">Developer</div>
|
||||
<div role="option" data-value="billing">Billing</div>
|
||||
<div role="option" data-value="owner">Owner</div>
|
||||
{% endcall %}
|
||||
</li>
|
||||
<li class="flex items-center gap-4">
|
||||
<img src="/assets/images/avatar-2.png" alt="Jackson Lee" class="w-10 h-10 rounded-full" />
|
||||
<div class="flex flex-col gap-1 mr-auto">
|
||||
<h3 class="text-sm font-medium leading-none">Jackson Lee</h3>
|
||||
<p class="text-sm text-muted-foreground">p@example.com</p>
|
||||
</div>
|
||||
|
||||
{% call select(
|
||||
is_combobox=true,
|
||||
content_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="option" data-value="viewer">Viewer</div>
|
||||
<div role="option" data-value="developer">Developer</div>
|
||||
<div role="option" data-value="billing">Billing</div>
|
||||
<div role="option" data-value="owner">Owner</div>
|
||||
{% endcall %}
|
||||
</li>
|
||||
<li class="flex items-center gap-4">
|
||||
<img src="/assets/images/avatar-3.png" alt="Isabella Nguyen" class="w-10 h-10 rounded-full" />
|
||||
<div class="flex flex-col gap-1 mr-auto">
|
||||
<h3 class="text-sm font-medium leading-none">Isabella Nguyen</h3>
|
||||
<p class="text-sm text-muted-foreground">i@example.com</p>
|
||||
</div>
|
||||
|
||||
{% call select(
|
||||
is_combobox=true,
|
||||
content_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="option" data-value="viewer">Viewer</div>
|
||||
<div role="option" data-value="developer">Developer</div>
|
||||
<div role="option" data-value="billing">Billing</div>
|
||||
<div role="option" data-value="owner">Owner</div>
|
||||
{% endcall %}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Cookie Settings</h2>
|
||||
<p>Manage your cookie settings here.</p>
|
||||
</header>
|
||||
<section class="text-sm grid gap-6">
|
||||
<label class="flex items-center justify-between gap-2">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="font-medium">Strictly Necessary</div>
|
||||
<div class="text-muted-foreground">These cookies are essential in order to use the website and use its features.</div>
|
||||
</div>
|
||||
<input type="checkbox" role="switch" class="input" checked />
|
||||
</label>
|
||||
<label class="flex items-center justify-between gap-2">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="font-medium">Functional Cookies</div>
|
||||
<div class="text-muted-foreground">These cookies allow the website to provide personalized functionality.</div>
|
||||
</div>
|
||||
<input type="checkbox" role="switch" class="input" />
|
||||
</label>
|
||||
<label class="flex items-center justify-between gap-2">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="font-medium">Performance Cookies</div>
|
||||
<div class="text-muted-foreground">These cookies help to improve the performance of the website.</div>
|
||||
</div>
|
||||
<input type="checkbox" role="switch" class="input" />
|
||||
</label>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn-outline w-full">Save preferences</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Payment Method</h2>
|
||||
<p>Add a new payment method to your account.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<ul class="flex gap-4">
|
||||
<li class="flex-1">
|
||||
<input type="radio" name="payment-method-type" id="payment-method-type-apple" class="hidden peer" />
|
||||
<label for="payment-method-type-apple" class="text-sm font-medium leading-none flex flex-col items-center justify-between rounded-md border-2 border-muted p-4 hover:bg-muted peer-checked:border-primary [&>svg]:mb-3 [&>svg]:size-6">
|
||||
{% lucide "credit-card" %}
|
||||
Card
|
||||
</label>
|
||||
</li>
|
||||
<li class="flex-1">
|
||||
<input type="radio" name="payment-method-type" id="payment-method-type-card" class="hidden peer" />
|
||||
<label for="payment-method-type-card" class="text-sm font-medium leading-none flex flex-col items-center justify-between rounded-md border-2 border-muted p-4 hover:bg-muted peer-checked:border-primary [&>svg]:mb-3 [&>svg]:size-6">
|
||||
<svg role="img" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>PayPal</title><path d="M7.016 19.198h-4.2a.562.562 0 0 1-.555-.65L5.093.584A.692.692 0 0 1 5.776 0h7.222c3.417 0 5.904 2.488 5.846 5.5-.006.25-.027.5-.066.747A6.794 6.794 0 0 1 12.071 12H8.743a.69.69 0 0 0-.682.583l-.325 2.056-.013.083-.692 4.39-.015.087zM19.79 6.142c-.01.087-.01.175-.023.261a7.76 7.76 0 0 1-7.695 6.598H9.007l-.283 1.795-.013.083-.692 4.39-.134.843-.014.088H6.86l-.497 3.15a.562.562 0 0 0 .555.65h3.612c.34 0 .63-.249.683-.585l.952-6.031a.692.692 0 0 1 .683-.584h2.126a6.793 6.793 0 0 0 6.707-5.752c.306-1.95-.466-3.744-1.89-4.906z"/></svg>
|
||||
Paypal
|
||||
</label>
|
||||
</li>
|
||||
<li class="flex-1">
|
||||
<input type="radio" name="payment-method-type" id="payment-method-type-paypal" class="hidden peer" />
|
||||
<label for="payment-method-type-paypal" class="text-sm font-medium leading-none flex flex-col items-center justify-between rounded-md border-2 border-muted p-4 hover:bg-muted peer-checked:border-primary [&>svg]:mb-3 [&>svg]:size-6">
|
||||
<svg role="img" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Apple</title><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"/></svg>
|
||||
Apple Pay
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="grid gap-2">
|
||||
<label for="payment-method-name">Name</label>
|
||||
<input type="text" id="payment-method-name" placeholder="John Doe" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label for="payment-method-city">City</label>
|
||||
<input type="text" id="payment-method-city" placeholder="New York" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label for="payment-method-card-number">Card Number</label>
|
||||
<input type="text" id="payment-method-card-number" placeholder="1234 5678 9012 3456" />
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="payment-method-expiration-month">Expires</label>
|
||||
<select id="payment-method-expiration-month" class="w-full">
|
||||
<option value="01">January</option>
|
||||
<option value="02">February</option>
|
||||
<option value="03">March</option>
|
||||
<option value="04">April</option>
|
||||
<option value="05">May</option>
|
||||
<option value="06">June</option>
|
||||
<option value="07">July</option>
|
||||
<option value="08">August</option>
|
||||
<option value="09">September</option>
|
||||
<option value="10">October</option>
|
||||
<option value="11">November</option>
|
||||
<option value="12">December</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="payment-method-expiration-year">Year</label>
|
||||
<select id="payment-method-expiration-year" class="w-full">
|
||||
<option value="2024">2024</option>
|
||||
<option value="2025">2025</option>
|
||||
<option value="2026">2026</option>
|
||||
<option value="2027">2027</option>
|
||||
<option value="2028">2028</option>
|
||||
<option value="2029">2029</option>
|
||||
<option value="2030">2030</option>
|
||||
<option value="2031">2031</option>
|
||||
<option value="2032">2032</option>
|
||||
<option value="2033">2033</option>
|
||||
<option value="2034">2034</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="payment-method-cvv">CVV</label>
|
||||
<input type="text" id="payment-method-cvv" placeholder="123" maxlength="4" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn w-full">Continue</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="card">
|
||||
<section class="space-y-6">
|
||||
<header class="flex items-center gap-2">
|
||||
<img src="/assets/images/avatar-1.png" alt="Sofia Davis" class="w-10 h-10 rounded-full" />
|
||||
<div class="flex flex-col gap-1 mr-auto">
|
||||
<h3 class="text-sm font-medium leading-none">Sofia Davis</h3>
|
||||
<p class="text-sm text-muted-foreground">m@example.com</p>
|
||||
</div>
|
||||
<button type="button" class="btn-icon-outline rounded-full" data-tooltip="New message">
|
||||
{% lucide "plus"%}
|
||||
</button>
|
||||
</header>
|
||||
<section class="space-y-4">
|
||||
<div class="flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm bg-muted">
|
||||
Hi, how can I help you today?
|
||||
</div>
|
||||
<div class="flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm ml-auto bg-primary text-primary-foreground">
|
||||
Hey, I'm having trouble with my account.
|
||||
</div>
|
||||
<div class="flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm bg-muted">
|
||||
What seems to be the problem?
|
||||
</div>
|
||||
<div class="flex w-max max-w-[75%] flex-col gap-2 rounded-lg px-3 py-2 text-sm ml-auto bg-primary text-primary-foreground">
|
||||
I can't log in.
|
||||
</div>
|
||||
</section>
|
||||
<footer class="flex items-center gap-2" x-data="{ message: '' }">
|
||||
<input type="text" class="input w-full" placeholder="Type your message here..." x-model="message" />
|
||||
<button type="button" class="btn-icon" :disabled="!message">
|
||||
{% lucide "send"%}
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Create an account</h2>
|
||||
<p>Enter your email below to create your account</p>
|
||||
</header>
|
||||
<section class="grid gap-6">
|
||||
<div class="flex gap-6">
|
||||
<button type="button" class="btn-outline flex-1">
|
||||
<svg role="img" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
||||
GitHub
|
||||
</button>
|
||||
<button type="button" class="btn-outline flex-1">
|
||||
<svg role="img" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Google</title><path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"/></svg>
|
||||
Google
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<span class="w-full border-t"></span>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-xs uppercase">
|
||||
<span class="bg-card px-2 text-muted-foreground">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-card-form-email">Email</label>
|
||||
<input type="email" id="demo-card-form-email">
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="demo-card-form-password">Password</label>
|
||||
<a href="#" class="ml-auto inline-block text-sm underline-offset-4 hover:underline">Forgot your password?</a>
|
||||
</div>
|
||||
<input type="password" id="demo-card-form-password">
|
||||
</div>
|
||||
<button type="button" class="btn w-full">Create an account</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Report an issue</h2>
|
||||
<p>What area are you having problems with?</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="flex gap-4">
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="report-issue-area">Area</label>
|
||||
<select id="report-issue-area" class="w-full">
|
||||
<option value="team">Team</option>
|
||||
<option value="billing">Billing</option>
|
||||
<option value="account">Account</option>
|
||||
<option value="deployments">Deployments</option>
|
||||
<option value="support">Support</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid gap-2 flex-1">
|
||||
<label for="report-issue-security-level">Security Level</label>
|
||||
<select id="report-issue-security-level" class="w-full">
|
||||
<option value="1">Severity 1 (Highest)</option>
|
||||
<option value="2">Severity 2</option>
|
||||
<option value="3">Severity 3</option>
|
||||
<option value="4">Severity 4 (Lowest)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label for="report-issue-subject">Subject</label>
|
||||
<input type="text" id="report-issue-subject" placeholder="I need help with..." class="w-full" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<label for="report-issue-description">Description</label>
|
||||
<textarea id="report-issue-description" placeholder="Please include all information relevant to your issue." class="w-full"></textarea>
|
||||
</div>
|
||||
<footer class="flex items-center gap-4 justify-between">
|
||||
<button type="button" class="btn-sm-ghost">Cancel</button>
|
||||
<button type="button" class="btn-sm">Continue</button>
|
||||
</footer>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -1,188 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Installation
|
||||
description: How to install and use Basecoat in your app.
|
||||
toc:
|
||||
- label: Installing the CSS
|
||||
id: install-css
|
||||
children:
|
||||
- label: Add Tailwind CSS
|
||||
id: install-css
|
||||
- label: Add Basecoat
|
||||
id: install-css-basecoat
|
||||
- label: Import basecoat in your CSS
|
||||
id: install-css-import
|
||||
- label: That's it
|
||||
id: install-css-done
|
||||
- label: Components with Javascript
|
||||
id: install-js
|
||||
- label: Use the CLI
|
||||
id: install-cli
|
||||
- label: Use Nunjucks or Jinja macros
|
||||
id: install-macros
|
||||
- label: Theming
|
||||
id: install-theming
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
<h2 id="install-css">Install the CSS</h2>
|
||||
|
||||
<ol class="[&>h3]:step steps mb-12 md:ml-4 md:border-l md:pl-8">
|
||||
<li class="step">
|
||||
<h4 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-css-tailwind"><a href="#install-css-tailwind">Add Tailwind CSS</a></h4>
|
||||
<div class="prose">
|
||||
<p>Basecoat uses Tailwind CSS. You need to install it in your project.</p>
|
||||
<p><a href="https://tailwindcss.com/docs/installation" target="_blank">Follow the Tailwind CSS installation instructions to get started.</a></p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="step mt-8">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-css-basecoat"><a href="#install-css-basecoat">Add Basecoat</a></h3>
|
||||
<div class="prose">
|
||||
<p>Add Basecoat to your Tailwind CSS file.</p>
|
||||
</div>
|
||||
{% set code_npm %}npm install basecoat-css{% endset %}
|
||||
{% set code_pnpm %}pnpm add basecoat-css{% endset %}
|
||||
{% set code_bun %}bun add basecoat-css{% endset %}
|
||||
{% set code_yarn %}yarn add basecoat-css{% endset %}
|
||||
{% set tabsets = [
|
||||
{ tab: "npm", panel: code_block(code_npm, class="") },
|
||||
{ tab: "pnpm", panel: code_block(code_pnpm, class="") },
|
||||
{ tab: "bun", panel: code_block(code_bun, class="") },
|
||||
{ tab: "yarn", panel: code_block(code_yarn, class="") }
|
||||
] %}
|
||||
{{ tabs(id="npm-install", tabsets=tabsets) }}
|
||||
<div class="prose">
|
||||
<p>Alternatively, you can directly <a href="https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css" target="_blank">copy the <code>basecoat.css</code> file</a> into your project.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="step mt-8">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-css-import"><a href="#install-css-import">Import basecoat in your CSS</a></h3>
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
</li>
|
||||
<li class="step mt-8">
|
||||
<h3 class="mb-4 scroll-m-20 font-semibold tracking-tight" id="install-css-done"><a href="#install-css-done">That's it</a></h3>
|
||||
<div class="prose">
|
||||
<p>You can now use any of the Basecoat classes in your project (e.g. <code>btn</code>, <code>card</code>, <code>input</code>, etc). Read the documentation about each component to get started (e.g. <a href="/components/button">button</a>, <a href="/components/card">card</a>, <a href="/components/input">input</a>, etc).</p>
|
||||
<p>Some components (e.g. <a href="/components/select">Select</a>) require <a href="#install-js">Javascript code to work</a>.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="install-js"><a href="#install-js">Components with Javascript</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p><b>Some components require Javascript code to work.</b></p>
|
||||
<p>The documentation provides Alpine.js code snippets to cover that. <a href="https://alpinejs.dev/docs/installation" target="_blank">Make sure to install Alpine.js if you decide to use them.</a></p>
|
||||
<p>For simple components like <a href="/components/accordion">Accordion</a> or <a href="/components/slider">Slider</a>, you will notice some inline Alpine.js code that you can just copy along with the rest of the HTML.</p>
|
||||
<p>For more complex components (<a href="/components/dialog">Dialog</a>, <a href="/components/dropdown-menu">Dropdown Menu</a>, <a href="/components/popover">Popover</a>, <a href="/components/select">Select</a>, <a href="/components/tabs">Tabs</a> and <a href="/components/tooltip">Toast</a>), you have one of 2 options:
|
||||
<ul>
|
||||
<li>Copy the Javascript snippet provided in the documentation page into your project.</li>
|
||||
<li>
|
||||
<p><a href="https://github.com/hunvreus/basecoat/tree/main/src/js" target="_blank">Download the script as a separate file</a> and include it in your project (<a href="#install-cli">or let the CLI do it for you</a>). <b>If you use this approach, make sure to load the file before the Alpine.js core JS file and register the component on the <code>alpine:init</code> event.</b> For example:</p>
|
||||
{% set code %}<script src="/assets/js/dialog.js" defer></script>
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerDialog(Alpine);
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
</li>
|
||||
</ul>
|
||||
<p>You are also welcome to write your own Javascript.</p>
|
||||
<p>All of the components try and implement WAI-ARIA standards to make them accessible.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="install-cli"><a href="#install-cli">Use the CLI</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>If you're using Nunjucks or Jinja, you can use the CLI to install the CSS and macros for any of the complex components(e.g. <a href="/components/dialog">Dialog</a>). It will copy the JS and <code>.html.jinja</code> or <code>.njk</code> macros into your code.</p>
|
||||
<p>For example, to add the Dialog component, run one of the following commands:</p>
|
||||
</div>
|
||||
|
||||
{% set code_npm_exec %}npx basecoat-cli add dialog{% endset %}
|
||||
{% set code_pnpm_exec %}pnpm dlx basecoat-cli add dialog{% endset %}
|
||||
{% set code_bun_exec %}bunx basecoat-cli add dialog{% endset %}
|
||||
{% set code_yarn_exec %}yarn dlx basecoat-cli add dialog{% endset %}
|
||||
{% set exec_tabsets = [
|
||||
{ tab: "npm", panel: code_block(code_npm_exec, class="") },
|
||||
{ tab: "pnpm", panel: code_block(code_pnpm_exec, class="") },
|
||||
{ tab: "bun", panel: code_block(code_bun_exec, class="") },
|
||||
{ tab: "yarn", panel: code_block(code_yarn_exec, class="") }
|
||||
] %}
|
||||
{{ tabs(id="cli-execution", tabsets=exec_tabsets, label="Execute CLI commands to add components:") }}
|
||||
|
||||
<div class="prose my-6">
|
||||
<p>You will be asked to confirm the template engine and destination directory for the Javascript and macros.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="install-macros"><a href="#install-macros">Use Nunjucks or Jinja macros</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>For more complex components, you can use <a href="https://mozilla.github.io/nunjucks/" target="_blank">Nunjucks</a> or <a href="https://jinja.palletsprojects.com" target="_blank">Jinja</a> macros to help you generate the HTML and Javascript code.</p>
|
||||
<p>To install them, either <a href="https://github.com/hunvreus/basecoat/tree/main/src" target="_blank">copy them directly from the GitHub repository</a> or use <a href="#install-cli">the CLI</a> to do the work for you.</p>
|
||||
<p>For example, here's how you could use the <code>select()</code> macro to generate the HTML and Javascript code for a <a href="/components/select">Select</a> component:</p>
|
||||
{% set code %}{% raw %}{% call select() %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
<div role="option" data-value="banana">Banana</div>
|
||||
<div role="option" data-value="blueberry">Blueberry</div>
|
||||
<div role="option" data-value="pineapple">Grapes</div>
|
||||
<div role="option" data-value="pineapple">Pineapple</div>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset %}
|
||||
{{ code_block(code, "jinja") }}
|
||||
<p>Make sure to include the Javascript code <b>before</b> Alpine.js core JS file:</p>
|
||||
{% set code %}<script src="/assets/js/select.js" defer></script>
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
window.basecoat.registerSelect(Alpine);
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "jinja") }}
|
||||
</div>
|
||||
|
||||
<h2 id="install-theming"><a href="#install-theming">Theming</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can import any <a href="https://shadcn-ui.com" target="_blank">shadcn/ui</a> compatible theme (e.g. <a href="https://tweakcn.com" target="_blank">tweakcn</a>). For example:</p>
|
||||
<ul>
|
||||
<li>Go to <a href="https://ui.shadcn.com/themes" target="_blank">ui.shadcn.com/themes</a> and select a theme.</li>
|
||||
<li>Click "Copy code" and save the theme variables in a file (e.g. <code>theme.css</code>).</li>
|
||||
<li>
|
||||
Import the theme in your CSS file:
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
@import "theme.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
<a class="badge-outline" href="https://ui.shadcn.com/docs/theming" target="_blank">
|
||||
About shadcn/ui theming
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 id="install-customization"><a href="#install-manual">Customization</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>You can override default styles using Tailwind:</p>
|
||||
{% set code %}<button class="btn font-normal ">Click me</button>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
<p>You can also obviously extend and/or override the existing styles with your own Tailwind files:</p>
|
||||
{% set code %}@import "tailwindcss";
|
||||
@import "basecoat-css";
|
||||
@import "theme.css";{% endset %}
|
||||
{{ code_block(code, "css") }}
|
||||
<p>You can also use use the <a href="#install-theming">theme variables</a> to customize many aspects of the components (colors, fonts, sizes, etc).</p>
|
||||
<p><b>If you want to make more drastic changes</b>, I recommend <a href="https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css" target="_blank">copying the <code>basecoat.css</code> file</a> into your project and making your changes there.</p>
|
||||
</div>
|
||||
@@ -1,63 +0,0 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Introduction
|
||||
description: Basecoat is a set of components built with Tailwind CSS. It is designed to be used with any traditional web stack
|
||||
toc:
|
||||
- label: Why Basecoat?
|
||||
id: why
|
||||
- label: How it works
|
||||
id: how-it-works
|
||||
- label: How can I help?
|
||||
id: how-can-i-help
|
||||
---
|
||||
|
||||
<h2 id="why"><a href="#why">Why Basecoat?</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p><b>Basecoat brings the magic of <a href="https://ui.shadcn.com/">shadcn/ui</a> to any traditional web stack: no React required.</b></p>
|
||||
|
||||
<p>Tailwind won. But building UIs with utility classes alone kinda sucks. Most Tailwind libraries like Flowbite, Preline, or even Tailwind UI ask you to copy walls of unreadable classes into your HTML. It works, but it's messy and hard to maintain.</p>
|
||||
|
||||
<p>shadcn/ui avoids that by wrapping everything in React components. It also gives you a killer design system, theme support, a CLI, and a growing ecosystem.</p>
|
||||
|
||||
<p>But maybe you're not using React. Maybe your app is built with plain HTML. Or Flask. Or Rails. Or Laravel. Or Django. Or whatever.</p>
|
||||
|
||||
<p><b>That's where Basecoat comes in.</b> It gives you modern, accessible components with the simplicity of plain HTML and Tailwind. Basecoat is:</p>
|
||||
|
||||
<ul>
|
||||
<li><b>Lightweight</b>: no runtime JS, just CSS and tiny Alpine.js scripts for the more interactive components.</li>
|
||||
<li><b>Easy to use</b>: add classes like <code>btn</code> or <code>input</code> and you're done.</li>
|
||||
<li><b>Framework-agnostic</b>: works with any backend or frontend stack.</li>
|
||||
<li><b>Accessible</b>: components follow accessibility best practices.</li>
|
||||
<li><b>Dark mode ready</b>: respects your Tailwind config.</li>
|
||||
<li><b>Extendable</b>: tweak styles with Tailwind or CSS variables.</li>
|
||||
<li><b>Themable</b>: fully compatible with shadcn/ui themes.</li>
|
||||
<li><b>Readable</b>: no class soup, just clean markup.</li>
|
||||
<li><b>Free and open source</b>: MIT licensed.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="how-it-works"><a href="#how-it-works">How it works</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>Add a single CSS file to your Tailwind setup. Use components by dropping in simple classes like <code>btn</code>, <code>form</code>, or <code>card</code>.</p>
|
||||
|
||||
<p>Some components (like modals or dropdowns) use a tiny bit of Alpine.js. You can skip it if you don't need interactivity.</p>
|
||||
|
||||
<p>There's also a CLI to help you scaffold components and get set up fast.</p>
|
||||
|
||||
<p><a href="/installation">Check out the installation guide</a> to get started.</p>
|
||||
</div>
|
||||
|
||||
<h2 id="how-can-i-help"><a href="#how-can-i-help">How can I help?</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p>Basecoat is 100% open source and free.</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/hunvreus/basecoat">Star it on GitHub</a></li>
|
||||
<li><a href="https://github.com/hunvreus/basecoat/issues">Report bugs or request features</a></li>
|
||||
<li><a href="https://github.com/hunvreus/basecoat/pulls">Submit a pull request</a></li>
|
||||
<li><a href="https://github.com/sponsors/hunvreus">Sponsor the project</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
layout: layouts/layout.njk
|
||||
title: Kitchen Sink
|
||||
description: A collection of all the components available in Basecoat.
|
||||
---
|
||||
{% set components = [
|
||||
{ label: "Accordion", id: "accordion" },
|
||||
{ label: "Alert", id: "alert" },
|
||||
{ label: "Alert Dialog", id: "alert-dialog" },
|
||||
{ label: "Avatar", id: "avatar" },
|
||||
{ label: "Badge", id: "badge" },
|
||||
{ label: "Breadcrumb", id: "breadcrumb" },
|
||||
{ label: "Button", id: "button" },
|
||||
{ label: "Card", id: "card" },
|
||||
{ label: "Checkbox", id: "checkbox" },
|
||||
{ label: "Combobox", id: "combobox" },
|
||||
{ label: "Dialog", id: "dialog" },
|
||||
{ label: "Dropdown Menu", id: "dropdown-menu" },
|
||||
{ label: "Form", id: "form" },
|
||||
{ label: "Input", id: "input" },
|
||||
{ label: "Label", id: "label" },
|
||||
{ label: "Pagination", id: "pagination" },
|
||||
{ label: "Popover", id: "popover" },
|
||||
{ label: "Radio Group", id: "radio-group" },
|
||||
{ label: "Select", id: "select" },
|
||||
{ label: "Skeleton", id: "skeleton" },
|
||||
{ label: "Slider", id: "slider" },
|
||||
{ label: "Switch", id: "switch" },
|
||||
{ label: "Table", id: "table" },
|
||||
{ label: "Tabs", id: "tabs" },
|
||||
{ label: "Textarea", id: "textarea" },
|
||||
{ label: "Toast", id: "toast" },
|
||||
{ label: "Tooltip", id: "tooltip" }
|
||||
] %}
|
||||
|
||||
<div class="flex gap-x-10 justify-center">
|
||||
<div>
|
||||
<header class="space-y-2 mb-8">
|
||||
<h1 class="text-3xl font-semibold tracking-tight">{{ title }}</h1>
|
||||
<p class="text-muted-foreground">{{ description }}</p>
|
||||
</header>
|
||||
<div class="grid gap-4 flex-1">
|
||||
{% for component in components %}
|
||||
{% include "partials/kitchen-sink/" ~ component.id ~ ".njk" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden text-sm xl:block w-full max-w-[200px]">
|
||||
{% from "macros/toc.njk" import toc_nav %}
|
||||
{{ toc_nav(components) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
permalink: /robots.txt
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
Sitemap: {{ site.url }}/sitemap.xml
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
permalink: /sitemap.xml
|
||||
eleventyExcludeFromCollections: true
|
||||
---
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
{% for page in collections.all %}
|
||||
{% if not page.url.startsWith('/fragments/') %}
|
||||
<url>
|
||||
<loc>{{ site.url }}{{ page.url | url }}</loc>
|
||||
<lastmod>{{ page.date.toISOString() }}</lastmod>
|
||||
</url>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</urlset>
|
||||
Generated
+267
-3267
File diff suppressed because it is too large
Load Diff
+109
-30
@@ -1,22 +1,19 @@
|
||||
{
|
||||
"name": "basecoat",
|
||||
"version": "0.1.0",
|
||||
"description": "shadcn/ui for the rest of us",
|
||||
"author": "hunvreus",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"files": [
|
||||
"src/css",
|
||||
"src/js",
|
||||
"src/nunjucks",
|
||||
"src/jinja"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/css/basecoat.css"
|
||||
"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",
|
||||
"main": "dist/basecoat.css",
|
||||
"style": "dist/basecoat.css",
|
||||
"files": [
|
||||
"dist/",
|
||||
"templates/"
|
||||
],
|
||||
"keywords": [
|
||||
"components",
|
||||
"component library",
|
||||
@@ -30,10 +27,8 @@
|
||||
"css",
|
||||
"html",
|
||||
"jinja",
|
||||
"nunjucks",
|
||||
"alpinejs"
|
||||
"nunjucks"
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/hunvreus/basecoat.git"
|
||||
@@ -41,20 +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": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --minify && eleventy",
|
||||
"docs:dev:css": "tailwindcss -i ./docs/css/styles.css -o ./docs/src/assets/styles.css --watch",
|
||||
"docs:dev:eleventy": "ELEVENTY_ENV=prod eleventy --serve",
|
||||
"docs:dev": "concurrently \"npm:docs:dev:css\" \"npm:docs:dev:eleventy\""
|
||||
"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": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"@tailwindcss/cli": "^4.1.17",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "^4.1.3"
|
||||
"tailwindcss": "^4.1.17",
|
||||
"terser": "^5.42.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +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).
|
||||
- [Alpine.js](https://alpinejs.dev/docs/installation) installed and initialized, as Basecoat components rely on it for interactivity.
|
||||
|
||||
## 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,193 +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
|
||||
|
||||
// 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 jsFiles = await fs.readdir(jsSourceDir);
|
||||
|
||||
return jsFiles
|
||||
.filter(file => file.endsWith('.js'))
|
||||
.map(file => path.basename(file, '.js'));
|
||||
} 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) {
|
||||
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 || !scriptExists) return; // Skip this component if sources are missing
|
||||
|
||||
await fs.ensureDir(path.dirname(templateDestPath));
|
||||
await fs.ensureDir(path.dirname(scriptDestPath));
|
||||
|
||||
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,45 +0,0 @@
|
||||
{
|
||||
"name": "basecoat-cli",
|
||||
"version": "0.1.1",
|
||||
"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",
|
||||
"alpinejs"
|
||||
],
|
||||
"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,54 +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
|
||||
|
||||
You can install `basecoat-css` using your preferred package manager:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install basecoat-css
|
||||
|
||||
# yarn
|
||||
yarn add basecoat-css
|
||||
|
||||
# pnpm
|
||||
pnpm add basecoat-css
|
||||
|
||||
# bun
|
||||
bun add basecoat-css
|
||||
```
|
||||
|
||||
Alternatively, you can download the `basecoat.css` file directly from the [GitHub repository](https://github.com/hunvreus/basecoat/blob/main/src/css/basecoat.css) (or the `dist/basecoat.css` file from this package after installation) and include it in your project.
|
||||
|
||||
## Usage
|
||||
|
||||
After installation, import `basecoat-css` into your main CSS file. Make sure to import it **after** Tailwind CSS and **before** any custom theme overrides.
|
||||
|
||||
```css
|
||||
/* Your main CSS file (e.g., styles.css) */
|
||||
|
||||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
@import "basecoat-css"; /* Or the path to basecoat.css if manually added */
|
||||
|
||||
/* Your custom styles or theme overrides (optional) */
|
||||
/* @import "your-theme.css"; */
|
||||
```
|
||||
|
||||
Once imported, you can start using Basecoat's component classes in your HTML.
|
||||
|
||||
## 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,40 +0,0 @@
|
||||
{
|
||||
"name": "basecoat-css",
|
||||
"version": "0.1.2",
|
||||
"description": "Tailwind CSS for Basecoat components",
|
||||
"author": {
|
||||
"name": "hunvreus",
|
||||
"url": "https://x.com/hunvreus"
|
||||
},
|
||||
"license": "MIT",
|
||||
"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",
|
||||
"alpinejs"
|
||||
],
|
||||
"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-css"
|
||||
}
|
||||
+140
-25
@@ -1,6 +1,11 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { generateCssEntrypoints } from './generate-css-entrypoints.js';
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
// Resolve project root, assuming this script is in <project_root>/scripts/
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
@@ -59,49 +64,159 @@ 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);
|
||||
await cleanDir(cssTemplatesDir);
|
||||
|
||||
// JS files minification and copy
|
||||
const jsFiles = await fs.readdir(srcJsDir);
|
||||
const cssJsDistDir = path.join(cssDistDir, 'js');
|
||||
await ensureDir(cssJsDistDir);
|
||||
console.log('Copying and minifying JS files...');
|
||||
|
||||
for (const jsFile of jsFiles) {
|
||||
if (path.extname(jsFile) === '.js') {
|
||||
const srcFile = path.join(srcJsDir, jsFile);
|
||||
const baseName = path.basename(jsFile, '.js');
|
||||
const minifiedFileName = `${baseName}.min.js`;
|
||||
|
||||
// Copy original file to destination
|
||||
await fs.copyFile(srcFile, path.join(cssJsDistDir, jsFile));
|
||||
|
||||
// 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', '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
|
||||
let combinedContent = '';
|
||||
for (const p of componentPaths) {
|
||||
combinedContent += await fs.readFile(p, 'utf-8') + '\n';
|
||||
}
|
||||
const allJsPath = path.join(cssJsDistDir, 'all.js');
|
||||
await fs.writeFile(allJsPath, combinedContent);
|
||||
|
||||
// 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
|
||||
// Create minified bundle
|
||||
const allMinJsPath = path.join(cssJsDistDir, 'all.min.js');
|
||||
await execPromise(`npx terser ${componentPaths.join(' ')} -o ${allMinJsPath} --compress --mangle`);
|
||||
|
||||
await fs.copyFile(path.join(cliPackageDir, 'index.js'), path.join(cliDistDir, 'index.js'));
|
||||
console.log(`Copied CLI index.js to ${cliDistDir}`);
|
||||
|
||||
await copyDirRecursive(srcJsDir, path.join(cliDistAssetsDir, 'js'));
|
||||
console.log(`Copied JS assets to ${path.join(cliDistAssetsDir, 'js')}`);
|
||||
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'));
|
||||
console.log(`Copied basecoat.css to ${cssDistDir}`);
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat.all.css'), path.join(cssDistDir, 'basecoat.all.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat-base.css'), path.join(cssDistDir, 'basecoat-base.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat-base.cdn.css'), path.join(cssDistDir, 'basecoat-base.cdn.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat-compat.css'), path.join(cssDistDir, 'basecoat-compat.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat-compat.cdn.css'), path.join(cssDistDir, 'basecoat-compat.cdn.css'));
|
||||
await fs.copyFile(path.join(srcCssDir, 'basecoat-components.css'), path.join(cssDistDir, 'basecoat-components.css'));
|
||||
for (const style of styles) {
|
||||
await fs.copyFile(path.join(srcCssDir, `basecoat-${style}.css`), path.join(cssDistDir, `basecoat-${style}.css`));
|
||||
await fs.copyFile(path.join(srcCssDir, `basecoat-${style}.cdn.css`), path.join(cssDistDir, `basecoat-${style}.cdn.css`));
|
||||
}
|
||||
console.log(`Copied basecoat CSS entrypoints to ${cssDistDir}`);
|
||||
|
||||
// Copy split CSS folders used by basecoat.css imports.
|
||||
const cssBaseSrcDir = path.join(srcCssDir, 'base');
|
||||
const cssComponentsSrcDir = path.join(srcCssDir, 'components');
|
||||
const cssBaseDistDir = path.join(cssDistDir, 'base');
|
||||
const cssComponentsDistDir = path.join(cssDistDir, 'components');
|
||||
const cssStylesDistDir = path.join(cssDistDir, 'styles');
|
||||
const cssCompatDistDir = path.join(cssDistDir, 'compat');
|
||||
await copyDirRecursive(cssBaseSrcDir, cssBaseDistDir);
|
||||
await copyDirRecursive(cssComponentsSrcDir, cssComponentsDistDir);
|
||||
await copyDirRecursive(srcCssStylesDir, cssStylesDistDir);
|
||||
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) {
|
||||
const cdnCssSrc = path.join(srcCssDir, entry);
|
||||
const baseName = path.basename(entry, '.css');
|
||||
const cssDistCdnPath = path.join(cssDistDir, `${baseName}.css`);
|
||||
const cssDistCdnMinPath = path.join(cssDistDir, `${baseName}.min.css`);
|
||||
|
||||
await execPromise(`npx tailwindcss -i "${cdnCssSrc}" -o "${cssDistCdnPath}"`);
|
||||
console.log(`Generated non-minified CSS: ${cssDistCdnPath}`);
|
||||
await execPromise(`npx tailwindcss -i "${cdnCssSrc}" -o "${cssDistCdnMinPath}" --minify`);
|
||||
console.log(`Generated minified CSS: ${cssDistCdnMinPath}`);
|
||||
}
|
||||
|
||||
console.log('Build process finished successfully!');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
const styles = ['vega', 'nova', 'maia', 'lyra', 'mira', 'luma', 'sera', 'rhea'];
|
||||
const excludedComponents = new Set(['form']);
|
||||
|
||||
// Keep cascade stable for components whose selectors intentionally compose.
|
||||
const componentOrder = [
|
||||
'accordion',
|
||||
'alert',
|
||||
'avatar',
|
||||
'badge',
|
||||
'breadcrumb',
|
||||
'button',
|
||||
'button-group',
|
||||
'card',
|
||||
'chart',
|
||||
'collapsible',
|
||||
'command',
|
||||
'combobox',
|
||||
'dialog',
|
||||
'drawer',
|
||||
'dropdown-menu',
|
||||
'empty',
|
||||
'field',
|
||||
'checkbox',
|
||||
'input',
|
||||
'item',
|
||||
'kbd',
|
||||
'label',
|
||||
'native-select',
|
||||
'popover',
|
||||
'progress',
|
||||
'radio',
|
||||
'range',
|
||||
'select',
|
||||
'sidebar',
|
||||
'scrollbar',
|
||||
'skeleton',
|
||||
'switch',
|
||||
'table',
|
||||
'tabs',
|
||||
'textarea',
|
||||
'input-group',
|
||||
'toast',
|
||||
'tooltip',
|
||||
];
|
||||
|
||||
async function existingComponentNames(cssDir) {
|
||||
const componentDir = path.join(cssDir, 'components');
|
||||
const files = await fs.readdir(componentDir);
|
||||
const names = files
|
||||
.filter((file) => file.endsWith('.css'))
|
||||
.map((file) => path.basename(file, '.css'))
|
||||
.filter((name) => !excludedComponents.has(name));
|
||||
const known = componentOrder.filter((name) => names.includes(name));
|
||||
const extra = names.filter((name) => !componentOrder.includes(name)).sort();
|
||||
return [...known, ...extra];
|
||||
}
|
||||
|
||||
function componentImports(names) {
|
||||
return [
|
||||
'/* Components */',
|
||||
...names.map((name) => `@import "./components/${name}.css";`),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
async function writeIfChanged(filePath, content) {
|
||||
let current = null;
|
||||
try {
|
||||
current = await fs.readFile(filePath, 'utf8');
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') throw error;
|
||||
}
|
||||
if (current !== content) await fs.writeFile(filePath, content);
|
||||
}
|
||||
|
||||
export async function generateCssEntrypoints({ cssDir = path.resolve('src/css') } = {}) {
|
||||
const components = await existingComponentNames(cssDir);
|
||||
const componentsCss = `${componentImports(components)}\n`;
|
||||
const baseCss = `@import "./base/base.css";\n@import "./basecoat-components.css";\n`;
|
||||
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat-components.css'), componentsCss);
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat-base.css'), baseCss);
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat-base.cdn.css'), '@import "tailwindcss" source(none);\n@import "./basecoat-base.css";\n');
|
||||
|
||||
for (const style of styles) {
|
||||
await writeIfChanged(
|
||||
path.join(cssDir, `basecoat-${style}.css`),
|
||||
`@import "./basecoat-base.css";\n@import "./styles/${style}.css";\n`,
|
||||
);
|
||||
await writeIfChanged(
|
||||
path.join(cssDir, `basecoat-${style}.cdn.css`),
|
||||
`@import "tailwindcss" source(none);\n@import "./basecoat-${style}.css";\n`,
|
||||
);
|
||||
}
|
||||
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat.css'), '@import "./basecoat-vega.css";\n');
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat.all.css'), '@import "./basecoat.css";\n');
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat.cdn.css'), '@import "tailwindcss" source(none);\n@import "./basecoat.css";\n');
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat-compat.css'), '@import "./compat/legacy.css";\n');
|
||||
await writeIfChanged(path.join(cssDir, 'basecoat-compat.cdn.css'), '@import "tailwindcss" source(none);\n@reference "./basecoat.css";\n@import "./basecoat-compat.css";\n');
|
||||
|
||||
return { components, styles };
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
await generateCssEntrypoints();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
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: {
|
||||
build: {
|
||||
assetsInlineLimit: 0,
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ['basecoat-css'],
|
||||
},
|
||||
},
|
||||
integrations: [
|
||||
reallySimpleDocs({
|
||||
docsDir: './src/docs',
|
||||
routeBase: '/',
|
||||
style: 'vega',
|
||||
css: false,
|
||||
bodyAttrs: {
|
||||
class: 'antialiased',
|
||||
'hx-boost': 'true',
|
||||
'hx-target': '#content',
|
||||
'hx-select': '#content',
|
||||
'hx-swap': 'outerHTML',
|
||||
'hx-push-url': 'true',
|
||||
},
|
||||
components: {
|
||||
Head: './src/components/StyleHead.astro',
|
||||
SidebarHeader: './src/components/SidebarHeader.astro',
|
||||
ContentHeader: './src/components/ContentHeader.astro',
|
||||
},
|
||||
site: {
|
||||
title: 'Basecoat',
|
||||
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'],
|
||||
author: {
|
||||
name: 'Ronan Berder',
|
||||
x: '@hunvreus',
|
||||
},
|
||||
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,
|
||||
}),
|
||||
],
|
||||
});
|
||||
Generated
+7398
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user