Compare commits

..
7 Commits
Author SHA1 Message Date
Ronan Berder 23585e3161 Fixing #107 2025-11-14 23:58:35 +08:00
Ronan Berder 7e3324edf2 Bumping up the version number 2025-11-14 19:51:01 +08:00
Ronan Berder cda08f3c95 Merge branch 'main' of github.com:hunvreus/basecoat 2025-11-14 19:38:29 +08:00
Ronan Berder 0b46e05a29 Draft llms.txt 2025-11-14 19:38:11 +08:00
Ronan Berder 7322220788 Merge pull request #106 from volfpeter/fix/add-command-to-combined-js-bundle
Fix: include command in combined JS bundle + fix command examples
2025-11-14 18:45:28 +08:00
Peter Volf 4c86ce9bfc fix: include command in combined JS bundle + fix command examples 2025-11-13 10:09:58 +01:00
Ronan Berder bff89833d4 Attempting to fix #84 2025-11-12 09:56:02 +08:00
14 changed files with 1084 additions and 516 deletions
+2 -1
View File
@@ -9,6 +9,7 @@ export default async function(eleventyConfig) {
eleventyConfig.setInputDirectory("./docs/src");
eleventyConfig.addPassthroughCopy({"docs/src/assets": "assets"});
eleventyConfig.addPassthroughCopy({ "src/js": "assets/js" });
eleventyConfig.addPassthroughCopy({ "docs/src/llms.txt": "llms.txt" });
eleventyConfig.addPlugin(eleventyLucideicons);
eleventyConfig.addShortcode("fetchCode", function(filePath) {
const absolutePath = path.resolve(process.cwd(), filePath);
@@ -43,4 +44,4 @@ export default async function(eleventyConfig) {
{ autoescape: true }
);
eleventyConfig.setLibrary("njk", nunjucksEnvironment);
}
}
+25
View File
@@ -56,6 +56,19 @@ Basecoat is a vanilla JavaScript/HTML/CSS port of shadcn/ui components. It reuse
- If it's just composition of existing elements (users can do it with standard HTML/Tailwind)
- If behavior is fully native (e.g., fieldset, legend)
## Adding new components
Before creating a component, ask:
1. Does shadcn/ui have it? → Port it
2. Is it pure composition? → Document pattern, no component
3. Does it need JS behavior? → Create component
4. Is it just CSS? → Add to basecoat.css, document usage
Examples:
- Field/Fieldset: CSS-only patterns (no JS component)
- Empty/Item/Input Group: Pure composition (docs only)
- Command/Select: JS behavior required (full component)
## Common tasks
- Update a component's CSS/JS: Edit `src/css/basecoat.css` and/or `src/js/*.js`.
@@ -81,6 +94,18 @@ No automated testing configured. User validates manually via the docs site (`npm
- Tailwind via `@tailwindcss/cli` v4.
- Build uses Tailwind CLI and `terser`; both are provided via devDependencies.
## Git workflow
- `.gitattributes` configures merge strategies for generated files
- `docs/src/assets/styles.css` auto-resolves to incoming changes (regenerated by Tailwind)
- Always run `npm run build` before publishing packages
## Publishing packages
1. Bump version in root `package.json`
2. Run `npm run build` to generate dist files
3. Run `npm publish --workspaces` to publish both packages
## Package references
- `packages/css` usage and details: see `packages/css/README.md`.
+375 -364
View File
@@ -1275,6 +1275,9 @@
.underline-offset-4 {
text-underline-offset: 4px;
}
.accent-foreground {
accent-color: var(--color-foreground);
}
.opacity-0 {
opacity: 0%;
}
@@ -1303,6 +1306,10 @@
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.ring {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.ring-offset-background {
--tw-ring-offset-color: var(--color-background);
}
@@ -2784,52 +2791,54 @@
}
}
}
.button-group {
isolation: isolate;
display: inline-flex;
width: fit-content;
align-items: stretch;
> *:focus-visible, > :is(.dropdown-menu, .popover, .select) > button:focus-visible {
position: relative;
z-index: 10;
}
> hr[role='separator'] {
margin: calc(var(--spacing) * 0);
height: auto;
width: calc(var(--spacing) * 0);
flex-shrink: 0;
align-self: stretch;
border-style: var(--tw-border-style);
border-width: 1px;
border-color: var(--color-input);
}
&:not([data-orientation='vertical']) {
> *:not(:first-child), > :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left-style: var(--tw-border-style);
border-left-width: 0px;
@layer components {
.button-group {
isolation: isolate;
display: inline-flex;
width: fit-content;
align-items: stretch;
> *:focus-visible, > :is(.dropdown-menu, .popover, .select) > button:focus-visible {
position: relative;
z-index: 10;
}
> *:not(:last-child), > :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
&[data-orientation='vertical'] {
flex-direction: column;
> hr[role='separator'] {
height: 1px;
width: auto;
margin: calc(var(--spacing) * 0);
height: auto;
width: calc(var(--spacing) * 0);
flex-shrink: 0;
align-self: stretch;
border-style: var(--tw-border-style);
border-width: 1px;
border-color: var(--color-input);
}
> *:not(:first-child), > :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top-style: var(--tw-border-style);
border-top-width: 0px;
&:not([data-orientation='vertical']) {
> *:not(:first-child), > :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left-style: var(--tw-border-style);
border-left-width: 0px;
}
> *:not(:last-child), > :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
> *:not(:last-child), > :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
&[data-orientation='vertical'] {
flex-direction: column;
> hr[role='separator'] {
height: 1px;
width: auto;
}
> *:not(:first-child), > :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top-style: var(--tw-border-style);
border-top-width: 0px;
}
> *:not(:last-child), > :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
}
}
}
@@ -4394,355 +4403,357 @@
}
}
}
.sidebar {
&:not([data-sidebar-initialized]) {
@media (width < 48rem) {
display: none;
@layer components {
.sidebar {
&:not([data-sidebar-initialized]) {
@media (width < 48rem) {
display: none;
}
}
}
&:not([aria-hidden]), &[aria-hidden=false] {
@media (width < 48rem) {
&:not([aria-hidden]), &[aria-hidden=false] {
@media (width < 48rem) {
position: fixed;
}
@media (width < 48rem) {
inset: calc(var(--spacing) * 0);
}
@media (width < 48rem) {
z-index: 40;
}
@media (width < 48rem) {
background-color: color-mix(in srgb, #000 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
}
}
}
nav {
position: fixed;
}
@media (width < 48rem) {
inset: calc(var(--spacing) * 0);
}
@media (width < 48rem) {
z-index: 40;
}
@media (width < 48rem) {
background-color: color-mix(in srgb, #000 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
inset-block: calc(var(--spacing) * 0);
z-index: 50;
display: flex;
width: var(--sidebar-mobile-width);
flex-direction: column;
background-color: var(--color-sidebar);
color: var(--color-sidebar-foreground);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 300ms;
transition-duration: 300ms;
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
@media (width >= 48rem) {
width: var(--sidebar-width);
}
}
}
nav {
position: fixed;
inset-block: calc(var(--spacing) * 0);
z-index: 50;
display: flex;
width: var(--sidebar-mobile-width);
flex-direction: column;
background-color: var(--color-sidebar);
color: var(--color-sidebar-foreground);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 300ms;
transition-duration: 300ms;
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
@media (width >= 48rem) {
width: var(--sidebar-width);
}
}
& + * {
transition-property: margin;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 300ms;
transition-duration: 300ms;
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
&:not([data-side]), &[data-side=left] {
nav {
left: calc(var(--spacing) * 0);
border-right-style: var(--tw-border-style);
border-right-width: 1px;
}
& + * {
position: relative;
@media (width >= 48rem) {
margin-left: var(--sidebar-width);
}
transition-property: margin;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 300ms;
transition-duration: 300ms;
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
&[aria-hidden=true] {
&:not([data-side]), &[data-side=left] {
nav {
--tw-translate-x: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
left: calc(var(--spacing) * 0);
border-right-style: var(--tw-border-style);
border-right-width: 1px;
}
& + * {
@media (width >= 48rem) {
margin-left: calc(var(--spacing) * 0);
}
}
}
}
&[data-side=right] {
nav {
right: calc(var(--spacing) * 0);
border-left-style: var(--tw-border-style);
border-left-width: 1px;
}
& + * {
position: relative;
@media (width >= 48rem) {
margin-right: var(--sidebar-width);
}
}
&[aria-hidden=true] {
nav {
--tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
& + * {
@media (width >= 48rem) {
margin-right: calc(var(--spacing) * 0);
}
}
}
}
nav {
> header, > footer {
display: flex;
flex-direction: column;
gap: calc(var(--spacing) * 2);
padding: calc(var(--spacing) * 2);
}
[role=separator] {
margin-inline: calc(var(--spacing) * 2);
width: auto;
border-color: var(--color-sidebar-border);
}
> section {
display: flex;
min-height: calc(var(--spacing) * 0);
flex: 1;
flex-direction: column;
gap: calc(var(--spacing) * 2);
overflow-y: auto;
> [role=group] {
position: relative;
@media (width >= 48rem) {
margin-left: var(--sidebar-width);
}
}
&[aria-hidden=true] {
nav {
--tw-translate-x: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
& + * {
@media (width >= 48rem) {
margin-left: calc(var(--spacing) * 0);
}
}
}
}
&[data-side=right] {
nav {
right: calc(var(--spacing) * 0);
border-left-style: var(--tw-border-style);
border-left-width: 1px;
}
& + * {
position: relative;
@media (width >= 48rem) {
margin-right: var(--sidebar-width);
}
}
&[aria-hidden=true] {
nav {
--tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
& + * {
@media (width >= 48rem) {
margin-right: calc(var(--spacing) * 0);
}
}
}
}
nav {
> header, > footer {
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
flex-direction: column;
gap: calc(var(--spacing) * 2);
padding: calc(var(--spacing) * 2);
}
h3 {
display: flex;
height: calc(var(--spacing) * 8);
flex-shrink: 0;
align-items: center;
border-radius: var(--radius-md);
padding-inline: calc(var(--spacing) * 2);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
color: var(--color-sidebar-foreground);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-sidebar-foreground) 70%, transparent);
}
--tw-ring-color: var(--color-sidebar-ring);
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
transition-property: margin,opacity;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
--tw-ease: linear;
transition-timing-function: linear;
&:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&>svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
&>svg {
flex-shrink: 0;
}
[role=separator] {
margin-inline: calc(var(--spacing) * 2);
width: auto;
border-color: var(--color-sidebar-border);
}
ul {
> section {
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
min-height: calc(var(--spacing) * 0);
flex: 1;
flex-direction: column;
gap: calc(var(--spacing) * 1);
li {
gap: calc(var(--spacing) * 2);
overflow-y: auto;
> [role=group] {
position: relative;
> a, > details > summary {
display: flex;
width: 100%;
align-items: center;
gap: calc(var(--spacing) * 2);
overflow: hidden;
border-radius: var(--radius-md);
padding: calc(var(--spacing) * 2);
text-align: left;
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-ring-color: var(--color-sidebar-ring);
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
transition-property: width,height,padding;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
&:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&:active {
background-color: var(--color-sidebar-accent);
}
&:active {
color: var(--color-sidebar-accent-foreground);
}
&:disabled {
pointer-events: none;
}
&:disabled {
opacity: 50%;
}
&[aria-disabled="true"] {
pointer-events: none;
}
&[aria-disabled="true"] {
opacity: 50%;
}
&>span:last-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&>svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
&>svg {
flex-shrink: 0;
}
&[aria-current=page] {
background-color: var(--color-sidebar-accent);
}
&[aria-current=page] {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
&[aria-current=page] {
color: var(--color-sidebar-accent-foreground);
}
&:not([data-variant]), &[data-variant=default] {
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
}
&[data-variant=outline] {
background-color: var(--color-background);
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-border)));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
&:hover {
@media (hover: hover) {
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-accent)));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
}
}
&:not([data-size]), &[data-size=default] {
height: calc(var(--spacing) * 8);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
&[data-size=sm] {
height: calc(var(--spacing) * 7);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
&[data-size=lg] {
height: calc(var(--spacing) * 12);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:is(:where(.group)[data-collapsible="icon"] *) {
padding: calc(var(--spacing) * 0) !important;
}
}
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
flex-direction: column;
padding: calc(var(--spacing) * 2);
}
h3 {
display: flex;
height: calc(var(--spacing) * 8);
flex-shrink: 0;
align-items: center;
border-radius: var(--radius-md);
padding-inline: calc(var(--spacing) * 2);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
color: var(--color-sidebar-foreground);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-sidebar-foreground) 70%, transparent);
}
> details {
&:not([open]) {
> summary {
&::after {
rotate: calc(90deg * -1);
}
}
}
> summary {
&::after {
margin-left: auto;
display: block;
width: calc(var(--spacing) * 3.5);
height: calc(var(--spacing) * 3.5);
background-color: var(--color-primary);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-ease: linear;
transition-timing-function: linear;
--tw-content: '';
content: var(--tw-content);
mask-image: var(--chevron-down-icon);
mask-size: 1rem;
mask-position: center;
mask-repeat: no-repeat;
}
}
&::details-content {
padding-inline: calc(var(--spacing) * 3.5);
}
--tw-ring-color: var(--color-sidebar-ring);
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
transition-property: margin,opacity;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
--tw-ease: linear;
transition-timing-function: linear;
&:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&>svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
&>svg {
flex-shrink: 0;
}
}
ul {
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
--tw-translate-x: 1px;
translate: var(--tw-translate-x) var(--tw-translate-y);
flex-direction: column;
gap: calc(var(--spacing) * 1);
border-left-style: var(--tw-border-style);
border-left-width: 1px;
border-color: var(--color-sidebar-border);
padding-inline: calc(var(--spacing) * 2.5);
padding-block: calc(var(--spacing) * 0.5);
li {
position: relative;
> a, > details > summary {
display: flex;
width: 100%;
align-items: center;
gap: calc(var(--spacing) * 2);
overflow: hidden;
border-radius: var(--radius-md);
padding: calc(var(--spacing) * 2);
text-align: left;
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-ring-color: var(--color-sidebar-ring);
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
transition-property: width,height,padding;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
&:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&:active {
background-color: var(--color-sidebar-accent);
}
&:active {
color: var(--color-sidebar-accent-foreground);
}
&:disabled {
pointer-events: none;
}
&:disabled {
opacity: 50%;
}
&[aria-disabled="true"] {
pointer-events: none;
}
&[aria-disabled="true"] {
opacity: 50%;
}
&>span:last-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&>svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
&>svg {
flex-shrink: 0;
}
&[aria-current=page] {
background-color: var(--color-sidebar-accent);
}
&[aria-current=page] {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
&[aria-current=page] {
color: var(--color-sidebar-accent-foreground);
}
&:not([data-variant]), &[data-variant=default] {
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
}
&[data-variant=outline] {
background-color: var(--color-background);
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-border)));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
&:hover {
@media (hover: hover) {
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-accent)));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
}
}
&:not([data-size]), &[data-size=default] {
height: calc(var(--spacing) * 8);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
&[data-size=sm] {
height: calc(var(--spacing) * 7);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
&[data-size=lg] {
height: calc(var(--spacing) * 12);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:is(:where(.group)[data-collapsible="icon"] *) {
padding: calc(var(--spacing) * 0) !important;
}
}
}
> details {
&:not([open]) {
> summary {
&::after {
rotate: calc(90deg * -1);
}
}
}
> summary {
&::after {
margin-left: auto;
display: block;
width: calc(var(--spacing) * 3.5);
height: calc(var(--spacing) * 3.5);
background-color: var(--color-primary);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-ease: linear;
transition-timing-function: linear;
--tw-content: '';
content: var(--tw-content);
mask-image: var(--chevron-down-icon);
mask-size: 1rem;
mask-position: center;
mask-repeat: no-repeat;
}
}
&::details-content {
padding-inline: calc(var(--spacing) * 3.5);
}
}
}
ul {
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
--tw-translate-x: 1px;
translate: var(--tw-translate-x) var(--tw-translate-y);
flex-direction: column;
gap: calc(var(--spacing) * 1);
border-left-style: var(--tw-border-style);
border-left-width: 1px;
border-color: var(--color-sidebar-border);
padding-inline: calc(var(--spacing) * 2.5);
padding-block: calc(var(--spacing) * 0.5);
}
}
}
}
+9 -9
View File
@@ -33,15 +33,15 @@ toc:
{% macro command_items(suggestions_id="suggestions", settings_id="settings") %}
<div role="group" aria-labelledby="{{ suggestions_id }}">
<span role="heading" id="{{ suggestions_id }}">Suggestions</span>
<div role="menuitem" data-value="calendar" onclick="console.log('Open calendar')">
<div role="menuitem" data-label="calendar" onclick="console.log('Open calendar')">
{% lucide "calendar" %}
<span>Calendar</span>
</div>
<div role="menuitem" data-value="search-emoji" onclick="console.log('Search emoji')">
<div role="menuitem" data-label="search-emoji" onclick="console.log('Search emoji')">
{% lucide "smile" %}
<span>Search Emoji</span>
</div>
<div role="menuitem" data-value="calculator" aria-disabled="true">
<div role="menuitem" data-label="calculator" aria-disabled="true">
{% lucide "calculator" %}
<span>Calculator</span>
</div>
@@ -49,17 +49,17 @@ toc:
<hr role="separator" />
<div role="group" aria-labelledby="{{ settings_id }}">
<span role="heading" id="{{ settings_id }}">Settings</span>
<div role="menuitem" data-value="profile" onclick="console.log('Open profile')">
<div role="menuitem" data-label="profile" onclick="console.log('Open profile')">
{% lucide "user" %}
<span>Profile</span>
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘P</kbd>
</div>
<div role="menuitem" data-value="billing" onclick="console.log('Open billing')">
<div role="menuitem" data-label="billing" onclick="console.log('Open billing')">
{% lucide "credit-card" %}
<span>Billing</span>
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘B</kbd>
</div>
<div role="menuitem" data-value="settings" onclick="console.log('Open settings')">
<div role="menuitem" data-label="settings" onclick="console.log('Open settings')">
{% lucide "settings" %}
<span>Settings</span>
<kbd class="ml-auto text-muted-foreground bg-transparent tracking-widest">⌘S</kbd>
@@ -209,11 +209,11 @@ toc:
</div>
{% set raw_code %}{% raw %}{% call command() %}
<div role="menuitem" data-value="calendar">
<div role="menuitem" data-label="calendar">
<svg>...</svg>
<span>Calendar</span>
</div>
<div role="menuitem" data-value="settings">
<div role="menuitem" data-label="settings">
<svg>...</svg>
<span>Settings</span>
</div>
@@ -258,4 +258,4 @@ toc:
</script>
{% endset %}
{{ code_preview("command-dialog", code_dialog | prettyHtml) }}
{{ code_preview("command-dialog", code_dialog | prettyHtml) }}
+2 -1
View File
@@ -112,7 +112,8 @@ toc:
<div class="prose">
<p>Or cherry-pick specific components, for example:</p>
</div>
{% set code %}{% raw %}import 'basecoat-css/popover';
{% set code %}{% raw %}import 'basecoat-css/basecoat';
import 'basecoat-css/popover';
import 'basecoat-css/tabs';{% endraw %}{% endset %}
{{ code_block(code, "js") }}
<div class="prose">
+506
View File
@@ -0,0 +1,506 @@
Basecoat – LLMs Guide
About
- Basecoat is a Tailwind‑first component library using semantic HTML and small vanilla JS for interactivity. No React required. Framework‑agnostic.
Install
- Tailwind project: `npm i basecoat-css`
- In CSS: `@import "tailwindcss";` then `@import "basecoat-css";`
CDN
- CSS: `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/basecoat.cdn.min.css">`
- JS (all): `<script src="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/js/all.min.js" defer></script>`
- JS (per component): include `basecoat.min.js` then `tabs.min.js`, `select.min.js`, etc.
JavaScript
- Auto‑init on `DOMContentLoaded`; observes DOM for new nodes.
- Manual: `window.basecoat.init('<name>')` or `window.basecoat.initAll()`.
- Packages to import with ESM: `import 'basecoat-css/all'` or `import 'basecoat-css/tabs'`.
Usage
- CSS‑only components: use classes like `btn`, `card`, `input`, etc.
- JS components: include the JS and use required roles/attributes (see below).
Theming
- Tailwind projects can override design tokens (e.g., `primary`, `muted-foreground`).
- Without Tailwind, use CDN CSS and add overrides after the link tag.
Common pitfalls
- Do not invent class names; use documented classes only.
- The package name is `basecoat-css` (not `basecoat`, not `@basecoat/css`).
- Interactive components require specific roles/attributes; include them.
- Basecoat is framework‑agnostic; do not mention React/Radix dependencies.
Components
- Button (CSS): `.btn`, variants like `.btn-secondary`, sizes `.btn-sm`, `.btn-lg`.
- Button Group (CSS): `.button-group` to group adjacent buttons; supports horizontal/vertical.
- Input (CSS): `.input` for text inputs; pairs with `.label` and `.field` patterns.
- Textarea (CSS): `.textarea` styled multiline input.
- Label (CSS): `.label` for associated form labels.
- Field (CSS): `.field` wrapper for label, input, hint, error.
- Form (CSS): `.form` patterns for layout and validation styling.
- Checkbox (CSS): styled native checkbox; use `input[type='checkbox']`.
- Radio Group (CSS): `.radio-group` pattern using native radios.
- Switch (CSS): styled checkbox with `role="switch"` if needed.
- Select (JS): `.select` custom select; trigger button + popover + `[role=listbox]` and `[role=option]`. Dispatches `change` with `detail.value`.
- Combobox (JS): `.combobox` typeahead; similar roles to Select.
- Command (JS): `.command` with search input and `[role=menu]`/`[role=menuitem]`; keyboard nav.
- Tabs (JS): `.tabs` with `[role=tablist]`, `[role=tab]`, `aria-controls` panels; arrow keys switch.
- Popover (JS): `.popover` wraps a button and `[data-popover]` content; toggles `aria-hidden`.
- Dropdown Menu (JS): `.dropdown-menu` trigger + menu with `[role=menu]`/`[role=menuitem]`.
- Dialog (CSS/Native): `<dialog>` styled; `.command-dialog` variant wraps `.command`.
- Alert Dialog (CSS/Native): confirm/cancel flow using `<dialog>` semantics.
- Drawer (CSS/Native): off‑canvas using `<dialog>`/popover; class names documented in examples.
- Tooltip (CSS/JS-lite): `.tooltip` small info overlays.
- Toast (JS): global notifications container `#toaster`; dispatch `basecoat:toast` with `{ detail: { config } }`.
- Sidebar (JS): `.sidebar` with collapsible items; tracks current page via `aria-current`.
- Breadcrumb (CSS): `.breadcrumb` list of links.
- Pagination (CSS): `.pagination` navigation controls.
- Card (CSS): `.card` container with header/section/footer slots.
- Alert (CSS): `.alert` contextual banners.
- Badge (CSS): `.badge` and `.badge-outline`.
- Avatar (CSS): `.avatar` image + fallback.
- Skeleton (CSS): `.skeleton` loading placeholder.
- Progress (CSS): `.progress` bar.
- Slider (CSS): `.slider` styled input range.
- Spinner (CSS): `.spinner` loading indicator.
- Kbd (CSS): `.kbd` keyboard key style.
- Table (CSS): `.table` basic and striped tables.
- Item (CSS): `.item` list rows with icons and actions.
- Empty State (CSS): `.empty` placeholder content.
- Theme Switcher (JS-lite): `.theme-switcher` toggles themes (example in docs).
Events
- `basecoat:toast` (document): show a toast by dispatching `{ detail: { config } }`.
- `basecoat:initialized` (component root): emitted after a component initializes.
- `basecoat:popover` (document): coordination event to close other popovers.
Accessibility
- Use semantic roles/attributes from examples (tabs, menus, listbox/options).
- Disabled items: use `disabled` or `aria-disabled="true"`; JS will ignore them.
Troubleshooting
- If a component doesn’t work, ensure its JS is loaded and required roles/IDs match examples.
- For dynamic content, call `window.basecoat.init('<name>')` or `initAll()` after insertion.
License
- MIT (see LICENSE.md)
---
description: Basecoat
alwaysApply: true
applyTo: "**"
downloadedFrom: https://basecoatui.com/llms.txt
version: 0.3.x
---
# Basecoat
Basecoat is a Tailwind CSS v4‑first UI library using semantic HTML and small vanilla JS for interactivity. No React required; framework‑agnostic.
- Basecoat docs: https://basecoatui.com
- Package CSS: `basecoat-css`
## Basecoat install notes
1. Basecoat targets Tailwind CSS v4.
2. Import Tailwind in your CSS, then import Basecoat:
```css
@import "tailwindcss";
@import "basecoat-css";
```
3. For interactive components, include JS. With ESM you can load all or cherry‑pick:
```js
import 'basecoat-css/all';
// or cherry‑pick
import 'basecoat-css/tabs';
import 'basecoat-css/select';
import 'basecoat-css/popover';
import 'basecoat-css/dropdown-menu';
import 'basecoat-css/sidebar';
import 'basecoat-css/toast';
```
4. CDN usage (no build tool):
```html
<link href="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/basecoat.cdn.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@latest/dist/js/all.min.js" defer></script>
```
5. JS auto‑initializes on `DOMContentLoaded` and observes the DOM for added nodes. You can manually re‑init:
```js
window.basecoat.init('tabs');
window.basecoat.initAll();
```
## Basecoat usage rules
1. Add Basecoat class names to semantic HTML. Combine with Tailwind utilities for layout/spacing.
2. Interactive components require specific roles/attributes (see each component). Keep `aria-*` for semantics and `data-*` for visual states.
3. Disabled elements must use `disabled` or `aria-disabled="true"`. Basecoat JS filters them from interactions.
4. Only use documented Basecoat classes or Tailwind utilities. Don’t invent new class names.
5. Prefer native elements (button, input, dialog) and keep markup minimal.
Basecoat class name categories (for reference)
- `component`: the main component class
- `part`: a child element/slot of a component
- `style`: variants changing appearance
- `size`: size variants
- `behavior`: classes that change behavior
- `modifier`: boolean/conditional changes
## Config
Basecoat has no Tailwind plugin config. Import the CSS after Tailwind. Customize design tokens via your Tailwind theme or overrides.
Minimal CSS setup:
```css
@import "tailwindcss";
@import "basecoat-css";
```
## Basecoat tokens (commonly used)
These semantic tokens are referenced by classes (examples): `background`, `foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `accent`, `accent-foreground`, `muted`, `muted-foreground`, `card`, `card-foreground`, `popover`, `popover-foreground`, `destructive`, `ring`, `input`, `border`.
Rules
- Tokens should be defined in your Tailwind theme or be available via your design system. You can override colors using Tailwind utilities (e.g., `bg-primary`).
- Prefer utilities for one‑off overrides; keep semantic tokens consistent.
## Basecoat components
### alert
Alerts inform users about important events.
#### Class names
- component: `alert`, `alert-destructive`
#### Syntax
```html
<div role="alert" class="alert">{CONTENT}</div>
```
#### Rules
- Use `alert-destructive` for destructive context.
### avatar
Avatar shows a user thumbnail.
#### Class names
- component: `avatar`
#### Syntax
```html
<div class="avatar"><img src="{image-url}" alt="{alt}" /></div>
```
#### Rules
- Provide meaningful `alt` text or decorative as appropriate.
### badge
Badges display statuses or counts.
#### Class names
- component: `badge`
- style: `badge-outline`, `badge-destructive`
#### Syntax
```html
<span class="badge {MODIFIER}">{TEXT}</span>
```
#### Rules
- {MODIFIER} is optional; combine with utilities for spacing.
### button
Buttons trigger actions.
#### Class names
- component: `btn`
- style: `btn-primary`, `btn-secondary`, `btn-outline`, `btn-ghost`, `btn-link`, `btn-destructive`
- size: `btn-sm`, `btn-lg`
- icon: `btn-icon`, `btn-sm-icon`, `btn-lg-icon`
#### Syntax
```html
<button class="btn {MODIFIER}">{CONTENT}</button>
```
#### Rules
- Use `disabled` attribute for disabled state. Use `aria-pressed="true"` for toggle buttons.
### button-group
Groups adjacent buttons into a single control.
#### Class names
- component: `button-group`
#### Syntax
```html
<div class="button-group" data-orientation="{horizontal|vertical}">{BUTTONS}</div>
```
#### Rules
- Use separators with `<hr role="separator">` inside the group if needed.
### card
Card container with optional header/section/footer.
#### Class names
- component: `card`
- part: `> header`, `> section`, `> footer`
#### Syntax
```html
<article class="card">
<header><h2>{TITLE}</h2><p>{SUBTITLE}</p></header>
<section>{CONTENT}</section>
<footer>{ACTIONS}</footer>
</article>
```
#### Rules
- Parts are optional; structure with semantic elements.
### checkbox (CSS)
Styled native checkbox.
#### Class names
- component: `.input[type='checkbox']:not([role='switch'])`
#### Syntax
```html
<input type="checkbox" class="input" />
```
#### Rules
- Use standard checkbox semantics; pair with `<label>`.
### command (JS)
Command palette with search and results.
#### Class names
- component: `command`, wrapper variant: `command-dialog`
- part: `> header input`, `[role='menu']`, `[role='menuitem']`
#### Syntax
```html
<div class="command">
<header><input type="text" placeholder="Search" /></header>
<div role="menu">
<button role="menuitem">{ITEM}</button>
</div>
</div>
```
#### Rules
- Requires JS. Keyboard navigation supported. Disabled items must use `aria-disabled="true"` or `disabled` and are ignored.
### dropdown-menu (JS)
Contextual menu triggered by a button.
#### Class names
- component: `dropdown-menu`
- part: `> button` (trigger), `[role='menu']`, `[role='menuitem']`
#### Syntax
```html
<div class="dropdown-menu">
<button type="button">{TRIGGER}</button>
<div role="menu"> <button role="menuitem">{ITEM}</button> </div>
</div>
```
#### Rules
- Requires JS. Uses `basecoat:popover` to coordinate with other popovers.
### input (CSS)
Styled text input fields.
#### Class names
- component: `input` (applied to native `<input>` types)
#### Syntax
```html
<input type="text" class="input" placeholder="{Placeholder}" />
```
#### Rules
- Supports common input types; pair with `.label` and `.field` patterns.
### kbd (CSS)
Keyboard key visual.
#### Class names
- component: `kbd`
#### Syntax
```html
<kbd class="kbd">⌘K</kbd>
```
### label (CSS)
Styled form labels.
#### Class names
- component: `label`
#### Syntax
```html
<label class="label" for="{id}">{TEXT}</label>
```
### popover (JS)
Small overlay anchored to a trigger.
#### Class names
- component: `popover`
- part: `> button` (trigger), `[data-popover]` (content)
#### Syntax
```html
<div class="popover">
<button type="button">{TRIGGER}</button>
<section data-popover aria-hidden="true">{CONTENT}</section>
</div>
```
#### Rules
- Requires JS. Toggling sets `aria-hidden` on content and `aria-expanded` on trigger.
### radio-group (CSS)
Styled native radio inputs.
#### Class names
- component: `.input[type='radio']`
#### Syntax
```html
<label><input type="radio" class="input" name="{name}" /> {LABEL}</label>
```
### select (JS)
Custom select with searchable popover and listbox.
#### Class names
- component: `select`
- part: `> button` (trigger with label span), `[data-popover]` (panel), `[role='listbox']`, `[role='option']`, `> input[type='hidden']`, optional `header input[type='text']` filter
#### Syntax
```html
<div class="select">
<button type="button" aria-expanded="false"><span>{LABEL}</span></button>
<input type="hidden" name="{name}" value="{value}" />
<section data-popover aria-hidden="true">
<header><input type="text" placeholder="Filter" /></header>
<div role="listbox">
<div role="option" data-value="{value}" data-label="{label}">{label}</div>
</div>
</section>
</div>
```
#### Rules
- Requires JS. Dispatches `change` on root with `detail.value`. Disabled options use `aria-disabled="true"`.
### sidebar (JS)
Collapsible navigation sidebar.
#### Class names
- component: `sidebar`
#### Rules
- Requires JS. Uses `aria-current="page"` for active link styling.
### switch (CSS)
Styled switch using checkbox with `role="switch"`.
#### Class names
- component: `.input[type='checkbox'][role='switch']`
#### Syntax
```html
<input type="checkbox" role="switch" class="input" />
```
### table (CSS)
Styled tables.
#### Class names
- component: `table`
#### Syntax
```html
<table class="table">{ROWS}</table>
```
### tabs (JS)
Tabbed interface with keyboard navigation.
#### Class names
- component: `tabs`
- part: `[role='tablist']`, `[role='tab']`, `[role='tabpanel']`
#### Syntax
```html
<div class="tabs">
<div role="tablist">
<button role="tab" aria-controls="panel-1" aria-selected="true" tabindex="0">Tab 1</button>
<button role="tab" aria-controls="panel-2" aria-selected="false" tabindex="-1">Tab 2</button>
</div>
<section id="panel-1" role="tabpanel">...</section>
<section id="panel-2" role="tabpanel" hidden>...</section>
</div>
```
#### Rules
- Requires JS. Arrow keys move focus; keep `aria-controls` and `aria-selected` accurate.
### textarea (CSS)
Styled multiline text input.
#### Class names
- component: `textarea`
#### Syntax
```html
<textarea class="textarea" rows="{n}"></textarea>
```
### toast (JS)
Global notifications.
#### Class names
- component: `#toaster` (container), `toast` (item)
#### Syntax
```html
<section id="toaster"></section>
```
Show a toast:
```js
document.dispatchEvent(new CustomEvent('basecoat:toast', { detail: { config: { title: 'Saved', description: 'Changes persisted', category: 'success' } } }))
```
#### Rules
- Requires JS. Uses timeouts; hovering pauses timers.
### popover/dialog variants (CSS/JS‑lite)
Dialog and drawer patterns built on native elements, styled by Basecoat.
#### Class names
- wrapper: `command-dialog` (for Command palette in a dialog)
#### Rules
- Use native `<dialog>` or popover attributes where applicable.
### other CSS components
Additional CSS components include: `progress`, `skeleton`, `slider`, `spinner`, `pagination`, `breadcrumb`, `item`, `empty`, `theme-switcher`, `form`, `field`, `input-group`, `radio-group`, `switch`, `avatar`, `badge`, `alert`, `card`, `kbd`, `table`.
## Events
- `basecoat:initialized` (component root): emitted after a component initializes.
- `basecoat:popover` (document): used to close other popovers when one opens.
- `basecoat:toast` (document): creates a toast using `{ detail: { config } }`.
## Accessibility
- Use semantic roles/labels per component. Keep `aria-*` for semantics and `data-*` for visual states.
- Disabled items must use `disabled` or `aria-disabled="true"` to be filtered from interactions.
## Troubleshooting
- If interactivity doesn’t work, ensure the component’s JS is included and required roles/IDs match the examples.
- For dynamic content, call `window.basecoat.init('<name>')` or `window.basecoat.initAll()` after insertion.
## License
MIT (see LICENSE.md)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "basecoat",
"version": "0.3.3",
"version": "0.3.5",
"description": "shadcn/ui for the rest of us",
"author": "hunvreus",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "basecoat-cli",
"version": "0.3.3",
"version": "0.3.5",
"description": "Add Basecoat components to your project",
"author": "hunvreus",
"license": "MIT",
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "basecoat-css",
"version": "0.3.3",
"version": "0.3.5",
"description": "Tailwind CSS for Basecoat components",
"author": {
"name": "hunvreus",
@@ -40,6 +40,8 @@
"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",
"./command": "./dist/js/command.js",
+1 -1
View File
@@ -121,7 +121,7 @@ async function build() {
// Create combined component files
console.log('Creating combined component files...');
const componentsToCombine = ['basecoat.js', 'dropdown-menu.js', 'popover.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
const componentsToCombine = ['basecoat.js', 'command.js', 'dropdown-menu.js', 'popover.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
const componentPaths = componentsToCombine.map(f => path.join(srcJsDir, f));
// Create non-minified bundle
+134 -130
View File
@@ -381,42 +381,44 @@
}
/* Button Group */
.button-group {
@apply inline-flex w-fit items-stretch isolate;
> *:focus-visible,
> :is(.dropdown-menu, .popover, .select) > button:focus-visible {
@apply relative z-10;
}
> hr[role='separator'] {
@apply w-0 h-auto self-stretch border border-input shrink-0 m-0;
}
&:not([data-orientation='vertical']) {
> *:not(:first-child),
> :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
@apply rounded-l-none border-l-0;
@layer components {
.button-group {
@apply inline-flex w-fit items-stretch isolate;
> *:focus-visible,
> :is(.dropdown-menu, .popover, .select) > button:focus-visible {
@apply relative z-10;
}
> *:not(:last-child),
> :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
@apply rounded-r-none;
}
}
&[data-orientation='vertical'] {
@apply flex-col;
> hr[role='separator'] {
@apply w-auto h-px;
@apply w-0 h-auto self-stretch border border-input shrink-0 m-0;
}
> *:not(:first-child),
> :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
@apply rounded-t-none border-t-0;
}
> *:not(:last-child),
> :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
@apply rounded-b-none;
&:not([data-orientation='vertical']) {
> *:not(:first-child),
> :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
@apply rounded-l-none border-l-0;
}
> *:not(:last-child),
> :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
@apply rounded-r-none;
}
}
&[data-orientation='vertical'] {
@apply flex-col;
> hr[role='separator'] {
@apply w-auto h-px;
}
> *:not(:first-child),
> :is(.dropdown-menu, .popover, .select):not(:first-child) > button {
@apply rounded-t-none border-t-0;
}
> *:not(:last-child),
> :is(.dropdown-menu, .popover, .select):not(:last-child) > button {
@apply rounded-b-none;
}
}
}
}
@@ -988,119 +990,121 @@
}
/* Sidebar */
.sidebar {
&:not([data-sidebar-initialized]) {
@apply max-md:hidden;
}
&:not([aria-hidden]),
&[aria-hidden=false] {
@apply max-md:bg-black/50 max-md:fixed max-md:inset-0 max-md:z-40;
}
nav {
@apply bg-sidebar text-sidebar-foreground flex flex-col w-(--sidebar-mobile-width) md:w-(--sidebar-width) fixed inset-y-0 z-50 transition-transform ease-in-out duration-300;
}
& + * {
@apply transition-[margin] ease-in-out duration-300;
}
&:not([data-side]),
&[data-side=left] {
@layer components {
.sidebar {
&:not([data-sidebar-initialized]) {
@apply max-md:hidden;
}
&:not([aria-hidden]),
&[aria-hidden=false] {
@apply max-md:bg-black/50 max-md:fixed max-md:inset-0 max-md:z-40;
}
nav {
@apply left-0 border-r;
@apply bg-sidebar text-sidebar-foreground flex flex-col w-(--sidebar-mobile-width) md:w-(--sidebar-width) fixed inset-y-0 z-50 transition-transform ease-in-out duration-300;
}
& + * {
@apply relative md:ml-(--sidebar-width);
@apply transition-[margin] ease-in-out duration-300;
}
&[aria-hidden=true] {
&:not([data-side]),
&[data-side=left] {
nav {
@apply -translate-x-full;
@apply left-0 border-r;
}
& + * {
@apply md:ml-0;
@apply relative md:ml-(--sidebar-width);
}
&[aria-hidden=true] {
nav {
@apply -translate-x-full;
}
& + * {
@apply md:ml-0;
}
}
}
&[data-side=right] {
nav {
@apply right-0 border-l;
}
& + * {
@apply relative md:mr-(--sidebar-width);
}
&[aria-hidden=true] {
nav {
@apply translate-x-full;
}
& + * {
@apply md:mr-0;
}
}
}
}
&[data-side=right] {
nav {
@apply right-0 border-l;
}
& + * {
@apply relative md:mr-(--sidebar-width);
}
&[aria-hidden=true] {
nav {
@apply translate-x-full;
> header,
> footer {
@apply flex flex-col gap-2 p-2;
}
& + * {
@apply md:mr-0;
[role=separator] {
@apply border-sidebar-border mx-2 w-auto;
}
}
}
nav {
> header,
> footer {
@apply flex flex-col gap-2 p-2;
}
[role=separator] {
@apply border-sidebar-border mx-2 w-auto;
}
> section {
@apply flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto;
> section {
@apply flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto;
> [role=group] {
@apply relative flex w-full min-w-0 flex-col p-2;
}
h3 {
@apply text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0;
}
ul {
@apply flex w-full min-w-0 flex-col gap-1;
li {
@apply relative;
> a,
> details > summary {
@apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&[aria-current=page]]:bg-sidebar-accent [&[aria-current=page]]:font-medium [&[aria-current=page]]:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0;
&:not([data-variant]),
&[data-variant=default] {
@apply hover:bg-sidebar-accent hover:text-sidebar-accent-foreground;
}
&[data-variant=outline] {
@apply bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))];
}
&:not([data-size]),
&[data-size=default] {
@apply h-8 text-sm;
}
&[data-size=sm] {
@apply h-7 text-xs;
}
&[data-size=lg] {
@apply h-12 text-sm group-data-[collapsible=icon]:p-0!;
}
}
> details {
&:not([open]) {
> summary {
&::after {
@apply -rotate-90;
}
}
}
> summary {
&::after {
@apply content-[''] block size-3.5 bg-primary ml-auto transition-transform ease-linear;
@apply mask-[image:var(--chevron-down-icon)] mask-size-[1rem] mask-no-repeat mask-center;
}
}
&::details-content {
@apply px-3.5;
}
}
> [role=group] {
@apply relative flex w-full min-w-0 flex-col p-2;
}
h3 {
@apply text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0;
}
ul {
@apply border-sidebar-border flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5 w-full;
@apply flex w-full min-w-0 flex-col gap-1;
li {
@apply relative;
> a,
> details > summary {
@apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&[aria-current=page]]:bg-sidebar-accent [&[aria-current=page]]:font-medium [&[aria-current=page]]:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0;
&:not([data-variant]),
&[data-variant=default] {
@apply hover:bg-sidebar-accent hover:text-sidebar-accent-foreground;
}
&[data-variant=outline] {
@apply bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))];
}
&:not([data-size]),
&[data-size=default] {
@apply h-8 text-sm;
}
&[data-size=sm] {
@apply h-7 text-xs;
}
&[data-size=lg] {
@apply h-12 text-sm group-data-[collapsible=icon]:p-0!;
}
}
> details {
&:not([open]) {
> summary {
&::after {
@apply -rotate-90;
}
}
}
> summary {
&::after {
@apply content-[''] block size-3.5 bg-primary ml-auto transition-transform ease-linear;
@apply mask-[image:var(--chevron-down-icon)] mask-size-[1rem] mask-no-repeat mask-center;
}
}
&::details-content {
@apply px-3.5;
}
}
}
ul {
@apply border-sidebar-border flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5 w-full;
}
}
}
}
+1 -1
View File
@@ -56,7 +56,7 @@
<div>
{% if title or description %}
<header
{% for key, value in header_attrs %}
{% for key, value in header_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
+12 -3
View File
@@ -5,6 +5,8 @@
@param id {string} [optional] [default="toaster"] - Unique identifier for the toaster container.
@param toasts {array} [optional] - An array of toast objects to render initially. See the <code>toast()</code> macro for more details.
@param attrs {object} [optional] - Additional HTML attributes for the main toaster container div.
Supports caller() for custom toast markup.
#}
{% macro toaster(
id="toaster",
@@ -24,9 +26,11 @@
title=item.title,
description=item.description,
action=item.action,
cancel=item.cancel
cancel=item.cancel,
attrs=item.attrs
) }}
{% endfor %}
{{ caller() if caller }}
</div>
{% endmacro %}
@@ -44,20 +48,25 @@
- label {string}: Button text.
- onclick {string}: JavaScript code to execute on click.
- url {string}: URL for an anchor link button.
@param attrs {object} [optional] - Additional HTML attributes for the toast div.
#}
{% macro toast(
category="success",
title="",
description="",
action=None,
cancel=None
cancel=None,
attrs={}
) %}
<div
class="toast"
class="toast {{ attrs.class }}"
role="{{ 'alert' if category == 'error' else 'status' }}"
aria-atomic="true"
aria-hidden="false"
{% if category %}data-category="{{ category }}"{% endif %}
{% for key, value in attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<div class="toast-content">
{% if category in ["error", "success", "info", "warning"] %}
+12 -3
View File
@@ -5,6 +5,8 @@
@param id {string} [optional] [default="toaster"] - Unique identifier for the toaster container.
@param toasts {array} [optional] - An array of toast objects to render initially. See the <code>toast()</code> macro for more details.
@param attrs {object} [optional] - Additional HTML attributes for the main toaster container div.
Supports caller() for custom toast markup.
#}
{% macro toaster(
id="toaster",
@@ -24,9 +26,11 @@
title=item.title,
description=item.description,
action=item.action,
cancel=item.cancel
cancel=item.cancel,
attrs=item.attrs
) }}
{% endfor %}
{{ caller() if caller }}
</div>
{% endmacro %}
@@ -44,20 +48,25 @@
- label {string}: Button text.
- onclick {string}: JavaScript code to execute on click.
- url {string}: URL for an anchor link button.
@param attrs {object} [optional] - Additional HTML attributes for the toast div.
#}
{% macro toast(
category="success",
title="",
description="",
action=None,
cancel=None
cancel=None,
attrs={}
) %}
<div
class="toast"
class="toast {{ attrs.class }}"
role="{{ 'alert' if category == 'error' else 'status' }}"
aria-atomic="true"
aria-hidden="false"
{% if category %}data-category="{{ category }}"{% endif %}
{% for key, value in attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<div class="toast-content">
{% if category in ["error", "success", "info", "warning"] %}