mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Starting work on documentation
This commit is contained in:
+23
-2
@@ -1,14 +1,35 @@
|
||||
import eleventyLucideicons from "@grimlink/eleventy-plugin-lucide-icons";
|
||||
import prettier from "prettier";
|
||||
|
||||
export default async function(eleventyConfig) {
|
||||
eleventyConfig.setInputDirectory("./docs");
|
||||
eleventyConfig.setServerPassthroughCopyBehavior("passthrough");
|
||||
eleventyConfig.addPassthroughCopy({"./dist/basecoat.css": "./assets/styles.css"});
|
||||
eleventyConfig.addPlugin(eleventyLucideicons);
|
||||
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);
|
||||
}
|
||||
});
|
||||
// Registry to ensure component assets (JS) load only once per page.
|
||||
let componentRegistry = {}
|
||||
eleventyConfig.on('beforeBuild', () => { componentRegistry = {}; })
|
||||
let componentRegistry;
|
||||
eleventyConfig.on('beforeBuild', () => { componentRegistry = {}; });
|
||||
eleventyConfig.addNunjucksGlobal("registerComponent", function(componentName) {
|
||||
// Reset registry at the start of each page build
|
||||
if (!this.page) {
|
||||
componentRegistry = {};
|
||||
}
|
||||
|
||||
if (!componentRegistry[componentName]) {
|
||||
componentRegistry[componentName] = true;
|
||||
return true;
|
||||
|
||||
Vendored
+504
-17
@@ -21,12 +21,18 @@
|
||||
--color-blue-700: oklch(48.8% 0.243 264.376);
|
||||
--color-blue-900: oklch(37.9% 0.146 265.522);
|
||||
--color-blue-950: oklch(28.2% 0.091 267.935);
|
||||
--color-zinc-50: oklch(98.5% 0 0);
|
||||
--color-zinc-700: oklch(37% 0.013 285.805);
|
||||
--color-neutral-900: oklch(20.5% 0 0);
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
--spacing: 0.25rem;
|
||||
--breakpoint-sm: 40rem;
|
||||
--breakpoint-md: 48rem;
|
||||
--breakpoint-lg: 64rem;
|
||||
--container-xs: 20rem;
|
||||
--container-sm: 24rem;
|
||||
--container-md: 28rem;
|
||||
--container-lg: 32rem;
|
||||
--container-xl: 36rem;
|
||||
--text-xs: 0.75rem;
|
||||
@@ -41,6 +47,8 @@
|
||||
--text-xl--line-height: calc(1.75 / 1.25);
|
||||
--text-2xl: 1.5rem;
|
||||
--text-2xl--line-height: calc(2 / 1.5);
|
||||
--text-3xl: 1.875rem;
|
||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
@@ -281,6 +289,15 @@
|
||||
.top-1\/2 {
|
||||
top: calc(1/2 * 100%);
|
||||
}
|
||||
.top-3 {
|
||||
top: calc(var(--spacing) * 3);
|
||||
}
|
||||
.top-22 {
|
||||
top: calc(var(--spacing) * 22);
|
||||
}
|
||||
.right-3 {
|
||||
right: calc(var(--spacing) * 3);
|
||||
}
|
||||
.left-0 {
|
||||
left: calc(var(--spacing) * 0);
|
||||
}
|
||||
@@ -299,21 +316,51 @@
|
||||
.col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
@media (width >= 40rem) {
|
||||
max-width: 40rem;
|
||||
}
|
||||
@media (width >= 48rem) {
|
||||
max-width: 48rem;
|
||||
}
|
||||
@media (width >= 64rem) {
|
||||
max-width: 64rem;
|
||||
}
|
||||
@media (width >= 80rem) {
|
||||
max-width: 80rem;
|
||||
}
|
||||
@media (width >= 96rem) {
|
||||
max-width: 96rem;
|
||||
}
|
||||
}
|
||||
.mx-auto {
|
||||
margin-inline: auto;
|
||||
}
|
||||
.my-4 {
|
||||
margin-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.my-6 {
|
||||
margin-block: calc(var(--spacing) * 6);
|
||||
}
|
||||
.mt-4 {
|
||||
margin-top: calc(var(--spacing) * 4);
|
||||
}
|
||||
.mr-auto {
|
||||
margin-right: auto;
|
||||
}
|
||||
.mb-6 {
|
||||
margin-bottom: calc(var(--spacing) * 6);
|
||||
}
|
||||
.ml-\(--sidebar-width\) {
|
||||
margin-left: var(--sidebar-width);
|
||||
}
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
@@ -346,10 +393,6 @@
|
||||
width: calc(var(--spacing) * 4);
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
.size-6 {
|
||||
width: calc(var(--spacing) * 6);
|
||||
height: calc(var(--spacing) * 6);
|
||||
}
|
||||
.size-8 {
|
||||
width: calc(var(--spacing) * 8);
|
||||
height: calc(var(--spacing) * 8);
|
||||
@@ -390,6 +433,12 @@
|
||||
.max-h-\[612px\] {
|
||||
max-height: 612px;
|
||||
}
|
||||
.max-h-\[650px\] {
|
||||
max-height: 650px;
|
||||
}
|
||||
.min-h-\[350px\] {
|
||||
min-height: 350px;
|
||||
}
|
||||
.scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||
@@ -413,6 +462,9 @@
|
||||
.w-4 {
|
||||
width: calc(var(--spacing) * 4);
|
||||
}
|
||||
.w-6 {
|
||||
width: calc(var(--spacing) * 6);
|
||||
}
|
||||
.w-72 {
|
||||
width: calc(var(--spacing) * 72);
|
||||
}
|
||||
@@ -440,12 +492,24 @@
|
||||
.max-w-lg {
|
||||
max-width: var(--container-lg);
|
||||
}
|
||||
.max-w-md {
|
||||
max-width: var(--container-md);
|
||||
}
|
||||
.max-w-screen-lg {
|
||||
max-width: var(--breakpoint-lg);
|
||||
}
|
||||
.max-w-screen-md {
|
||||
max-width: var(--breakpoint-md);
|
||||
}
|
||||
.max-w-sm {
|
||||
max-width: var(--container-sm);
|
||||
}
|
||||
.max-w-xl {
|
||||
max-width: var(--container-xl);
|
||||
}
|
||||
.min-w-0 {
|
||||
min-width: calc(var(--spacing) * 0);
|
||||
}
|
||||
.min-w-5 {
|
||||
min-width: calc(var(--spacing) * 5);
|
||||
}
|
||||
@@ -535,6 +599,13 @@
|
||||
.gap-8 {
|
||||
gap: calc(var(--spacing) * 8);
|
||||
}
|
||||
.space-y-2 {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
|
||||
margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
|
||||
}
|
||||
}
|
||||
.space-y-4 {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-y-reverse: 0;
|
||||
@@ -542,6 +613,13 @@
|
||||
margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
|
||||
}
|
||||
}
|
||||
.space-y-6 {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
|
||||
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
|
||||
}
|
||||
}
|
||||
.gap-x-2 {
|
||||
column-gap: calc(var(--spacing) * 2);
|
||||
}
|
||||
@@ -555,6 +633,13 @@
|
||||
margin-inline-end: calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
}
|
||||
.space-x-2 {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));
|
||||
margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
}
|
||||
.gap-y-4 {
|
||||
row-gap: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -590,6 +675,9 @@
|
||||
.rounded-sm {
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.rounded-xl {
|
||||
border-radius: var(--radius-xl);
|
||||
}
|
||||
.border {
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
@@ -605,6 +693,9 @@
|
||||
.border-amber-50 {
|
||||
border-color: var(--color-amber-50);
|
||||
}
|
||||
.\!bg-neutral-900 {
|
||||
background-color: var(--color-neutral-900) !important;
|
||||
}
|
||||
.bg-accent {
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
@@ -626,6 +717,9 @@
|
||||
.object-cover {
|
||||
object-fit: cover;
|
||||
}
|
||||
.p-0 {
|
||||
padding: calc(var(--spacing) * 0);
|
||||
}
|
||||
.p-2 {
|
||||
padding: calc(var(--spacing) * 2);
|
||||
}
|
||||
@@ -635,6 +729,9 @@
|
||||
.p-4 {
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
.p-10 {
|
||||
padding: calc(var(--spacing) * 10);
|
||||
}
|
||||
.px-0 {
|
||||
padding-inline: calc(var(--spacing) * 0);
|
||||
}
|
||||
@@ -659,9 +756,18 @@
|
||||
.py-4 {
|
||||
padding-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.py-6 {
|
||||
padding-block: calc(var(--spacing) * 6);
|
||||
}
|
||||
.pt-8 {
|
||||
padding-top: calc(var(--spacing) * 8);
|
||||
}
|
||||
.pb-4 {
|
||||
padding-bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
.pb-12 {
|
||||
padding-bottom: calc(var(--spacing) * 12);
|
||||
}
|
||||
.pl-6 {
|
||||
padding-left: calc(var(--spacing) * 6);
|
||||
}
|
||||
@@ -680,6 +786,10 @@
|
||||
.font-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.text-3xl {
|
||||
font-size: var(--text-3xl);
|
||||
line-height: var(--tw-leading, var(--text-3xl--line-height));
|
||||
}
|
||||
.text-base {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--tw-leading, var(--text-base--line-height));
|
||||
@@ -720,6 +830,14 @@
|
||||
--tw-font-weight: var(--font-weight-normal);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
.font-semibold {
|
||||
--tw-font-weight: var(--font-weight-semibold);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
.tracking-tight {
|
||||
--tw-tracking: var(--tracking-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
.tracking-widest {
|
||||
--tw-tracking: var(--tracking-widest);
|
||||
letter-spacing: var(--tracking-widest);
|
||||
@@ -727,6 +845,12 @@
|
||||
.break-words {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.whitespace-pre-wrap {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.text-amber-900 {
|
||||
color: var(--color-amber-900);
|
||||
}
|
||||
@@ -754,10 +878,16 @@
|
||||
.text-transparent {
|
||||
color: transparent;
|
||||
}
|
||||
.text-zinc-50 {
|
||||
color: var(--color-zinc-50);
|
||||
}
|
||||
.tabular-nums {
|
||||
--tw-numeric-spacing: tabular-nums;
|
||||
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
|
||||
}
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
.underline-offset-4 {
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
@@ -778,6 +908,9 @@
|
||||
.ring-sidebar-ring {
|
||||
--tw-ring-color: var(--color-sidebar-ring);
|
||||
}
|
||||
.ring-offset-background {
|
||||
--tw-ring-offset-color: var(--color-background);
|
||||
}
|
||||
.outline-hidden {
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
@@ -786,6 +919,10 @@
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
.outline {
|
||||
outline-style: var(--tw-outline-style);
|
||||
outline-width: 1px;
|
||||
}
|
||||
.filter {
|
||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||
}
|
||||
@@ -925,6 +1062,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-zinc-700 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-zinc-700);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-foreground {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -939,6 +1083,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:text-zinc-50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-zinc-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:underline {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -964,6 +1115,12 @@
|
||||
border-color: var(--color-ring);
|
||||
}
|
||||
}
|
||||
.focus-visible\:ring-1 {
|
||||
&:focus-visible {
|
||||
--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);
|
||||
}
|
||||
}
|
||||
.focus-visible\:ring-2 {
|
||||
&:focus-visible {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
@@ -976,6 +1133,11 @@
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
.focus-visible\:ring-ring {
|
||||
&:focus-visible {
|
||||
--tw-ring-color: var(--color-ring);
|
||||
}
|
||||
}
|
||||
.focus-visible\:ring-ring\/50 {
|
||||
&:focus-visible {
|
||||
--tw-ring-color: color-mix(in srgb, var(--ring) 50%, transparent);
|
||||
@@ -984,6 +1146,28 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.focus-visible\:ring-offset-2 {
|
||||
&:focus-visible {
|
||||
--tw-ring-offset-width: 2px;
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
}
|
||||
}
|
||||
.focus-visible\:outline-none {
|
||||
&:focus-visible {
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
}
|
||||
}
|
||||
.disabled\:pointer-events-none {
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.disabled\:opacity-50 {
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
}
|
||||
}
|
||||
.has-\[input\[type\=\'checkbox\'\]\:checked\]\:border-blue-600 {
|
||||
&:has(*:is(input[type='checkbox']:checked)) {
|
||||
border-color: var(--color-blue-600);
|
||||
@@ -1030,6 +1214,21 @@
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
.lg\:gap-10 {
|
||||
@media (width >= 64rem) {
|
||||
gap: calc(var(--spacing) * 10);
|
||||
}
|
||||
}
|
||||
.lg\:py-8 {
|
||||
@media (width >= 64rem) {
|
||||
padding-block: calc(var(--spacing) * 8);
|
||||
}
|
||||
}
|
||||
.xl\:block {
|
||||
@media (width >= 80rem) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.\@md\:w-auto {
|
||||
@container (width >= 28rem) {
|
||||
width: auto;
|
||||
@@ -1146,6 +1345,37 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.\[\&_a\]\:inline-block {
|
||||
& a {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.\[\&_a\]\:text-muted-foreground {
|
||||
& a {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
}
|
||||
.\[\&_a\]\:no-underline {
|
||||
& a {
|
||||
text-decoration-line: none;
|
||||
}
|
||||
}
|
||||
.\[\&_a\]\:transition-colors {
|
||||
& a {
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
}
|
||||
.\[\&_a\]\:hover\:text-foreground {
|
||||
& a {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.\[\&_img\]\:size-8 {
|
||||
& img {
|
||||
width: calc(var(--spacing) * 8);
|
||||
@@ -1184,6 +1414,11 @@
|
||||
--tw-ring-color: var(--color-background);
|
||||
}
|
||||
}
|
||||
.\[\&_img\]\:ring-card {
|
||||
& img {
|
||||
--tw-ring-color: var(--color-card);
|
||||
}
|
||||
}
|
||||
.\[\&_img\]\:grayscale {
|
||||
& img {
|
||||
--tw-grayscale: grayscale(100%);
|
||||
@@ -1209,11 +1444,62 @@
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
}
|
||||
}
|
||||
.\[\&_li\]\:mt-0 {
|
||||
& li {
|
||||
margin-top: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
.\[\&_li\]\:pt-2 {
|
||||
& li {
|
||||
padding-top: calc(var(--spacing) * 2);
|
||||
}
|
||||
}
|
||||
.\[\&_svg\]\:pointer-events-none {
|
||||
& svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.\[\&_svg\]\:size-4 {
|
||||
& svg {
|
||||
width: calc(var(--spacing) * 4);
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.\[\&_svg\]\:h-3 {
|
||||
& svg {
|
||||
height: calc(var(--spacing) * 3);
|
||||
}
|
||||
}
|
||||
.\[\&_svg\]\:w-3 {
|
||||
& svg {
|
||||
width: calc(var(--spacing) * 3);
|
||||
}
|
||||
}
|
||||
.\[\&_svg\]\:shrink-0 {
|
||||
& svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.\[\&_svg\]\:\!text-destructive {
|
||||
& svg {
|
||||
color: var(--color-destructive) !important;
|
||||
}
|
||||
}
|
||||
.\[\&_ul\]\:m-0 {
|
||||
& ul {
|
||||
margin: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
.\[\&_ul\]\:list-none {
|
||||
& ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
.\[\&_ul_ul\]\:pl-4 {
|
||||
& ul ul {
|
||||
padding-left: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.md\:\*\:\[\.card\]\:basis-1\/4 {
|
||||
@media (width >= 48rem) {
|
||||
:is(& > *) {
|
||||
@@ -1223,6 +1509,80 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.\[\&\>h2\]\:mt-16 {
|
||||
&>h2 {
|
||||
margin-top: calc(var(--spacing) * 16);
|
||||
}
|
||||
}
|
||||
.\[\&\>h2\]\:scroll-m-22 {
|
||||
&>h2 {
|
||||
scroll-margin: calc(var(--spacing) * 22);
|
||||
}
|
||||
}
|
||||
.\[\&\>h2\]\:border-b {
|
||||
&>h2 {
|
||||
border-bottom-style: var(--tw-border-style);
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
}
|
||||
.\[\&\>h2\]\:pb-4 {
|
||||
&>h2 {
|
||||
padding-bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.\[\&\>h2\]\:text-xl {
|
||||
&>h2 {
|
||||
font-size: var(--text-xl);
|
||||
line-height: var(--tw-leading, var(--text-xl--line-height));
|
||||
}
|
||||
}
|
||||
.\[\&\>h2\]\:font-semibold {
|
||||
&>h2 {
|
||||
--tw-font-weight: var(--font-weight-semibold);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
}
|
||||
.\[\&\>h2\]\:tracking-tight {
|
||||
&>h2 {
|
||||
--tw-tracking: var(--tracking-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
}
|
||||
.\[\&\>h2\]\:first\:mt-0 {
|
||||
&>h2 {
|
||||
&:first-child {
|
||||
margin-top: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
.\[\&\>h3\]\:mt-8 {
|
||||
&>h3 {
|
||||
margin-top: calc(var(--spacing) * 8);
|
||||
}
|
||||
}
|
||||
.\[\&\>h3\]\:scroll-m-22 {
|
||||
&>h3 {
|
||||
scroll-margin: calc(var(--spacing) * 22);
|
||||
}
|
||||
}
|
||||
.\[\&\>h3\]\:text-lg {
|
||||
&>h3 {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--tw-leading, var(--text-lg--line-height));
|
||||
}
|
||||
}
|
||||
.\[\&\>h3\]\:font-semibold {
|
||||
&>h3 {
|
||||
--tw-font-weight: var(--font-weight-semibold);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
}
|
||||
.\[\&\>h3\]\:tracking-tight {
|
||||
&>h3 {
|
||||
--tw-tracking: var(--tracking-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
}
|
||||
.\[\&\>svg\]\:size-4 {
|
||||
&>svg {
|
||||
width: calc(var(--spacing) * 4);
|
||||
@@ -1434,7 +1794,7 @@
|
||||
}
|
||||
}
|
||||
@layer components {
|
||||
.btn, .btn-primary, .btn-secondary, .btn-outline, .btn-ghost, .btn-link, .btn-destructive, .btn-sm, .btn-sm-primary, .btn-sm-secondary, .btn-sm-outline, .btn-sm-ghost, .btn-sm-link, .btn-sm-destructive, .btn-lg, .btn-lg-primary, .btn-lg-secondary, .btn-lg-outline, .btn-lg-ghost, .btn-lg-link, .btn-lg-destructive, .btn-icon, .btn-icon-primary, .btn-icon-secondary, .btn-icon-outline, .btn-icon-ghost, .btn-icon-link, .btn-icon-destructive {
|
||||
.btn, .btn-primary, .btn-secondary, .btn-outline, .btn-ghost, .btn-link, .btn-destructive, .btn-sm, .btn-sm-primary, .btn-sm-secondary, .btn-sm-outline, .btn-sm-ghost, .btn-sm-link, .btn-sm-destructive, .btn-lg, .btn-lg-primary, .btn-lg-secondary, .btn-lg-outline, .btn-lg-ghost, .btn-lg-link, .btn-lg-destructive, .btn-icon, .btn-icon-primary, .btn-icon-secondary, .btn-icon-outline, .btn-icon-ghost, .btn-icon-link, .btn-icon-destructive, .btn-sm-icon, .btn-sm-icon-primary, .btn-sm-icon-secondary, .btn-sm-icon-outline, .btn-sm-icon-ghost, .btn-sm-icon-link, .btn-sm-icon-destructive, .btn-lg-icon, .btn-lg-icon-primary, .btn-lg-icon-secondary, .btn-lg-icon-outline, .btn-lg-icon-ghost, .btn-lg-icon-link, .btn-lg-icon-destructive {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
@@ -1519,6 +1879,10 @@
|
||||
padding-inline: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
}
|
||||
.btn-sm-icon, .btn-sm-icon-primary, .btn-sm-icon-secondary, .btn-sm-icon-outline, .btn-sm-icon-ghost, .btn-sm-icon-link, .btn-sm-icon-destructive {
|
||||
width: calc(var(--spacing) * 8);
|
||||
height: calc(var(--spacing) * 8);
|
||||
}
|
||||
.btn-lg, .btn-lg-primary, .btn-lg-secondary, .btn-lg-outline, .btn-lg-ghost, .btn-lg-link, .btn-lg-destructive {
|
||||
height: calc(var(--spacing) * 10);
|
||||
gap: calc(var(--spacing) * 2);
|
||||
@@ -1527,7 +1891,11 @@
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
.btn, .btn-primary, .btn-sm, .btn-sm-primary, .btn-lg, .btn-lg-primary, .btn-icon, .btn-icon-primary {
|
||||
.btn-lg-icon, .btn-lg-icon-primary, .btn-lg-icon-secondary, .btn-lg-icon-outline, .btn-lg-icon-ghost, .btn-lg-icon-link, .btn-lg-icon-destructive {
|
||||
width: calc(var(--spacing) * 10);
|
||||
height: calc(var(--spacing) * 10);
|
||||
}
|
||||
.btn, .btn-primary, .btn-sm, .btn-sm-primary, .btn-lg, .btn-lg-primary, .btn-icon, .btn-icon-primary, .btn-sm-icon, .btn-sm-icon-primary, .btn-lg-icon, .btn-lg-icon-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-primary-foreground);
|
||||
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
||||
@@ -1547,7 +1915,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-secondary, .btn-sm-secondary, .btn-lg-secondary, .btn-icon-secondary {
|
||||
.btn-secondary, .btn-sm-secondary, .btn-lg-secondary, .btn-icon-secondary, .btn-sm-icon-secondary, .btn-lg-icon-secondary {
|
||||
background-color: var(--color-secondary);
|
||||
color: var(--color-secondary-foreground);
|
||||
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
||||
@@ -1559,7 +1927,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-outline, .btn-sm-outline, .btn-lg-outline, .btn-icon-outline {
|
||||
.btn-outline, .btn-sm-outline, .btn-lg-outline, .btn-icon-outline, .btn-sm-icon-outline, .btn-lg-icon-outline {
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
background-color: var(--color-background);
|
||||
@@ -1585,7 +1953,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-ghost, .btn-sm-ghost, .btn-lg-ghost, .btn-icon-ghost {
|
||||
.btn-ghost, .btn-sm-ghost, .btn-lg-ghost, .btn-icon-ghost, .btn-sm-icon-ghost, .btn-lg-icon-ghost {
|
||||
&:hover, &[aria-pressed='true'] {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-accent-foreground);
|
||||
@@ -1597,7 +1965,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-link, .btn-sm-link, .btn-lg-link, .btn-icon-link {
|
||||
.btn-link, .btn-sm-link, .btn-lg-link, .btn-icon-link, .btn-sm-icon-link, .btn-lg-icon-link {
|
||||
color: var(--color-primary);
|
||||
text-underline-offset: 4px;
|
||||
&:hover, &[aria-pressed='true'] {
|
||||
@@ -1608,7 +1976,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-destructive, .btn-sm-destructive, .btn-lg-destructive, .btn-icon-destructive {
|
||||
.btn-destructive, .btn-sm-destructive, .btn-lg-destructive, .btn-icon-destructive, .btn-sm-icon-destructive, .btn-lg-icon-destructive {
|
||||
background-color: var(--color-destructive);
|
||||
color: var(--color-white);
|
||||
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
||||
@@ -2401,6 +2769,12 @@
|
||||
color: var(--color-accent-foreground);
|
||||
}
|
||||
}
|
||||
&:focus-visible {
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
&:focus-visible {
|
||||
color: var(--color-accent-foreground);
|
||||
}
|
||||
}
|
||||
[role='option'] {
|
||||
padding-right: calc(var(--spacing) * 7.5);
|
||||
@@ -2981,6 +3355,9 @@
|
||||
}
|
||||
@layer components {
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
[role='tablist'] {
|
||||
display: inline-flex;
|
||||
height: calc(var(--spacing) * 9);
|
||||
@@ -3408,6 +3785,116 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
@layer components {
|
||||
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6, .h1, .h2, .h3, .h4, .h5, .h6 {
|
||||
--tw-leading: var(--leading-tight);
|
||||
line-height: var(--leading-tight);
|
||||
--tw-font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
.prose h1, .h1 {
|
||||
font-size: var(--text-2xl);
|
||||
line-height: var(--tw-leading, var(--text-2xl--line-height));
|
||||
}
|
||||
.prose h2, .h2 {
|
||||
font-size: var(--text-xl);
|
||||
line-height: var(--tw-leading, var(--text-xl--line-height));
|
||||
}
|
||||
.prose h3, .h3 {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--tw-leading, var(--text-lg--line-height));
|
||||
}
|
||||
.prose h4, .prose h5, .prose h6, .h4, .h5, .h6 {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--tw-leading, var(--text-base--line-height));
|
||||
}
|
||||
.prose a, .link {
|
||||
--tw-font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
text-decoration-line: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
.prose code, .code {
|
||||
position: relative;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--color-muted);
|
||||
padding-inline: 0.3rem;
|
||||
padding-block: 0.2rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
}
|
||||
.prose pre:has(> code), .code-block {
|
||||
border-radius: var(--radius-md);
|
||||
border-color: var(--color-border);
|
||||
background-color: var(--color-muted);
|
||||
}
|
||||
.prose pre code, .code-block code {
|
||||
display: inline-block;
|
||||
padding: calc(var(--spacing) * 4);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
}
|
||||
.prose blockquote, .quote {
|
||||
border-left-style: var(--tw-border-style);
|
||||
border-left-width: 4px;
|
||||
border-color: var(--color-border);
|
||||
padding-left: calc(var(--spacing) * 4);
|
||||
}
|
||||
.prose ol, .ordered-list {
|
||||
margin-block: calc(var(--spacing) * 6);
|
||||
margin-left: calc(var(--spacing) * 6);
|
||||
list-style-type: decimal;
|
||||
li {
|
||||
margin-bottom: calc(var(--spacing) * 2);
|
||||
}
|
||||
}
|
||||
.prose ul, .unordered-list {
|
||||
margin-block: calc(var(--spacing) * 6);
|
||||
margin-left: calc(var(--spacing) * 6);
|
||||
list-style-type: disc;
|
||||
li {
|
||||
margin-bottom: calc(var(--spacing) * 2);
|
||||
}
|
||||
}
|
||||
.prose > *:first-child {
|
||||
margin-top: calc(var(--spacing) * 0) !important;
|
||||
}
|
||||
.prose > *:last-child {
|
||||
margin-bottom: calc(var(--spacing) * 0) !important;
|
||||
}
|
||||
.prose h1 {
|
||||
margin-bottom: calc(var(--spacing) * 8);
|
||||
}
|
||||
.prose h2 {
|
||||
margin-top: calc(var(--spacing) * 8);
|
||||
margin-bottom: calc(var(--spacing) * 6);
|
||||
}
|
||||
.prose h3 {
|
||||
margin-top: calc(var(--spacing) * 6);
|
||||
margin-bottom: calc(var(--spacing) * 4);
|
||||
}
|
||||
.prose h4 {
|
||||
margin-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.prose h5 {
|
||||
margin-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.prose h6 {
|
||||
margin-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.prose > p, .prose > blockquote, .prose > ul, .prose > ol, .prose > dl, .prose > table, .prose > pre, .prose > p {
|
||||
margin-block: calc(var(--spacing) * 4);
|
||||
}
|
||||
.prose > *:last-child {
|
||||
margin-bottom: calc(var(--spacing) * 0) !important;
|
||||
}
|
||||
.prose strong, .prose b {
|
||||
--tw-font-weight: var(--font-weight-semibold);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
}
|
||||
@property --tw-translate-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -3535,6 +4022,11 @@
|
||||
inherits: false;
|
||||
initial-value: 0 0 #0000;
|
||||
}
|
||||
@property --tw-outline-style {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@property --tw-blur {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -3616,11 +4108,6 @@
|
||||
inherits: false;
|
||||
initial-value: 1;
|
||||
}
|
||||
@property --tw-outline-style {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
@@ -3662,6 +4149,7 @@
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-outline-style: solid;
|
||||
--tw-blur: initial;
|
||||
--tw-brightness: initial;
|
||||
--tw-contrast: initial;
|
||||
@@ -3681,7 +4169,6 @@
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-scale-z: 1;
|
||||
--tw-outline-style: solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+175
-11
@@ -231,7 +231,21 @@
|
||||
.btn-icon-outline,
|
||||
.btn-icon-ghost,
|
||||
.btn-icon-link,
|
||||
.btn-icon-destructive {
|
||||
.btn-icon-destructive,
|
||||
.btn-sm-icon,
|
||||
.btn-sm-icon-primary,
|
||||
.btn-sm-icon-secondary,
|
||||
.btn-sm-icon-outline,
|
||||
.btn-sm-icon-ghost,
|
||||
.btn-sm-icon-link,
|
||||
.btn-sm-icon-destructive,
|
||||
.btn-lg-icon,
|
||||
.btn-lg-icon-primary,
|
||||
.btn-lg-icon-secondary,
|
||||
.btn-lg-icon-outline,
|
||||
.btn-lg-icon-ghost,
|
||||
.btn-lg-icon-link,
|
||||
.btn-lg-icon-destructive {
|
||||
@apply inline-flex items-center justify-center whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer rounded-md;
|
||||
}
|
||||
.btn,
|
||||
@@ -261,6 +275,15 @@
|
||||
.btn-sm-destructive {
|
||||
@apply gap-1.5 h-8 px-3 has-[>svg]:px-2.5;
|
||||
}
|
||||
.btn-sm-icon,
|
||||
.btn-sm-icon-primary,
|
||||
.btn-sm-icon-secondary,
|
||||
.btn-sm-icon-outline,
|
||||
.btn-sm-icon-ghost,
|
||||
.btn-sm-icon-link,
|
||||
.btn-sm-icon-destructive {
|
||||
@apply size-8;
|
||||
}
|
||||
.btn-lg,
|
||||
.btn-lg-primary,
|
||||
.btn-lg-secondary,
|
||||
@@ -270,6 +293,15 @@
|
||||
.btn-lg-destructive {
|
||||
@apply gap-2 h-10 px-6 has-[>svg]:px-4;
|
||||
}
|
||||
.btn-lg-icon,
|
||||
.btn-lg-icon-primary,
|
||||
.btn-lg-icon-secondary,
|
||||
.btn-lg-icon-outline,
|
||||
.btn-lg-icon-ghost,
|
||||
.btn-lg-icon-link,
|
||||
.btn-lg-icon-destructive {
|
||||
@apply size-10;
|
||||
}
|
||||
.btn,
|
||||
.btn-primary,
|
||||
.btn-sm,
|
||||
@@ -277,7 +309,11 @@
|
||||
.btn-lg,
|
||||
.btn-lg-primary,
|
||||
.btn-icon,
|
||||
.btn-icon-primary {
|
||||
.btn-icon-primary,
|
||||
.btn-sm-icon,
|
||||
.btn-sm-icon-primary,
|
||||
.btn-lg-icon,
|
||||
.btn-lg-icon-primary {
|
||||
@apply bg-primary text-primary-foreground shadow-xs hover:bg-primary/90;
|
||||
&[aria-pressed='true'] {
|
||||
@apply bg-primary/90;
|
||||
@@ -286,7 +322,9 @@
|
||||
.btn-secondary,
|
||||
.btn-sm-secondary,
|
||||
.btn-lg-secondary,
|
||||
.btn-icon-secondary {
|
||||
.btn-icon-secondary,
|
||||
.btn-sm-icon-secondary,
|
||||
.btn-lg-icon-secondary {
|
||||
@apply bg-secondary text-secondary-foreground shadow-xs;
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@@ -296,7 +334,9 @@
|
||||
.btn-outline,
|
||||
.btn-sm-outline,
|
||||
.btn-lg-outline,
|
||||
.btn-icon-outline {
|
||||
.btn-icon-outline,
|
||||
.btn-sm-icon-outline,
|
||||
.btn-lg-icon-outline {
|
||||
@apply border bg-background shadow-xs dark:bg-input/30 dark:border-input;
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@@ -306,7 +346,9 @@
|
||||
.btn-ghost,
|
||||
.btn-sm-ghost,
|
||||
.btn-lg-ghost,
|
||||
.btn-icon-ghost {
|
||||
.btn-icon-ghost,
|
||||
.btn-sm-icon-ghost,
|
||||
.btn-lg-icon-ghost {
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@apply bg-accent text-accent-foreground dark:bg-accent/50;
|
||||
@@ -315,7 +357,9 @@
|
||||
.btn-link,
|
||||
.btn-sm-link,
|
||||
.btn-lg-link,
|
||||
.btn-icon-link {
|
||||
.btn-icon-link,
|
||||
.btn-sm-icon-link,
|
||||
.btn-lg-icon-link {
|
||||
@apply text-primary underline-offset-4;
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@@ -325,7 +369,9 @@
|
||||
.btn-destructive,
|
||||
.btn-sm-destructive,
|
||||
.btn-lg-destructive,
|
||||
.btn-icon-destructive {
|
||||
.btn-icon-destructive,
|
||||
.btn-sm-icon-destructive,
|
||||
.btn-lg-icon-destructive {
|
||||
@apply bg-destructive text-white shadow-xs focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60;
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@@ -585,13 +631,13 @@
|
||||
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none w-full truncate;
|
||||
}
|
||||
[role='menuitem'] {
|
||||
@apply hover:bg-accent hover:text-accent-foreground;
|
||||
@apply focus-visible:bg-accent focus-visible:text-accent-foreground hover:bg-accent hover:text-accent-foreground;
|
||||
}
|
||||
[role='option'] {
|
||||
@apply data-[focus]:bg-accent data-[focus]:text-accent-foreground pr-7.5;
|
||||
|
||||
&[aria-selected='true'] {
|
||||
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
|
||||
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
|
||||
}
|
||||
}
|
||||
[role='menu'] [role='heading'] {
|
||||
@@ -744,7 +790,9 @@
|
||||
|
||||
/* Tabs */
|
||||
@layer components {
|
||||
.tabs {
|
||||
.tabs {
|
||||
@apply flex flex-col gap-2;
|
||||
|
||||
[role='tablist'] {
|
||||
@apply bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px];
|
||||
|
||||
@@ -821,7 +869,7 @@
|
||||
}
|
||||
&:not([data-side]),
|
||||
&[data-side='top'] {
|
||||
@apply before:bottom-full before:mb-1.5 before:translate-y-2 hover:before:translate-y-0 ;
|
||||
@apply before:bottom-full before:mb-1.5 before:translate-y-2 hover:before:translate-y-0 ;
|
||||
}
|
||||
&[data-side='bottom'] {
|
||||
@apply before:top-full before:mt-1.5 before:-translate-y-2 hover:before:translate-y-0 ;
|
||||
@@ -860,4 +908,120 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Prose */
|
||||
@layer components {
|
||||
.prose h1,
|
||||
.prose h2,
|
||||
.prose h3,
|
||||
.prose h4,
|
||||
.prose h5,
|
||||
.prose h6,
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.h5,
|
||||
.h6 {
|
||||
@apply font-medium leading-tight;
|
||||
}
|
||||
.prose h1,
|
||||
.h1 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
.prose h2,
|
||||
.h2 {
|
||||
@apply text-xl;
|
||||
}
|
||||
.prose h3,
|
||||
.h3 {
|
||||
@apply text-lg;
|
||||
}
|
||||
.prose h4,
|
||||
.prose h5,
|
||||
.prose h6,
|
||||
.h4,
|
||||
.h5,
|
||||
.h6 {
|
||||
@apply text-base;
|
||||
}
|
||||
.prose a,
|
||||
.link {
|
||||
@apply font-medium underline underline-offset-4;
|
||||
}
|
||||
.prose code,
|
||||
.code {
|
||||
@apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm;
|
||||
}
|
||||
.prose pre:has(> code),
|
||||
.code-block {
|
||||
@apply bg-muted border-border rounded-md;
|
||||
}
|
||||
.prose pre code,
|
||||
.code-block code {
|
||||
@apply inline-block p-4 text-sm font-mono;
|
||||
}
|
||||
.prose blockquote,
|
||||
.quote {
|
||||
@apply border-l-4 border-border pl-4;
|
||||
}
|
||||
.prose ol,
|
||||
.ordered-list {
|
||||
@apply my-6 ml-6 list-decimal;
|
||||
|
||||
li {
|
||||
@apply mb-2;
|
||||
}
|
||||
}
|
||||
.prose ul,
|
||||
.unordered-list {
|
||||
@apply my-6 ml-6 list-disc;
|
||||
|
||||
li {
|
||||
@apply mb-2;
|
||||
}
|
||||
}
|
||||
.prose > *:first-child {
|
||||
@apply !mt-0;
|
||||
}
|
||||
.prose > *:last-child {
|
||||
@apply !mb-0;
|
||||
}
|
||||
.prose h1 {
|
||||
@apply mb-8;
|
||||
}
|
||||
.prose h2 {
|
||||
@apply mt-8 mb-6;
|
||||
}
|
||||
.prose h3 {
|
||||
@apply mt-6 mb-4;
|
||||
}
|
||||
.prose h4 {
|
||||
@apply my-4;
|
||||
}
|
||||
.prose h5 {
|
||||
@apply my-4;
|
||||
}
|
||||
.prose h6 {
|
||||
@apply my-4;
|
||||
}
|
||||
.prose > p,
|
||||
.prose > blockquote,
|
||||
.prose > ul,
|
||||
.prose > ol,
|
||||
.prose > dl,
|
||||
.prose > table,
|
||||
.prose > pre,
|
||||
.prose > p {
|
||||
@apply my-4;
|
||||
}
|
||||
.prose > *:last-child {
|
||||
@apply !mb-0;
|
||||
}
|
||||
|
||||
.prose strong,
|
||||
.prose b {
|
||||
@apply font-semibold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "Basecoat",
|
||||
"description": "Minimal CSS UI kit for vanilla HTML."
|
||||
}
|
||||
@@ -27,21 +27,21 @@
|
||||
this.open = initialOpen;
|
||||
this.closeOnOverlayClick = initialCloseOnOverlayClick;
|
||||
},
|
||||
|
||||
show() {
|
||||
if (!this.open) {
|
||||
this.open = true;
|
||||
this.$nextTick(() => this.$el.dispatchEvent(new CustomEvent('dialog:opened', { bubbles: true })));
|
||||
this.$nextTick(() => {
|
||||
this.$el.dispatchEvent(new CustomEvent('dialog:opened', { bubbles: true }));
|
||||
setTimeout(() => this.$refs.dialog.focus(), 50);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (this.open) {
|
||||
this.open = false;
|
||||
this.$el.dispatchEvent(new CustomEvent('dialog:closed', { bubbles: true }));
|
||||
}
|
||||
},
|
||||
|
||||
handleControl(event) {
|
||||
const detail = event.detail;
|
||||
if (detail && detail.targetId === this.id) {
|
||||
@@ -52,9 +52,7 @@
|
||||
},
|
||||
|
||||
$main: {
|
||||
'@dialog:control.window'(e) {
|
||||
this.handleControl(e)
|
||||
},
|
||||
'@dialog:control.window'(e) { this.handleControl(e) },
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
@@ -63,9 +61,9 @@
|
||||
},
|
||||
|
||||
$content: {
|
||||
'@keydown.escape'() { this.open && this.hide() },
|
||||
':inert'() { return !this.open },
|
||||
'@click'(e) { e.target === this.$el && this.closeOnOverlayClick && this.hide() },
|
||||
'@keydown.escape.prevent'() { this.open && this.hide() },
|
||||
'x-cloak': ''
|
||||
}
|
||||
}));
|
||||
@@ -97,6 +95,8 @@
|
||||
<div
|
||||
role="dialog"
|
||||
id="{{ id }}-dialog"
|
||||
x-ref="dialog"
|
||||
tabindex="-1"
|
||||
aria-modal="true"
|
||||
aria-labelledby="{{ id }}-title"
|
||||
inert
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
{% macro dropdown_menu(
|
||||
id=None,
|
||||
trigger=None,
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
content_attrs=None
|
||||
) %}
|
||||
{% if registerComponent('dropdown-menu') %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('dropdownMenu', () => ({
|
||||
open: false,
|
||||
focusedIndex: null,
|
||||
menuItems: [],
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.menuItems = Array.from(this.$el.querySelectorAll('[role=menuitem]:not([disabled])'));
|
||||
});
|
||||
},
|
||||
focusMenuitem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
|
||||
if (this.focusedIndex >= this.menuItems.length) {
|
||||
this.focusedIndex = this.menuItems.length - 1;
|
||||
} else if (this.focusedIndex < 0 || this.focusedIndex === null) {
|
||||
this.focusedIndex = 0;
|
||||
}
|
||||
this.menuItems.forEach(item => item.blur());
|
||||
this.menuItems[this.focusedIndex].focus();
|
||||
},
|
||||
moveMenuitemFocus(delta) {
|
||||
if (this.menuItems.length === 0) return;
|
||||
|
||||
let wasOpen = this.open;
|
||||
if (!this.open) {
|
||||
this.open = true;
|
||||
this.focusedIndex = 0;
|
||||
} else {
|
||||
this.focusedIndex = this.focusedIndex === null
|
||||
? 0
|
||||
: this.focusedIndex + delta;
|
||||
}
|
||||
|
||||
if (wasOpen) {
|
||||
this.focusMenuitem();
|
||||
} else {
|
||||
setTimeout(() => this.focusMenuitem(), 50);
|
||||
}
|
||||
},
|
||||
handleMenuitemClick(event) {
|
||||
const menuitem = event.target.closest('[role=menuitem]');
|
||||
if (menuitem && menuitem.getAttribute('aria-disabled') !== 'true' && !menuitem.disabled) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.trigger.focus();
|
||||
this.open = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMenuitemMousemove(event) {
|
||||
const menuitem = event.target.closest('[role=menuitem]');
|
||||
if (menuitem && menuitem.getAttribute('aria-disabled') !== 'true' && !menuitem.disabled) {
|
||||
this.focusedIndex = this.menuItems.indexOf(menuitem);
|
||||
this.focusMenuitem();
|
||||
}
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.open = !this.open },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
'@keydown.down.prevent'() { this.moveMenuitemFocus(+1); },
|
||||
'@keydown.up.prevent'() { this.moveMenuitemFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusMenuitem(0) },
|
||||
'@keydown.end.prevent'() { this.focusMenuitem(this.menuItems.length - 1) },
|
||||
'@keydown.enter.prevent'() { this.open = !this.open },
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
$content: {
|
||||
'@click'(e) { this.handleMenuitemClick(e) },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
'@keydown.down.prevent'() { this.moveMenuitemFocus(+1); },
|
||||
'@keydown.up.prevent'() { this.moveMenuitemFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusMenuitem(0) },
|
||||
'@keydown.end.prevent'() { this.focusMenuitem(this.menuItems.length - 1) },
|
||||
'@mousemove'(e) { this.handleMenuitemMousemove(e) },
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<div
|
||||
class="popover {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
x-data="dropdownMenu"
|
||||
@click.away="open = false"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if trigger %}
|
||||
<button
|
||||
type="button"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
x-bind="$trigger"
|
||||
{% if id %}
|
||||
id="{{ id }}-trigger"
|
||||
aria-controls="{{ id }}-menu"
|
||||
{% endif %}
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
{% endif %}
|
||||
<div
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
x-bind="$content"
|
||||
{% if id %}id="{{ id }}-menu"{% endif %}
|
||||
{% for key, value in content_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<nav role="menu">
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -10,76 +10,22 @@
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('popover', () => ({
|
||||
open: false,
|
||||
focusedIndex: null,
|
||||
menuItems: [],
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.menuItems = Array.from(this.$el.querySelectorAll('[role=menuitem]'));
|
||||
});
|
||||
},
|
||||
|
||||
handleTriggerClick() {
|
||||
this.open = !this.open;
|
||||
},
|
||||
|
||||
handleEscape() {
|
||||
if (this.open) this.open = false;
|
||||
},
|
||||
|
||||
focusNavItem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
if (this.focusedIndex >= this.menuItems.length) {
|
||||
this.focusedIndex = this.menuItems.length - 1;
|
||||
}
|
||||
if (this.focusedIndex < 0) {
|
||||
this.focusedIndex = 0;
|
||||
}
|
||||
this.menuItems[this.focusedIndex].focus();
|
||||
},
|
||||
|
||||
nextNavItem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
if (this.focusedIndex === null) {
|
||||
this.focusedIndex = 0;
|
||||
} else {
|
||||
this.focusedIndex++;
|
||||
}
|
||||
this.focusNavItem();
|
||||
},
|
||||
|
||||
prevNavItem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
if (this.focusedIndex === null) {
|
||||
this.focusedIndex = 0;
|
||||
} else {
|
||||
this.focusedIndex--;
|
||||
}
|
||||
this.focusNavItem();
|
||||
},
|
||||
|
||||
handleNavItemClick(event) {
|
||||
if (this.menuItems.length === 0) return;
|
||||
const menuItem = event.target.closest('[role=menuitem]');
|
||||
if (menuItem && !menuItem.disabled) {
|
||||
menuItem.blur();
|
||||
this.open = false;
|
||||
}
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.handleTriggerClick() },
|
||||
'@keydown.down.prevent'() { this.open && this.nextNavItem() },
|
||||
'@click'() { this.open = !this.open },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
|
||||
$content: {
|
||||
'@click'(e) { this.handleNavItemClick(e) },
|
||||
'@keydown.escape.prevent'() { this.handleEscape() },
|
||||
'@keydown.down.prevent'() { this.nextNavItem() },
|
||||
'@keydown.up.prevent'() { this.prevNavItem() },
|
||||
'@keydown.home.prevent'() { this.focusNavItem(0) },
|
||||
'@keydown.end.prevent'() { this.focusNavItem(this.menuItems.length - 1) },
|
||||
'@click'(e) { this.handleMenuitemClick(e) },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
|
||||
@@ -46,7 +46,9 @@
|
||||
this.focusedIndex = 0;
|
||||
}
|
||||
this.options.forEach(opt => opt.removeAttribute('data-focus'));
|
||||
this.options[this.focusedIndex].setAttribute('data-focus', '');
|
||||
const option = this.options[this.focusedIndex];
|
||||
option.setAttribute('data-focus', '');
|
||||
option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||
},
|
||||
focusOnSelectedOption() {
|
||||
if (this.options.length === 0 || this.selectedValue === null) return;
|
||||
@@ -77,7 +79,6 @@
|
||||
}
|
||||
},
|
||||
handleOptionMousemove(event) {
|
||||
console.log("here");
|
||||
const option = event.target.closest('[role=option]');
|
||||
if (option && option.getAttribute('aria-disabled') !== 'true') {
|
||||
this.focusedIndex = this.options.indexOf(option);
|
||||
@@ -126,18 +127,19 @@
|
||||
'@keydown.up.prevent'() { this.moveOptionFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusOption(0) },
|
||||
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
|
||||
'@keydown.enter.prevent'() { this.handleOptionEnter() },
|
||||
'@keydown.enter.prevent'() {
|
||||
if (this.open) this.handleOptionEnter();
|
||||
else this.open = true;
|
||||
},
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
|
||||
$content: {
|
||||
'@click'(e) { this.handleOptionClick(e) },
|
||||
'@mousemove'(e) { this.handleOptionMousemove(e) },
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
|
||||
$filter: {
|
||||
'@input'(e) { this.filterOptions(e.target.value) },
|
||||
'@keydown.escape.prevent'() {
|
||||
@@ -180,7 +182,11 @@
|
||||
>
|
||||
<div x-html="selectedLabel" class="flex items-center gap-x-2"
|
||||
></div>
|
||||
{% lucide "chevron-down", class="text-muted-foreground opacity-50 shrink-0" %}
|
||||
{% if is_combobox %}
|
||||
{% lucide "chevrons-up-down", class="text-muted-foreground opacity-50 shrink-0" %}
|
||||
{% else %}
|
||||
{% lucide "chevron-down", class="text-muted-foreground opacity-50 shrink-0" %}
|
||||
{% endif %}
|
||||
</button>
|
||||
<div
|
||||
data-popover
|
||||
|
||||
@@ -1,75 +1,73 @@
|
||||
{% macro tabs(
|
||||
tabs,
|
||||
id,
|
||||
tabsets=[],
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
content_attrs=None
|
||||
tablist_attrs=None,
|
||||
default_tab_index=1
|
||||
)
|
||||
%}
|
||||
{% if registerComponent('toast') %}
|
||||
{% if registerComponent('tabs') %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('toast', () => ({
|
||||
open: false,
|
||||
focusedIndex: -1,
|
||||
menuItems: [],
|
||||
document.addEventListener('alpine:init', (initialTabIndex = 0) => {
|
||||
Alpine.data('tabs', () => ({
|
||||
activeTabIndex: initialTabIndex,
|
||||
tabs: [],
|
||||
panels: [],
|
||||
|
||||
init() {
|
||||
console.log("initialTabIndex", initialTabIndex);
|
||||
this.$nextTick(() => {
|
||||
const menuElement = this.$el.querySelector('[role="menu"]');
|
||||
this.menuItems = Array.from(menuElement.querySelectorAll('[role=menuitem]'));
|
||||
this.tabs = Array.from(this.$el.querySelectorAll('[role=tab]:not([disabled])'));
|
||||
this.panels = Array.from(this.$el.querySelectorAll('[role=tabpanel]'));
|
||||
|
||||
if (this.tabs.length > 0) {
|
||||
this.selectTab(this.tabs[initialTabIndex], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.handleClick() },
|
||||
'@keydown.down.prevent'() { this.open && this.nextItem() },
|
||||
':aria-expanded'() { return this.open },
|
||||
':aria-pressed'() { return this.open }
|
||||
nextTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex + 1) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
|
||||
$menu: {
|
||||
'@click'(e) { this.handleMenuItemClick(e) },
|
||||
'@keydown.escape.prevent'() { this.handleEscape() },
|
||||
'@keydown.down.prevent'() { this.nextItem() },
|
||||
'@keydown.up.prevent'() { this.prevItem() },
|
||||
'@keydown.home.prevent'() { this.focusItem(0) },
|
||||
'@keydown.end.prevent'() { this.focusItem(this.menuItems.length - 1) },
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
prevTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex - 1 + this.tabs.length) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
|
||||
focusItem(index) {
|
||||
if (index >= 0 && index < this.menuItems.length) {
|
||||
this.focusedIndex = index;
|
||||
this.menuItems[index].focus();
|
||||
}
|
||||
},
|
||||
|
||||
nextItem() {
|
||||
this.focusItem((this.focusedIndex + 1) % this.menuItems.length);
|
||||
},
|
||||
|
||||
prevItem() {
|
||||
this.focusItem(this.focusedIndex <= 0 ? this.menuItems.length - 1 : this.focusedIndex - 1);
|
||||
},
|
||||
|
||||
handleClick() {
|
||||
this.open = !this.open;
|
||||
this.$nextTick(() => {
|
||||
if(this.open && this.menuItems.length) this.focusItem(0);
|
||||
selectTab(tab, focus = true) {
|
||||
if (!tab || this.tabs.length === 0) return;
|
||||
|
||||
this.tabs.forEach((t, index) => {
|
||||
const isSelected = t === tab;
|
||||
t.setAttribute('aria-selected', isSelected);
|
||||
t.setAttribute('tabindex', isSelected ? '0' : '-1');
|
||||
if (isSelected) {
|
||||
this.activeTabIndex = index;
|
||||
this.activeTab = t;
|
||||
if (focus) {
|
||||
t.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const panelId = tab.getAttribute('aria-controls');
|
||||
if (!panelId) return;
|
||||
|
||||
this.panels.forEach(panel => {
|
||||
panel.hidden = (panel.getAttribute('id') !== panelId);
|
||||
});
|
||||
},
|
||||
|
||||
handleEscape() {
|
||||
if (this.open) this.open = false;
|
||||
},
|
||||
|
||||
handleMenuItemClick(event) {
|
||||
const menuItem = event.target.closest('[role=menuitem]');
|
||||
if (menuItem && !menuItem.disabled) {
|
||||
menuItem.blur();
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
$tablist: {
|
||||
['@click'](event) {
|
||||
const clickedTab = event.target.closest('[role=tab]');
|
||||
if (clickedTab) {
|
||||
this.selectTab(clickedTab);
|
||||
}
|
||||
},
|
||||
['@keydown.arrow-right.prevent']() { this.nextTab() },
|
||||
['@keydown.arrow-left.prevent']() { this.prevTab() },
|
||||
},
|
||||
}));
|
||||
});
|
||||
@@ -77,44 +75,53 @@
|
||||
{% endif %}
|
||||
|
||||
<div
|
||||
class="toast {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
x-data="toast"
|
||||
@click.away="open = false"
|
||||
class="tabs {{ main_attrs.class }}"
|
||||
x-data="tabs({{ default_tab_index }})"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% if trigger %}
|
||||
<button
|
||||
type="button"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
aria-pressed="false"
|
||||
x-bind="$trigger"
|
||||
{% if id %}
|
||||
id="{{ id }}-trigger"
|
||||
aria-controls="{{ id }}-menu"
|
||||
{% endif %}
|
||||
{% for key, value in trigger_attrs %}
|
||||
<div
|
||||
role="tablist"
|
||||
aria-orientation="horizontal"
|
||||
x-bind="$tablist"
|
||||
{% for key, value in tablist_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
{% endif %}
|
||||
<nav
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-hidden="true"
|
||||
x-bind="$menu"
|
||||
{% if id %}id="{{ id }}-menu"{% endif %}
|
||||
{% for key, value in menu_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% for tabset in tabsets %}
|
||||
<button
|
||||
role="tab"
|
||||
id="{{ tabset.id }}-tab-{{ loop.index }}"
|
||||
aria-controls="{{ tabset.id }}-panel-{{ loop.index }}"
|
||||
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
|
||||
tabindex="0"
|
||||
{% for key, value in tabset.tab_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ tabset.tab | safe }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
>
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
</div>
|
||||
|
||||
{% for tabset in tabsets %}
|
||||
{% if tabset.panel %}
|
||||
<div
|
||||
role="tabpanel"
|
||||
id="{{ tabset.id }}-panel-{{ loop.index }}"
|
||||
aria-labelledby="{{ tabset.id }}-tab-{{ loop.index }}"
|
||||
tabindex="-1"
|
||||
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
|
||||
{% if loop.index != default_tab_index %}hidden{% endif %}
|
||||
{% for key, value in tabset.panel_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ tabset.panel | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="dark"><head>
|
||||
<!-- Theme -->
|
||||
<script>
|
||||
function isDark() {
|
||||
return localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
}
|
||||
|
||||
if (isDark()) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('theme', {
|
||||
isDark: isDark(),
|
||||
set(value) {
|
||||
this.isDark = value;
|
||||
localStorage.setItem('theme', value ? 'dark' : 'light');
|
||||
},
|
||||
toggle() {
|
||||
this.isDark = document.documentElement.classList.toggle('dark');
|
||||
localStorage.setItem('theme', this.isDark ? 'dark' : 'light');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Language" content="en">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Title & Description -->
|
||||
<title>{{ site.title + ": " + title if page.url === "/" else title + " - " + site.title }}</title>
|
||||
<meta name="description" content="{{ description or site.description }}"/>
|
||||
<meta name="title" content="{{ title }} | Basecoat">
|
||||
<meta name="description" content="{{ description }}">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="preconnect" href="https://rsms.me/">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
|
||||
<!-- JS -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="sidebar"
|
||||
class="fixed inset-y-0 left-0 z-50 h-svh border-r bg-sidebar text-sidebar-foreground flex flex-col w-[var(--sidebar-width)] transition-transform ease-in-out peer aria-[hidden=true]:-translate-x-full"
|
||||
aria-hidden="false"
|
||||
:aria-hidden="open ? 'false' : 'true'"
|
||||
@sidebar:toggle.window="toggle()"
|
||||
x-data="{
|
||||
open: true,
|
||||
toggle() { this.open = !this.open }
|
||||
}"
|
||||
>
|
||||
{% include "partials/sidebar.njk" %}
|
||||
</div>
|
||||
|
||||
<main id="content" class="relative ml-(--sidebar-width) peer-aria-hidden:ml-0 transition-[margin-left] ease-in-out">
|
||||
<header class="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
||||
<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-icon-ghost size-8 mr-auto"
|
||||
x-init
|
||||
x-data="{ sidebarOpen: true }"
|
||||
@click="
|
||||
sidebarOpen = !sidebarOpen;
|
||||
$dispatch('sidebar:toggle');
|
||||
"
|
||||
>
|
||||
{% lucide "panel-left" %}
|
||||
</button>
|
||||
<select class="select h-8 leading-none">
|
||||
<option value="light">Default</option>
|
||||
<option value="light">Other</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle theme"
|
||||
data-tooltip="Toggle theme"
|
||||
data-side="bottom"
|
||||
data-align="end"
|
||||
class="btn-icon-outline size-8"
|
||||
x-init
|
||||
@click="$store.theme.toggle()"
|
||||
>
|
||||
<span x-show="$store.theme.isDark">{% lucide "sun" %}</span>
|
||||
<span x-show="!$store.theme.isDark">{% lucide "moon" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container grid gap-4 p-4">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</main>
|
||||
{% from "components/toast.njk" import toaster %}
|
||||
{{ toaster() }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
---
|
||||
|
||||
<main class="mx-auto relative py-6 lg:gap-10 lg:py-8 flex w-full max-w-screen-lg">
|
||||
<div class="mx-auto w-full min-w-0 max-w-screen-md">
|
||||
<header class="space-y-2">
|
||||
<h1 class="text-3xl font-semibold tracking-tight">{{ title }}</h1>
|
||||
<p class="text-muted-foreground">{{ description }}</p>
|
||||
</header>
|
||||
<article class="pb-12 pt-8 [&>h2]:mt-16 [&>h2]:scroll-m-22 [&>h2]:border-b [&>h2]:pb-4 [&>h2]:text-xl [&>h2]:font-semibold [&>h2]:tracking-tight [&>h2]:first:mt-0 [&>h3]:font-heading [&>h3]:mt-8 [&>h3]:scroll-m-22 [&>h3]:text-lg [&>h3]:font-semibold [&>h3]:tracking-tight content">
|
||||
{{ content | safe }}
|
||||
</article>
|
||||
</div>
|
||||
<div class="hidden text-sm xl:block w-full max-w-[300px]">
|
||||
<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>
|
||||
{% if toc %}
|
||||
{% macro renderToc(items) %}
|
||||
{% if items and items.length %}
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li>
|
||||
<a href="#{{ item.id }}">{{ item.label }}</a>
|
||||
{# Recursively call the macro if there are children #}
|
||||
{% if item.children and item.children.length %}
|
||||
{{ renderToc(item.children) }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{{ renderToc(toc) }}
|
||||
{% else %}
|
||||
<p class="text-muted-foreground text-center bg-muted rounded-md p-2">No table of contents.</p>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
<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/html.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/django.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
@@ -0,0 +1,29 @@
|
||||
{% macro code_block(code, language="html") %}
|
||||
<div
|
||||
class="relative"
|
||||
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 %}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{% macro code_preview(id, code, class=None, default_code=false) %}
|
||||
{% from "./code_block.njk" import code_block %}
|
||||
{% set code_panel = code_block(code) %}
|
||||
{% 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 class="{{ class }}">
|
||||
{{ code | safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
<div class="relative my-4">
|
||||
{% from "components/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)
|
||||
) }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,150 +0,0 @@
|
||||
<section id="dropdown-menu" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Dropdown Menu</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% call popover(
|
||||
id="dropdown-menu",
|
||||
trigger="Open Menu",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</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>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
|
||||
{% set profile_menu %}
|
||||
<nav role="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">
|
||||
<div role="group" aria-labelledby="theme-options">
|
||||
<span id="theme-options" role="heading">Theme</span>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "check", { "class": "text-foreground" } %}
|
||||
Light
|
||||
</button>
|
||||
<button type="button" role="menuitem" >
|
||||
{% lucide "check", { "class": "text-transparent" } %}
|
||||
Dark
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "check", { "class": "text-transparent" } %}
|
||||
System
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</button>
|
||||
</nav>
|
||||
{% endset %}
|
||||
|
||||
{% 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 popover(
|
||||
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 popover(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost rounded-full size-8"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost"},
|
||||
content_attrs={"class": "min-w-32"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<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>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
{% from "components/dialog.njk" import dialog %}
|
||||
|
||||
<section id="alert-dialog" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Alert Dialog</h2>
|
||||
@@ -59,15 +59,6 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{# <div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
<button type="button" class="btn-sm-outline">
|
||||
Undo
|
||||
</button>
|
||||
</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>
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
{% from "components/popover.njk" import popover %}
|
||||
|
||||
<section id="breadcrumb" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Breadcrumb</h2>
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
{% from "components/select.njk" import select %}
|
||||
|
||||
<section id="combobox" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Combobox</h2>
|
||||
@@ -1,3 +1,5 @@
|
||||
{% from "components/dialog.njk" import dialog %}
|
||||
|
||||
<section id="dialog" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Dialog</h2>
|
||||
@@ -0,0 +1,146 @@
|
||||
{% from "components/dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
<section id="dropdown-menu" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Dropdown Menu</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
trigger="Open Menu",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</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 %}
|
||||
|
||||
{% 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">
|
||||
<div role="group" aria-labelledby="theme-options">
|
||||
<span id="theme-options" role="heading">Theme</span>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "check", { "class": "text-foreground" } %}
|
||||
Light
|
||||
</button>
|
||||
<button type="button" role="menuitem" >
|
||||
{% lucide "check", { "class": "text-transparent" } %}
|
||||
Dark
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "check", { "class": "text-transparent" } %}
|
||||
System
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</button>
|
||||
{% endset %}
|
||||
|
||||
{% 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"}
|
||||
) %}
|
||||
{{ 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,3 +1,5 @@
|
||||
{% from "components/popover.njk" import popover %}
|
||||
|
||||
<section id="popover" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Popover</h2>
|
||||
+3
-3
@@ -5,9 +5,9 @@
|
||||
<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="yes" class="input">Yes</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="no" class="input">No</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="maybe" class="input">Maybe</label>
|
||||
<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">
|
||||
@@ -1,3 +1,5 @@
|
||||
{% from "components/select.njk" import select %}
|
||||
|
||||
<section id="select" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Select</h2>
|
||||
@@ -0,0 +1,102 @@
|
||||
{% from "components/tabs.njk" import tabs %}
|
||||
|
||||
<section id="tabs" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Tabs</h2>
|
||||
</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>
|
||||
@@ -0,0 +1,149 @@
|
||||
{% set components = [
|
||||
{ "id": "accordion", "name": "Accordion" },
|
||||
{ "id": "alert", "name": "Alert" },
|
||||
{ "id": "alert-dialog", "name": "Alert Dialog" },
|
||||
{ "id": "avatar", "name": "Avatar" },
|
||||
{ "id": "badge", "name": "Badge" },
|
||||
{ "id": "breadcrumb", "name": "Breadcrumb" },
|
||||
{ "id": "button", "name": "Button" },
|
||||
{ "id": "card", "name": "Card" },
|
||||
{ "id": "checkbox", "name": "Checkbox" },
|
||||
{ "id": "combobox", "name": "Combobox" },
|
||||
{ "id": "dialog", "name": "Dialog" },
|
||||
{ "id": "dropdown-menu", "name": "Dropdown Menu" },
|
||||
{ "id": "form", "name": "Form" },
|
||||
{ "id": "input", "name": "Input" },
|
||||
{ "id": "label", "name": "Label" },
|
||||
{ "id": "pagination", "name": "Pagination" },
|
||||
{ "id": "popover", "name": "Popover" },
|
||||
{ "id": "radio-group", "name": "Radio Group" },
|
||||
{ "id": "select", "name": "Select" },
|
||||
{ "id": "skeleton", "name": "Skeleton" },
|
||||
{ "id": "slider", "name": "Slider" },
|
||||
{ "id": "switch", "name": "Switch" },
|
||||
{ "id": "table", "name": "Table" },
|
||||
{ "id": "tabs", "name": "Tabs" },
|
||||
{ "id": "textarea", "name": "Textarea" },
|
||||
{ "id": "toast", "name": "Toast" },
|
||||
{ "id": "tooltip", "name": "Tooltip" }
|
||||
] %}
|
||||
|
||||
<header class="flex flex-col gap-2 p-2">
|
||||
<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">
|
||||
{% lucide "paint-roller", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Basecoat</span>
|
||||
<span class="truncate text-xs">v0.0.1</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>
|
||||
</header>
|
||||
<section class="flex flex-1 flex-col gap-2 overflow-y-auto scrollbar">
|
||||
<nav aria-label="Pages navigation" class="flex flex-col p-2">
|
||||
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Getting Started</h2>
|
||||
<ul class="flex flex-col gap-1">
|
||||
<li>
|
||||
<a
|
||||
href="/"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
Introduction
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/installation"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
Installation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/kitchen-sink"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
Kitchen sink
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/theming"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
Theming
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/hunvreus/basecoat"
|
||||
target="_blank"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
GitHub
|
||||
{% lucide "arrow-up-right", { "class": "text-muted-foreground size-3.5 ml-auto" } %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav aria-label="Components navigation" class="flex flex-col p-2">
|
||||
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Components</h2>
|
||||
<ul class="flex flex-col gap-1">
|
||||
{% for component in components %}
|
||||
<li>
|
||||
<a
|
||||
href="/components/{{ component.id }}"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
|
||||
>
|
||||
{{ component.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
<footer class="flex flex-col gap-2 p-2">
|
||||
{% set profile_menu %}
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img src="https://avatar.vercel.sh/ronan_berder" 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">
|
||||
<nav role="menu">
|
||||
<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>
|
||||
</nav>
|
||||
{% endset %}
|
||||
</footer>
|
||||
@@ -1,167 +0,0 @@
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('tabs', () => ({
|
||||
activeTabIndex: 0,
|
||||
tabs: [],
|
||||
panels: [],
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.tabs = Array.from(this.$el.querySelectorAll('[role=tab]:not([disabled])'));
|
||||
this.panels = Array.from(this.$el.querySelectorAll('[role=tabpanel]'));
|
||||
|
||||
if (this.tabs.length > 0) {
|
||||
this.selectTab(this.tabs[0], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
nextTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex + 1) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
prevTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex - 1 + this.tabs.length) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
selectTab(tab, focus = true) {
|
||||
if (!tab || this.tabs.length === 0) return;
|
||||
|
||||
this.tabs.forEach((t, index) => {
|
||||
const isSelected = t === tab;
|
||||
t.setAttribute('aria-selected', isSelected);
|
||||
t.setAttribute('tabindex', isSelected ? '0' : '-1');
|
||||
if (isSelected) {
|
||||
this.activeTabIndex = index;
|
||||
this.activeTab = t;
|
||||
if (focus) {
|
||||
t.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const panelId = tab.getAttribute('aria-controls');
|
||||
if (!panelId) return;
|
||||
|
||||
this.panels.forEach(panel => {
|
||||
panel.hidden = (panel.getAttribute('id') !== panelId);
|
||||
});
|
||||
},
|
||||
|
||||
$tablist: {
|
||||
['@click'](event) {
|
||||
const clickedTab = event.target.closest('[role=tab]');
|
||||
if (clickedTab) {
|
||||
this.selectTab(clickedTab);
|
||||
}
|
||||
},
|
||||
['@keydown.arrow-right.prevent']() { this.nextTab() },
|
||||
['@keydown.arrow-left.prevent']() { this.prevTab() },
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<section id="tabs" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Tabs</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="tabs flex flex-col gap-2 max-w-[300px]" x-data="tabs">
|
||||
<div role="tablist" aria-orientation="horizontal" aria-label="Feature Sections" class="w-full" x-bind="$tablist">
|
||||
<button role="tab" id="demo-tab-account" aria-controls="demo-panel-account" aria-selected="true" tabindex="0">
|
||||
Account
|
||||
</button>
|
||||
<button role="tab" id="demo-tab-password" aria-controls="demo-panel-password" aria-selected="false" tabindex="-1">
|
||||
Password
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" id="demo-panel-account" aria-labelledby="demo-tab-account" tabindex="-1">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" id="demo-panel-password" aria-labelledby="demo-tab-password" tabindex="-1" hidden>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" x-data="tabs">
|
||||
<div role="tablist" aria-orientation="horizontal" aria-label="Feature Sections" x-bind="$tablist">
|
||||
<button role="tab" aria-selected="true" tabindex="0">
|
||||
Home
|
||||
</button>
|
||||
<button role="tab" aria-selected="false" tabindex="-1">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" x-data="tabs">
|
||||
<div role="tablist" aria-orientation="horizontal" aria-label="Feature Sections" x-bind="$tablist">
|
||||
<button role="tab" aria-selected="true" tabindex="0">
|
||||
Home
|
||||
</button>
|
||||
<button role="tab" aria-selected="false" tabindex="-1" disabled>
|
||||
Disabled
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" x-data="tabs">
|
||||
<div role="tablist" aria-orientation="horizontal" aria-label="Feature Sections" x-bind="$tablist">
|
||||
<button role="tab" aria-selected="true" tabindex="0">
|
||||
{% lucide 'app-window' %}
|
||||
Preview
|
||||
</button>
|
||||
<button role="tab" aria-selected="false" tabindex="-1">
|
||||
{% lucide 'code' %}
|
||||
Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,193 +0,0 @@
|
||||
<header class="flex flex-col gap-2 p-2">
|
||||
{% set trigger %}
|
||||
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
{% lucide "gallery-vertical-end", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Acme Inc</span>
|
||||
<span class="truncate text-xs">Enterprise</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down", { "class": "ml-auto" } %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
id="demo-sidebar-dropdown-org",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-ghost p-2 h-12 w-full justify-start"},
|
||||
content_attrs={"data-side": "right"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<div role="group" aria-labelledby="teams-label">
|
||||
<span id="teams-label" role="heading" class="text-xs text-muted-foreground">Teams</span>
|
||||
<button type="button" role="menuitem" class="p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
{% lucide "gallery-vertical-end", { "class": "size-3.5 shrink-0" } %}
|
||||
</div>
|
||||
Acme Inc
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘1</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem" class="p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
{% lucide "audio-waveform", { "class": "size-3.5 shrink-0" } %}
|
||||
</div>
|
||||
Acme Corp
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘2</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem" class="p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
{% lucide "command", { "class": "size-3.5 shrink-0" } %}
|
||||
</div>
|
||||
Evil Corp
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘3</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem" class="p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
{% lucide "plus", { "class": "size-3.5 shrink-0" } %}
|
||||
</div>
|
||||
<span class="text-muted-foreground font-medium">Add team</span>
|
||||
</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
|
||||
<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>
|
||||
</header>
|
||||
<section class="flex flex-1 flex-col gap-2 overflow-y-auto scrollbar">
|
||||
<nav aria-label="Pages navigation" class="flex flex-col p-2">
|
||||
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Pages</h2>
|
||||
<ul class="flex flex-col gap-1">
|
||||
<li>
|
||||
<a
|
||||
href="#alert-dialog"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
{% lucide "house" %}
|
||||
Homepage
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#alert-dialog"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
{% lucide "book-open" %}
|
||||
Docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#alert-dialog"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
{% lucide "github" %}
|
||||
GitHub
|
||||
{% lucide "arrow-up-right", { "class": "text-muted-foreground size-3.5 ml-auto" } %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav aria-label="Components navigation" class="flex flex-col p-2">
|
||||
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Components</h2>
|
||||
<ul class="flex flex-col gap-1">
|
||||
{% set components = [
|
||||
{ "id": "accordion", "name": "Accordion" },
|
||||
{ "id": "alert", "name": "Alert" },
|
||||
{ "id": "alert-dialog", "name": "Alert Dialog" },
|
||||
{ "id": "avatar", "name": "Avatar" },
|
||||
{ "id": "badge", "name": "Badge" },
|
||||
{ "id": "breadcrumb", "name": "Breadcrumb" },
|
||||
{ "id": "button", "name": "Button" },
|
||||
{ "id": "card", "name": "Card" },
|
||||
{ "id": "checkbox", "name": "Checkbox" },
|
||||
{ "id": "combobox", "name": "Combobox" },
|
||||
{ "id": "dialog", "name": "Dialog" },
|
||||
{ "id": "dropdown-menu", "name": "Dropdown Menu" },
|
||||
{ "id": "form", "name": "Form" },
|
||||
{ "id": "input", "name": "Input" },
|
||||
{ "id": "label", "name": "Label" },
|
||||
{ "id": "pagination", "name": "Pagination" },
|
||||
{ "id": "popover", "name": "Popover" },
|
||||
{ "id": "radio-group", "name": "Radio Group" },
|
||||
{ "id": "select", "name": "Select" },
|
||||
{ "id": "skeleton", "name": "Skeleton" },
|
||||
{ "id": "slider", "name": "Slider" },
|
||||
{ "id": "switch", "name": "Switch" },
|
||||
{ "id": "table", "name": "Table" },
|
||||
{ "id": "tabs", "name": "Tabs" },
|
||||
{ "id": "textarea", "name": "Textarea" },
|
||||
{ "id": "toast", "name": "Toast" },
|
||||
{ "id": "tooltip", "name": "Tooltip" }
|
||||
] %}
|
||||
{% for component in components %}
|
||||
<li>
|
||||
<a
|
||||
href="#{{ component.id }}"
|
||||
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
{{ component.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
<footer class="flex flex-col gap-2 p-2">
|
||||
{% set profile_menu %}
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img src="https://avatar.vercel.sh/ronan_berder" 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">
|
||||
<nav role="menu">
|
||||
<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>
|
||||
</nav>
|
||||
{% endset %}
|
||||
|
||||
{% set trigger %}
|
||||
<img src="https://avatar.vercel.sh/ronan_berder" 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 popover(
|
||||
id="demo-sidebar-dropdown-account",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-ghost h-12 justify-start p-2 w-full"},
|
||||
content_attrs={"class": "min-w-56", "data-side": "right", "data-align": "end"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
</footer>
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
layout: layouts/component.njk
|
||||
title: Accordion
|
||||
description: A vertically stacked set of interactive headings that each reveal a section of content.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated accordion component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% 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>
|
||||
|
||||
<section class="prose my-6">
|
||||
<p>Basecoat animates <code><details></code> elements by default. The example above shows how to use Alpine.js, Tailwind and <code><details></code> elements to implement a simple accordion.</p>
|
||||
</section>
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
layout: layouts/component.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: Default
|
||||
id: usage-default
|
||||
- label: Jinja or Nunjucks
|
||||
id: usage-jinja
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "components/dialog.njk" import dialog %}
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "info" %}
|
||||
<h2>The Alert Dialog component uses the <a href="/components/dialog" class="font-medium underline underline-offset-4">Dialog</a> component.</h2>
|
||||
</div>
|
||||
|
||||
{% set code %}{% 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
|
||||
) }}{% endset %}
|
||||
{{ code_preview("alert-dialog", code | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">Default</a></h3>
|
||||
|
||||
<section class="prose my-6">
|
||||
<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>
|
||||
</section>
|
||||
|
||||
{{ code_block(code | prettyHtml) }}
|
||||
|
||||
<h3 id="usage-jinja"><a href="#usage-jinja">Jinja or Nunjucks</a></h3>
|
||||
|
||||
<section class="prose my-6">
|
||||
<p>If you use Jinja (or Nunjucks), you can use the Dialog macro directly, making sure to set <code>close_on_overlay_click</code> to <code>False</code> (or <code>false</code> for Nunjucks).</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/jinja-and-nunjucks">Read more about using Basecoat with Jinja or Nunjucks</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/components/dialog">Read more about the Dialog component</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{% 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") }}
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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.</li>
|
||||
</ul>
|
||||
<p><a href="/kitchen-sink#alert">See the kitchen sink for more examples.</a></p>
|
||||
</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) }}
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
layout: layouts/component.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 accordion 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>
|
||||
|
||||
<section class="prose my-6">
|
||||
<p>Avatars are fairly simple to implement with Tailwind. The example above shows how to implement one with Tailwind and an <code><img></code> element. <a href="/kitchen-sink#avatar">You can find more examples in the kitchen sink</a>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<p>Simply 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>. <a href="/kitchen-sink#badge">You can find more examples in the kitchen sink</a>.</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-primary"><a href="#example-primary">Primary</a></h3>
|
||||
|
||||
{% set code %}<span class="badge">Primary</span>{% endset %}
|
||||
{{ code_preview("badge-primary", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-secondary">Secondary</span>{% endset %}
|
||||
{{ code_preview("badge-secondary", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-destructive">Destructive</span>{% endset %}
|
||||
{{ code_preview("badge-destructive", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-outline">Outline</span>{% endset %}
|
||||
{{ code_preview("badge-outline", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>
|
||||
|
||||
{% set code %}<span class="badge-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
With icon
|
||||
</span>{% endset %}
|
||||
{{ code_preview("badge-with-icon", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<a href="#" class="badge-outline">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>{% endset %}
|
||||
{{ code_preview("badge-link", code) }}
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
layout: layouts/component.njk
|
||||
title: Breadcrumb
|
||||
description: Displays the path to the current resource using a hierarchy of links.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>There is no dedicated breadcrumb component in Basecoat.</h2>
|
||||
</div>
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% from "components/dropdown-menu.njk" import dropdown_menu %}
|
||||
{% set code %}<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="demo-breadcrumb-menu",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>{% endset %}
|
||||
{{ code_preview("dropdown-menu", code | prettyHtml) }}
|
||||
|
||||
<h2 id="usage"><a href="#usage">Usage</a></h2>
|
||||
|
||||
<section class="prose my-6">
|
||||
<p>The example below shows how to implement it with Tailwind and the <a href="/components/dropdown-menu" class="font-medium underline underline-offset-4">Dropdown Menu</a> component.</p>
|
||||
</section>
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<p>You can use one of the <code>btn</code> classes to style any element as a button:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variants</b>:
|
||||
<ul>
|
||||
<li><code>btn</code> or <code>btn-primary</code> for primary
|
||||
buttons,</li>
|
||||
<li><code>btn-secondary</code> for secondary buttons.</li>
|
||||
<li><code>btn-destructive</code> for destructive buttons.</li>
|
||||
<li><code>btn-outline</code> for outline buttons.</li>
|
||||
<li><code>btn-ghost</code> for ghost buttons.</li>
|
||||
<li><code>btn-link</code> for link buttons.</li>
|
||||
<li><code>btn-icon</code> for icon buttons. This can be combined with other variants, for example <code>btn-icon-destructive</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Sizes</b>:
|
||||
<ul>
|
||||
<li><code>btn-sm</code> for small buttons.</li>
|
||||
<li><code>btn-lg</code> for large buttons.</li>
|
||||
<li>You can combine sizes with any variant, for example <code>btn-lg-destructive</code> or <code>btn-sm-icon-outline</code>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
|
||||
<h2 id="examples"><a href="#examples">Examples</a></h2>
|
||||
|
||||
<h3 id="example-primary"><a href="#example-primary">Primary</a></h3>
|
||||
|
||||
{% set code %}<button class="btn">Primary</button>{% endset %}
|
||||
{{ code_preview("button-primary", code) }}
|
||||
|
||||
<h3 id="example-secondary"><a href="#example-secondary">Secondary</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-secondary">Button</button>{% endset %}
|
||||
{{ code_preview("button-secondary", code) }}
|
||||
|
||||
<h3 id="example-destructive"><a href="#example-destructive">Destructive</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-destructive">Destructive</button>{% endset %}
|
||||
{{ code_preview("button-destructive", code) }}
|
||||
|
||||
<h3 id="example-outline"><a href="#example-outline">Outline</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline">Outline</button>{% endset %}
|
||||
{{ code_preview("button-outline", code) }}
|
||||
|
||||
<h3 id="example-ghost"><a href="#example-ghost">Ghost</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-ghost">Ghost</button>{% endset %}
|
||||
{{ code_preview("button-ghost", code) }}
|
||||
|
||||
<h3 id="example-link"><a href="#example-link">Link</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-link">Link</button>{% endset %}
|
||||
{{ code_preview("button-link", code) }}
|
||||
|
||||
<h3 id="example-icon"><a href="#example-icon">Icon</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-icon-outline">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-icon", code) }}
|
||||
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With Icon</a></h3>
|
||||
|
||||
{% set code %}<button class="btn">
|
||||
{% lucide "send" %}
|
||||
Send email
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-with-icon", code) }}
|
||||
|
||||
<h3 id="example-loading"><a href="#example-loading">Loading</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-loading", code) }}
|
||||
|
||||
<h3 id="example-large"><a href="#example-large">Large</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-lg">
|
||||
{% lucide "shopping-cart" %}
|
||||
Buy
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-large", code) }}
|
||||
|
||||
<h3 id="example-small"><a href="#example-small">Small</a></h3>
|
||||
|
||||
{% set code %}<button class="btn-sm-icon-destructive">
|
||||
{% lucide "trash" %}
|
||||
</button>{% endset %}
|
||||
{{ code_preview("button-small", code) }}
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<p>Simply 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>
|
||||
<p><a href="/kitchen-sink#alert">See the kitchen sink for more examples.</a></p>
|
||||
</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) }}
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}<label class="label gap-3">
|
||||
<input type="checkbox" class="input" disabled>
|
||||
Accept terms and conditions
|
||||
</label>{% endset %}
|
||||
{{ code_preview("checkbox-disabled", code) }}
|
||||
|
||||
<h3 id="example-with-text"><a href="#example-with-text">With text</a></h3>
|
||||
|
||||
{% set code %}<div class="flex items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-with-text" class="input">
|
||||
<div class="grid gap-2">
|
||||
<label for="checkbox-with-text" class="label">Accept terms and conditions</label>
|
||||
<p class="text-muted-foreground text-sm">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</div>{% endset %}
|
||||
{{ code_preview("checkbox-with-text", code) }}
|
||||
|
||||
<h3 id="example-form"><a href="#example-form">Form</a></h3>
|
||||
|
||||
{% set code %}<form class="form flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
|
||||
<input type="checkbox" id="checkbox-form-1">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="checkbox-form-1" class="leading-snug">Use different settings for my mobile devices</label>
|
||||
<p class="text-muted-foreground text-sm leading-snug">You can manage your mobile notifications in the mobile settings page.</p>
|
||||
</div>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code) }}
|
||||
|
||||
{% set code %}<form class="form flex flex-col gap-4">
|
||||
<header>
|
||||
<label for="demo-form-checkboxes" class="text-base leading-normal">Sidebar</label>
|
||||
<p class="text-muted-foreground text-sm">Select the items you want to display in the sidebar.</p>
|
||||
</header>
|
||||
<fieldset id="demo-form-checkboxes" class="flex flex-col gap-2">
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="1" checked>
|
||||
Recents
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="2" checked>
|
||||
Home
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="3">
|
||||
Applications
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="4">
|
||||
Desktop
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="5">
|
||||
Download
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="6">
|
||||
Documents
|
||||
</label>
|
||||
</fieldset>
|
||||
</form>{% endset %}
|
||||
{{ code_preview("input-form", code) }}
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
layout: layouts/component.njk
|
||||
title: Dialog
|
||||
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "components/dialog.njk" import dialog %}
|
||||
{% set code %}{% 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
|
||||
) }}{% endset %}
|
||||
{{ code_block("alert-dialog", code | prettyHtml) }}
|
||||
|
||||
<h2>Usage</h2>
|
||||
|
||||
<p>The Alert Dialog component uses the same syntax as the <a href="/components/dialog" class="font-medium underline underline-offset-4">Dialog</a> component with one difference in the JS.</p>
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
layout: layouts/component.njk
|
||||
title: Dropdown Menu
|
||||
description: Displays a menu to the user — such as a set of actions or functions — triggered by a button.
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "components/dropdown-menu.njk" import dropdown_menu %}
|
||||
{% set code %}{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
trigger="Open Menu",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</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 %}
|
||||
{{ code_block("dropdown-menu", code | prettyHtml) }}
|
||||
|
||||
|
||||
<h2 id="usage">Usage</h2>
|
||||
|
||||
<section class="prose my-6">
|
||||
<p>You can use one of the <code class="">btn</code> classes to style any element as a button:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variants</b>: <code>btn</code> or <code>btn-primary</code> for primary
|
||||
buttons, then <code>btn-secondary</code>, <code>btn-destructive</code>, <code>btn-outline</code>,
|
||||
<code>btn-ghost</code>, <code>btn-link</code>. You can also use <code>btn-icon</code> to
|
||||
style an icon as a button, along with all of its variants (e.g. <code>btn-icon-destructive</code>).
|
||||
</li>
|
||||
<li>
|
||||
<b>Sizes</b>: <code>btn-sm</code> for small and <code>btn-lg</code> for large.
|
||||
You can combine this with other attributes, for example <code>btn-lg-destructive</code>
|
||||
or <code>btn-icon-sm-secondary</code>.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<h2>Examples</h2>
|
||||
|
||||
<h3>Primary</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn">Primary</button>{% endset %}
|
||||
{{ code_block("accordion", code | prettyHtml) }}
|
||||
|
||||
<h3>Secondary</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-secondary">Button</button>{% endset %}
|
||||
{{ code_block("accordion", code | prettyHtml) }}
|
||||
|
||||
<h3>Destructive</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-destructive">Destructive</button>{% endset %}
|
||||
{{ code_block("accordion", code | prettyHtml) }}
|
||||
|
||||
<h3>Outline</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-outline">Outline</button>{% endset %}
|
||||
{{ code_block("accordion", code | prettyHtml) }}
|
||||
|
||||
<h3>Ghost</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-ghost">Ghost</button>{% endset %}
|
||||
{{ code_block("accordion", code | prettyHtml) }}
|
||||
|
||||
<h3>Link</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-link">Link</button>{% endset %}
|
||||
{{ code_block("accordion", code | prettyHtml) }}
|
||||
|
||||
<h3>Icon</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-icon-outline">
|
||||
{% lucide "chevron-right" %}
|
||||
</button>{% endset %}
|
||||
{{ code_block("accordion", code | prettyHtml) }}
|
||||
|
||||
<h3>With Icon</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn">
|
||||
{% lucide "send" %}
|
||||
Send email
|
||||
</button>{% endset %}
|
||||
{{ code_block("accordion", code | prettyHtml) }}
|
||||
|
||||
<h3>Loading</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>{% endset %}
|
||||
{{ code_block("accordion", code) }}
|
||||
|
||||
<h3>Large</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-lg">
|
||||
{% lucide "shopping-cart" %}
|
||||
Buy
|
||||
</button>{% endset %}
|
||||
{{ code_block("accordion", code) }}
|
||||
|
||||
<h3>Small</h3>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-icon-sm-destructive">
|
||||
{% lucide "trash" %}
|
||||
</button>{% endset %}
|
||||
{{ code_block("accordion", code) }}
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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>
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{{ 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") }}
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
layout: layouts/component.njk
|
||||
title: Label
|
||||
description: Renders an accessible label associated with controls.
|
||||
toc:
|
||||
- label: Usage
|
||||
id: usage
|
||||
---
|
||||
|
||||
{% 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 my-6">
|
||||
<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 will be styled accordingly if a peer or child element is disabled.</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<label class="label" for="email">Your email address</label>{% endset %}
|
||||
{{ code_block(code) }}
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<p>A pagination is fairly simple to implement with Tailwind using the Basecoat button classes:</p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code) }}
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
layout: layouts/component.njk
|
||||
title: Popover
|
||||
description: Displays rich content in a portal, triggered by a button.
|
||||
---
|
||||
|
||||
<div class="alert mb-6">
|
||||
{% lucide "info" %}
|
||||
<h2>There is no dedicated avatar component in Basecoat.</h2>
|
||||
<section>
|
||||
<p>Simply use regular Tailwind classes. <a href="/kitchen-sink#avatar" class="underline hover:text-foreground transition-all">You can find more examples of avatars in the kitchen sink.</a></p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<img class="size-8 shrink-0 object-cover rounded-full" alt="@hunvreus" src="https://github.com/hunvreus.png">{% endset %}
|
||||
{{ code_block("accordion", code, "max-w-md") }}
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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") }}
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
layout: layouts/component.njk
|
||||
title: Select
|
||||
description: isplays a list of options for the user to pick from—triggered by a button.
|
||||
---
|
||||
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% set code %}<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/success" hx-target="#toast-htmx">
|
||||
Success
|
||||
</button>
|
||||
<div id="toast-htmx"></div>{% endset %}
|
||||
{{ code_block("accordion", code, "max-w-md") }}
|
||||
|
||||
<h2>Usage</h2>
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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) }}
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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") }}
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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) }}
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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>
|
||||
|
||||
<h3 id="example-default"><a href="#example-default">Default</a></h3>
|
||||
|
||||
{{ 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") }}
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
layout: layouts/component.njk
|
||||
title: Toast
|
||||
description: A succinct message that is displayed temporarily.
|
||||
---
|
||||
|
||||
{% from "macros/code_preview.njk" import code_preview %}
|
||||
{% set code %}<button class="btn-outline" hx-trigger="click" hx-get="/fragments/toast/success" hx-target="#toast-htmx">
|
||||
Success
|
||||
</button>
|
||||
<div id="toast-htmx"></div>{% endset %}
|
||||
{{ code_preview("accordion", code, "max-w-md") }}
|
||||
|
||||
<h2>Usage</h2>
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
layout: layouts/component.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 my-6">
|
||||
<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) }}
|
||||
+5
-139
@@ -1,141 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="dark"><head>
|
||||
<!-- Theme -->
|
||||
<script>
|
||||
function isDark() {
|
||||
return localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
}
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
---
|
||||
|
||||
if (isDark()) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
What is it?
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('theme', {
|
||||
isDark: isDark(),
|
||||
set(value) {
|
||||
this.isDark = value;
|
||||
localStorage.setItem('theme', value ? 'dark' : 'light');
|
||||
},
|
||||
toggle() {
|
||||
this.isDark = document.documentElement.classList.toggle('dark');
|
||||
localStorage.setItem('theme', this.isDark ? 'dark' : 'light');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Language" content="en">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Title & Description -->
|
||||
<title>Basecoat</title>
|
||||
<meta name="title" content="Basecoat">
|
||||
<meta name="description" content="Minimal CSS UI kit for vanilla HTML.">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="preconnect" href="https://rsms.me/">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
|
||||
<!-- JS -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
</head>
|
||||
<body>
|
||||
{% from "./_includes/components/dialog.njk" import dialog %}
|
||||
{% from "./_includes/components/popover.njk" import popover %}
|
||||
{% from "./_includes/components/select.njk" import select %}
|
||||
{% from "./_includes/components/toast.njk" import toaster %}
|
||||
|
||||
<div
|
||||
class="fixed inset-y-0 left-0 z-50 h-svh border-r bg-sidebar text-sidebar-foreground flex flex-col w-[var(--sidebar-width)] transition-transform ease-in-out peer aria-[hidden=true]:-translate-x-full"
|
||||
aria-hidden="false"
|
||||
:aria-hidden="open ? 'false' : 'true'"
|
||||
@sidebar:toggle.window="toggle()"
|
||||
x-data="{
|
||||
open: true,
|
||||
toggle() { this.open = !this.open }
|
||||
}"
|
||||
>
|
||||
{% include "./_includes/sidebar.njk" %}
|
||||
</div>
|
||||
|
||||
<main class="relative ml-(--sidebar-width) peer-aria-hidden:ml-0 transition-[margin-left] ease-in-out">
|
||||
<header class="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
||||
<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-icon-ghost size-8 mr-auto"
|
||||
x-init
|
||||
x-data="{ sidebarOpen: true }"
|
||||
@click="
|
||||
sidebarOpen = !sidebarOpen;
|
||||
$dispatch('sidebar:toggle');
|
||||
"
|
||||
>
|
||||
{% lucide "panel-left" %}
|
||||
</button>
|
||||
<select class="select h-8 leading-none">
|
||||
<option value="light">Default</option>
|
||||
<option value="light">Other</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle theme"
|
||||
data-tooltip="Toggle theme"
|
||||
data-side="bottom"
|
||||
data-align="end"
|
||||
class="btn-icon-outline size-8"
|
||||
x-init
|
||||
@click="$store.theme.toggle()"
|
||||
>
|
||||
<span x-show="$store.theme.isDark">{% lucide "sun" %}</span>
|
||||
<span x-show="!$store.theme.isDark">{% lucide "moon" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-4 p-4">
|
||||
{% include "./_includes/partials/accordion.njk" %}
|
||||
{% include "./_includes/partials/alert.njk" %}
|
||||
{% include "./_includes/partials/alert-dialog.njk" %}
|
||||
{% include "./_includes/partials/avatar.njk" %}
|
||||
{% include "./_includes/partials/badge.njk" %}
|
||||
{% include "./_includes/partials/breadcrumb.njk" %}
|
||||
{% include "./_includes/partials/button.njk" %}
|
||||
{% include "./_includes/partials/card.njk" %}
|
||||
{% include "./_includes/partials/checkbox.njk" %}
|
||||
{% include "./_includes/partials/combobox.njk" %}
|
||||
{% include "./_includes/partials/dialog.njk" %}
|
||||
{% include "./_includes/partials/dropdown-menu.njk" %}
|
||||
{% include "./_includes/partials/form.njk" %}
|
||||
{% include "./_includes/partials/input.njk" %}
|
||||
{% include "./_includes/partials/label.njk" %}
|
||||
{% include "./_includes/partials/pagination.njk" %}
|
||||
{% include "./_includes/partials/popover.njk" %}
|
||||
{% include "./_includes/partials/radio-group.njk" %}
|
||||
{% include "./_includes/partials/select.njk" %}
|
||||
{% include "./_includes/partials/skeleton.njk" %}
|
||||
{% include "./_includes/partials/slider.njk" %}
|
||||
{% include "./_includes/partials/switch.njk" %}
|
||||
{% include "./_includes/partials/table.njk" %}
|
||||
{% include "./_includes/partials/tabs.njk" %}
|
||||
{% include "./_includes/partials/textarea.njk" %}
|
||||
{% include "./_includes/partials/toast.njk" %}
|
||||
{% include "./_includes/partials/tooltip.njk" %}
|
||||
</div>
|
||||
</main>
|
||||
{{ toaster() }}
|
||||
</body>
|
||||
</html>
|
||||
Who is it for?
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
---
|
||||
{% include "partials/kitchen-sink/accordion.njk" %}
|
||||
{% include "partials/kitchen-sink/alert.njk" %}
|
||||
{% include "partials/kitchen-sink/alert-dialog.njk" %}
|
||||
{% include "partials/kitchen-sink/avatar.njk" %}
|
||||
{% include "partials/kitchen-sink/badge.njk" %}
|
||||
{% include "partials/kitchen-sink/breadcrumb.njk" %}
|
||||
{% include "partials/kitchen-sink/button.njk" %}
|
||||
{% include "partials/kitchen-sink/card.njk" %}
|
||||
{% include "partials/kitchen-sink/checkbox.njk" %}
|
||||
{% include "partials/kitchen-sink/combobox.njk" %}
|
||||
{% include "partials/kitchen-sink/dialog.njk" %}
|
||||
{% include "partials/kitchen-sink/dropdown-menu.njk" %}
|
||||
{% include "partials/kitchen-sink/form.njk" %}
|
||||
{% include "partials/kitchen-sink/input.njk" %}
|
||||
{% include "partials/kitchen-sink/label.njk" %}
|
||||
{% include "partials/kitchen-sink/pagination.njk" %}
|
||||
{% include "partials/kitchen-sink/popover.njk" %}
|
||||
{% include "partials/kitchen-sink/radio-group.njk" %}
|
||||
{% include "partials/kitchen-sink/select.njk" %}
|
||||
{% include "partials/kitchen-sink/skeleton.njk" %}
|
||||
{% include "partials/kitchen-sink/slider.njk" %}
|
||||
{% include "partials/kitchen-sink/switch.njk" %}
|
||||
{% include "partials/kitchen-sink/table.njk" %}
|
||||
{% include "partials/kitchen-sink/tabs.njk" %}
|
||||
{% include "partials/kitchen-sink/textarea.njk" %}
|
||||
{% include "partials/kitchen-sink/toast.njk" %}
|
||||
{% include "partials/kitchen-sink/tooltip.njk" %}
|
||||
Generated
+17
@@ -9,6 +9,7 @@
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "^4.1.3"
|
||||
}
|
||||
},
|
||||
@@ -2877,6 +2878,22 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/promise": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwindcss": "^4.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user