mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import eleventyLucideicons from "@grimlink/eleventy-plugin-lucide-icons";
|
||||
|
||||
export default async function(eleventyConfig) {
|
||||
eleventyConfig.setInputDirectory("./docs");
|
||||
eleventyConfig.setServerPassthroughCopyBehavior("passthrough");
|
||||
eleventyConfig.addPassthroughCopy({"./dist/basecoat.css": "./assets/styles.css"});
|
||||
eleventyConfig.addPlugin(eleventyLucideicons);
|
||||
// Registry to ensure component assets (JS) load only once per page.
|
||||
let componentRegistry = {}
|
||||
eleventyConfig.on('beforeBuild', () => { componentRegistry = {}; })
|
||||
eleventyConfig.addNunjucksGlobal("registerComponent", function(componentName) {
|
||||
if (!componentRegistry[componentName]) {
|
||||
componentRegistry[componentName] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
.*
|
||||
!.gitignore
|
||||
!.eleventy.js
|
||||
|
||||
/node_modules
|
||||
/_site
|
||||
|
||||
|
||||
# Logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,8 @@
|
||||
TODO
|
||||
|
||||
- Card (HTML)
|
||||
- Combobox
|
||||
- Command
|
||||
- Tabs
|
||||
- Aspect ratio???
|
||||
- Avatar
|
||||
Vendored
+2950
File diff suppressed because it is too large
Load Diff
Vendored
+790
@@ -0,0 +1,790 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--scrollbar-track: transparent;
|
||||
--scrollbar-thumb: rgba(0, 0, 0, 0.3);
|
||||
--scrollbar-width: 6px;
|
||||
--scrollbar-radius: 6px;
|
||||
--chevron-down-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0 / 0.5)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>'); /* --muted-foreground + 50% opacity */
|
||||
--check-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>'); /* --muted-foreground */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.269 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.371 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
--scrollbar-track: transparent;
|
||||
--scrollbar-thumb: rgba(255, 255, 255, 0.3);
|
||||
--scrollbar-width: 6px;
|
||||
--scrollbar-radius: 6px;
|
||||
--chevron-down-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.708 0 0 / 0.5)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>'); /* --muted-foreground + 50% opacity */
|
||||
--check-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.708 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>');/* --muted-foreground */
|
||||
}
|
||||
|
||||
@theme {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--font-sans: 'Inter var', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* @import "./theme.css"; */
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
html {
|
||||
@apply scroll-smooth;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground overscroll-none antialiased;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: var(--scrollbar-width);
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-track);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-thumb);
|
||||
border-radius: var(--scrollbar-radius);
|
||||
}
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Alpine.js */
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
/* HTMX */
|
||||
.htmx-indicator {
|
||||
transition-behavior: allow-discrete;
|
||||
@apply invisible opacity-0 transition-all;
|
||||
}
|
||||
.htmx-indicator:not(.htmx-request) {
|
||||
@apply hidden;
|
||||
}
|
||||
.htmx-request,
|
||||
.htmx-request.htmx-indicator {
|
||||
@apply visible opacity-100;
|
||||
}
|
||||
}
|
||||
|
||||
/* Alert */
|
||||
@layer components {
|
||||
.alert,
|
||||
.alert-destructive {
|
||||
@apply relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current;
|
||||
|
||||
h2 {
|
||||
@apply col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight;
|
||||
}
|
||||
|
||||
section {
|
||||
@apply text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed;
|
||||
|
||||
ul {
|
||||
@apply list-inside list-disc text-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
@apply bg-card text-card-foreground;
|
||||
}
|
||||
.alert-destructive {
|
||||
@apply text-destructive bg-card [&>svg]:text-current;
|
||||
|
||||
section {
|
||||
@apply text-destructive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Button */
|
||||
@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 {
|
||||
@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,
|
||||
.btn-primary,
|
||||
.btn-secondary,
|
||||
.btn-outline,
|
||||
.btn-ghost,
|
||||
.btn-link,
|
||||
.btn-destructive {
|
||||
@apply gap-2 h-9 px-4 py-2 has-[>svg]:px-3;
|
||||
}
|
||||
.btn-icon,
|
||||
.btn-icon-primary,
|
||||
.btn-icon-secondary,
|
||||
.btn-icon-outline,
|
||||
.btn-icon-ghost,
|
||||
.btn-icon-link,
|
||||
.btn-icon-destructive {
|
||||
@apply size-9;
|
||||
}
|
||||
.btn-sm,
|
||||
.btn-sm-primary,
|
||||
.btn-sm-secondary,
|
||||
.btn-sm-outline,
|
||||
.btn-sm-ghost,
|
||||
.btn-sm-link,
|
||||
.btn-sm-destructive {
|
||||
@apply gap-1.5 h-8 px-3 has-[>svg]:px-2.5;
|
||||
}
|
||||
.btn-lg,
|
||||
.btn-lg-primary,
|
||||
.btn-lg-secondary,
|
||||
.btn-lg-outline,
|
||||
.btn-lg-ghost,
|
||||
.btn-lg-link,
|
||||
.btn-lg-destructive {
|
||||
@apply gap-2 h-10 px-6 has-[>svg]:px-4;
|
||||
}
|
||||
.btn,
|
||||
.btn-primary,
|
||||
.btn-sm,
|
||||
.btn-sm-primary,
|
||||
.btn-lg,
|
||||
.btn-lg-primary,
|
||||
.btn-icon,
|
||||
.btn-icon-primary {
|
||||
@apply bg-primary text-primary-foreground shadow-xs hover:bg-primary/90;
|
||||
&[aria-pressed='true'] {
|
||||
@apply bg-primary/90;
|
||||
}
|
||||
}
|
||||
.btn-secondary,
|
||||
.btn-sm-secondary,
|
||||
.btn-lg-secondary,
|
||||
.btn-icon-secondary {
|
||||
@apply bg-secondary text-secondary-foreground shadow-xs;
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@apply bg-secondary/80;
|
||||
}
|
||||
}
|
||||
.btn-outline,
|
||||
.btn-sm-outline,
|
||||
.btn-lg-outline,
|
||||
.btn-icon-outline {
|
||||
@apply border bg-background shadow-xs dark:bg-input/30 dark:border-input;
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@apply bg-accent text-accent-foreground dark:bg-accent/50;
|
||||
}
|
||||
}
|
||||
.btn-ghost,
|
||||
.btn-sm-ghost,
|
||||
.btn-lg-ghost,
|
||||
.btn-icon-ghost {
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@apply bg-accent text-accent-foreground dark:bg-accent/50;
|
||||
}
|
||||
}
|
||||
.btn-link,
|
||||
.btn-sm-link,
|
||||
.btn-lg-link,
|
||||
.btn-icon-link {
|
||||
@apply text-primary underline-offset-4;
|
||||
&:hover,
|
||||
&[aria-pressed='true'] {
|
||||
@apply hover:underline;
|
||||
}
|
||||
}
|
||||
.btn-destructive,
|
||||
.btn-sm-destructive,
|
||||
.btn-lg-destructive,
|
||||
.btn-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'] {
|
||||
@apply bg-destructive/90 dark:bg-destructive/50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Badge */
|
||||
@layer components {
|
||||
.badge,
|
||||
.badge-primary,
|
||||
.badge-secondary,
|
||||
.badge-destructive,
|
||||
.badge-outline {
|
||||
@apply inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-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 transition-[color,box-shadow] overflow-hidden
|
||||
}
|
||||
.badge,
|
||||
.badge-primary {
|
||||
@apply border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90;
|
||||
}
|
||||
.badge-secondary {
|
||||
@apply border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90;
|
||||
}
|
||||
.badge-destructive {
|
||||
@apply border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60;
|
||||
}
|
||||
.badge-outline {
|
||||
@apply text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
@layer components {
|
||||
.form input[type='checkbox']:not([role='switch']),
|
||||
.input[type='checkbox']:not([role='switch']) {
|
||||
@apply appearance-none border-input dark:bg-input/30 checked:bg-primary dark:checked:bg-primary checked:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50;
|
||||
|
||||
&:checked:after {
|
||||
@apply content-[''] block size-3.5 bg-primary-foreground;
|
||||
@apply mask-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWNoZWNrLWljb24gbHVjaWRlLWNoZWNrIj48cGF0aCBkPSJNMjAgNiA5IDE3bC01LTUiLz48L3N2Zz4=')] mask-size-[0.875rem] mask-no-repeat mask-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Collapsible */
|
||||
@layer components {
|
||||
details {
|
||||
&::details-content {
|
||||
block-size: 0;
|
||||
transition-behavior: allow-discrete;
|
||||
@apply block opacity-0 transition-all;
|
||||
}
|
||||
}
|
||||
details[open]::details-content {
|
||||
block-size: auto;
|
||||
block-size: calc-size(auto, size);
|
||||
@apply opacity-100;
|
||||
}
|
||||
details > summary {
|
||||
@apply inline-flex items-center cursor-pointer;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dialog */
|
||||
@layer components {
|
||||
.dialog {
|
||||
[role='dialog'] {
|
||||
@apply fixed inset-0 z-50 bg-black/50 opacity-0 invisible transition-all;
|
||||
|
||||
&:not([inert]),
|
||||
&[inert='false'] {
|
||||
@apply opacity-100 visible;
|
||||
> article {
|
||||
@apply opacity-100 scale-100 visible;
|
||||
}
|
||||
}
|
||||
> article {
|
||||
@apply bg-background fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg sm:max-w-lg opacity-0 scale-95 invisible transition-all max-h-[calc(100%-2rem)];
|
||||
|
||||
> header {
|
||||
@apply flex flex-col gap-2 text-center sm:text-left;
|
||||
|
||||
> h2 {
|
||||
@apply text-lg leading-none font-semibold;
|
||||
}
|
||||
> p {
|
||||
@apply text-muted-foreground text-sm;
|
||||
}
|
||||
}
|
||||
> section {
|
||||
@apply flex-1 -mx-6 px-6;
|
||||
}
|
||||
> footer {
|
||||
@apply flex flex-col-reverse gap-2 sm:flex-row sm:justify-end;
|
||||
}
|
||||
> button {
|
||||
@apply ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Dropdown */
|
||||
@layer components {
|
||||
.dropdown {
|
||||
@apply relative inline-flex;
|
||||
|
||||
[role='menu'],
|
||||
[role='listbox'] {
|
||||
@apply absolute bg-popover text-popover-foreground overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md z-50 w-max visible opacity-100 scale-100 transition-all;
|
||||
|
||||
&[aria-hidden='true'] {
|
||||
@apply invisible opacity-0 scale-95;
|
||||
|
||||
&:not([data-side]),
|
||||
&[data-side='bottom'] {
|
||||
@apply -translate-y-2;
|
||||
}
|
||||
&[data-side='top'] {
|
||||
@apply translate-y-2;
|
||||
}
|
||||
&[data-side='left'] {
|
||||
@apply translate-x-2;
|
||||
}
|
||||
&[data-side='right'] {
|
||||
@apply -translate-x-2;
|
||||
}
|
||||
}
|
||||
&:not([data-side]),
|
||||
&[data-side='bottom'] {
|
||||
@apply mt-1 top-full;
|
||||
}
|
||||
&[data-side='top'] {
|
||||
@apply mb-1 bottom-full;
|
||||
}
|
||||
&[data-side='left'] {
|
||||
@apply mr-1 right-full;
|
||||
}
|
||||
&[data-side='right'] {
|
||||
@apply ml-1 left-full;
|
||||
}
|
||||
&:not([data-side]),
|
||||
&[data-side='bottom'],
|
||||
&[data-side='top'] {
|
||||
&:not([data-align]),
|
||||
&[data-align='start'] {
|
||||
@apply left-0;
|
||||
}
|
||||
&[data-align='end'] {
|
||||
@apply right-0;
|
||||
}
|
||||
&[data-align='center'] {
|
||||
@apply left-1/2 -translate-x-1/2;
|
||||
}
|
||||
}
|
||||
&[data-side='left'],
|
||||
&[data-side='right'] {
|
||||
&:not([data-align]),
|
||||
&[data-align='start'] {
|
||||
@apply top-0;
|
||||
}
|
||||
&[data-align='end'] {
|
||||
@apply bottom-0;
|
||||
}
|
||||
&[data-align='center'] {
|
||||
@apply top-1/2 -translate-y-1/2;
|
||||
}
|
||||
}
|
||||
|
||||
[role='menuitem'],
|
||||
[role='option'] {
|
||||
@apply hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground [&_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 disabled:opacity-50 disabled:pointer-events-none w-full truncate;
|
||||
}
|
||||
[role='option'] {
|
||||
@apply 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];
|
||||
}
|
||||
}
|
||||
[role='separator'] {
|
||||
@apply border-border -mx-1 my-1;
|
||||
}
|
||||
}
|
||||
[role='menu'] [role='heading'] {
|
||||
@apply flex px-2 py-1.5 text-sm font-medium;
|
||||
}
|
||||
[role='listbox'] [role='heading'] {
|
||||
@apply flex text-muted-foreground px-2 py-1.5 text-xs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Input */
|
||||
@layer components {
|
||||
.form input[type='text'],
|
||||
.form input[type='email'],
|
||||
.form input[type='password'],
|
||||
.form input[type='number'],
|
||||
.form input[type='file'],
|
||||
.form input[type='tel'],
|
||||
.form input[type='url'],
|
||||
.form input[type='search'],
|
||||
.form input[type='date'],
|
||||
.form input[type='datetime-local'],
|
||||
.form input[type='month'],
|
||||
.form input[type='week'],
|
||||
.form input[type='time'],
|
||||
.input[type='text'],
|
||||
.input[type='email'],
|
||||
.input[type='password'],
|
||||
.input[type='number'],
|
||||
.input[type='file'],
|
||||
.input[type='tel'],
|
||||
.input[type='url'],
|
||||
.input[type='search'],
|
||||
.input[type='date'],
|
||||
.input[type='datetime-local'],
|
||||
.input[type='month'],
|
||||
.input[type='week'],
|
||||
.input[type='time'] {
|
||||
@apply appearance-none file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm;
|
||||
@apply focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px];
|
||||
@apply aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive;
|
||||
}
|
||||
.form input[type='date'],
|
||||
.form input[type='datetime-local'],
|
||||
.form input[type='month'],
|
||||
.form input[type='week'],
|
||||
.form input[type='time'],
|
||||
.input[type='date'],
|
||||
.input[type='datetime-local'],
|
||||
.input[type='month'],
|
||||
.input[type='week'],
|
||||
.input[type='time'] {
|
||||
@apply dark:scheme-dark;
|
||||
}
|
||||
}
|
||||
|
||||
/* Label */
|
||||
@layer components {
|
||||
.form label,
|
||||
.label {
|
||||
@apply flex items-center gap-2 text-sm leading-none font-medium select-none peer-disabled:pointer-events-none peer-disabled:opacity-50;
|
||||
|
||||
&:has(>*:disabled),
|
||||
&:has(+*:disabled) {
|
||||
@apply opacity-50 pointer-events-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Radio */
|
||||
@layer components {
|
||||
.form input[type='radio'],
|
||||
.input[type='radio'] {
|
||||
@apply appearance-none border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 relative;
|
||||
|
||||
&:checked:before {
|
||||
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 content-[''] rounded-full size-2 bg-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Range */
|
||||
@layer components {
|
||||
.form input[type='range'],
|
||||
.input[type='range'] {
|
||||
@apply appearance-none flex items-center;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
@apply appearance-none border-primary bg-background ring-ring/50 block size-4 shrink-0 rounded-full border shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50;
|
||||
|
||||
&:focus {
|
||||
@apply ring-2 ring-offset-2 ring-offset-background ring-primary transition-all;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
@apply appearance-none bg-primary h-1.5 rounded-full;
|
||||
}
|
||||
/* .input[type="range"]:focus::-webkit-slider-thumb {
|
||||
@apply ring-2 ring-offset-2 ring-offset-background ring-primary transition-all;
|
||||
}
|
||||
.input[type="range"]::-webkit-slider-runnable-track {
|
||||
@apply bg-input h-2 rounded-full;
|
||||
}
|
||||
.input[type="range"]::-moz-range-thumb {
|
||||
@apply outline-none appearance-none bg-primary w-6 h-6 rounded-full cursor-grab p-0 m-0 border-0 focus:ring-2 hover:bg-primary/90 transition-all;
|
||||
}
|
||||
.input[type="range"]:focus::-moz-range-thumb {
|
||||
@apply ring-2 ring-offset-2 ring-offset-background ring-primary transition-all;
|
||||
}
|
||||
.input[type="range"]::-moz-range-track {
|
||||
@apply bg-input h-2 rounded-full border-0;
|
||||
}
|
||||
.input[type="range"]::-ms-thumb {
|
||||
@apply outline-none appearance-none bg-primary w-6 h-6 rounded-full cursor-grab p-0 m-0 border-0 focus:ring-2 hover:bg-primary/90 transition-all;
|
||||
}
|
||||
.input[type="range"]:focus::-ms-thumb {
|
||||
@apply ring-2 ring-offset-2 ring-offset-background ring-primary transition-all;
|
||||
}
|
||||
.input[type="range"]::-ms-track {
|
||||
@apply bg-input h-2 rounded-full border-0 text-transparent;
|
||||
}
|
||||
.input[type="range"]::-ms-fill-lower {
|
||||
@apply bg-primary rounded-full;
|
||||
}
|
||||
.input[type="range"]::-ms-fill-upper {
|
||||
@apply bg-input rounded-full;
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
/* Select */
|
||||
@layer components {
|
||||
.form select,
|
||||
.select {
|
||||
@apply appearance-none border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent pl-3 pr-9 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 h-9 leading-none;
|
||||
@apply bg-[image:var(--chevron-down-icon)] bg-no-repeat bg-position-[center_right_0.75rem] bg-size-[1rem];
|
||||
}
|
||||
}
|
||||
|
||||
/* Switch */
|
||||
@layer components {
|
||||
.form input[type='checkbox'][role='switch'],
|
||||
.input[type='checkbox'][role='switch'] {
|
||||
@apply appearance-none focus-visible:border-ring focus-visible:ring-ring/50 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50;
|
||||
@apply bg-input dark:bg-input/80 checked:bg-primary dark:checked:bg-primary;
|
||||
@apply before:content-[''] before:pointer-events-none before:block before:size-4 before:rounded-full before:ring-0 before:transition-all;
|
||||
@apply before:bg-background dark:before:bg-foreground;
|
||||
@apply dark:checked:before:bg-primary-foreground checked:before:ms-3.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
@layer components {
|
||||
.table {
|
||||
@apply w-full caption-bottom text-sm;
|
||||
thead {
|
||||
@apply [&_tr]:border-b;
|
||||
}
|
||||
tbody {
|
||||
@apply [&_tr:last-child]:border-0;
|
||||
}
|
||||
tfoot {
|
||||
@apply bg-muted/50 border-t font-medium [&>tr]:last:border-b-0;
|
||||
}
|
||||
tr {
|
||||
@apply hover:bg-muted/50 border-b transition-colors;
|
||||
}
|
||||
th {
|
||||
@apply text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px];
|
||||
}
|
||||
td {
|
||||
@apply p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px];
|
||||
}
|
||||
caption {
|
||||
@apply text-muted-foreground mt-4 text-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Textarea */
|
||||
@layer components {
|
||||
.form textarea,
|
||||
.textarea {
|
||||
@apply border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
@layer components {
|
||||
[data-tooltip] {
|
||||
@apply relative;
|
||||
|
||||
&:before {
|
||||
@apply absolute content-[attr(data-tooltip)] bg-primary text-primary-foreground z-50 truncate max-w-xs w-fit rounded-md px-3 py-1.5 text-xs invisible opacity-0 scale-95 transition-all;
|
||||
}
|
||||
&:hover:before {
|
||||
@apply visible opacity-100 scale-100;
|
||||
}
|
||||
&:not([data-side]),
|
||||
&[data-side='top'] {
|
||||
@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 ;
|
||||
}
|
||||
&:not([data-side]),
|
||||
&[data-side='top'],
|
||||
&[data-side='bottom'] {
|
||||
&[data-align='start'] {
|
||||
@apply before:bg-red-500 before:left-0;
|
||||
}
|
||||
&[data-align='end'] {
|
||||
@apply before:right-0;
|
||||
}
|
||||
&:not([data-align]),
|
||||
&[data-align='center'] {
|
||||
@apply before:left-1/2 before:-translate-x-1/2;
|
||||
}
|
||||
}
|
||||
&[data-side='left'] {
|
||||
@apply before:right-full before:mr-1.5 before:translate-x-2 hover:before:translate-x-0;
|
||||
}
|
||||
&[data-side='right'] {
|
||||
@apply before:left-full before:ml-1.5 before:-translate-x-2 hover:before:translate-x-0;
|
||||
}
|
||||
&[data-side='left'],
|
||||
&[data-side='right'] {
|
||||
&[data-align='start'] {
|
||||
@apply before:top-0;
|
||||
}
|
||||
&[data-align='end'] {
|
||||
@apply before:bottom-0;
|
||||
}
|
||||
&:not([data-align]),
|
||||
&[data-align='center'] {
|
||||
@apply before:top-1/2 before:-translate-y-1/2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
|
||||
/* Notifications */
|
||||
@layer components {
|
||||
.toasts {
|
||||
@apply fixed bottom-0 right-0 p-6 space-y-2 pointer-events-none z-[100] max-w-sm;
|
||||
}
|
||||
.toasts > [role='alert'],
|
||||
.toasts > [role='status'] {
|
||||
transition-behavior: allow-discrete;
|
||||
@apply bg-background border shadow-lg rounded-lg p-4 pointer-events-auto flex flex-row-reverse items-start justify-between gap-x-2;
|
||||
}
|
||||
.toasts > [aria-hidden='true'] {
|
||||
@apply hidden opacity-0 scale-75 transition-all;
|
||||
}
|
||||
.toasts > [role='alert'] > button,
|
||||
.toasts > [role='status'] > button {
|
||||
@apply opacity-50 hover:opacity-100 transition-opacity py-0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
@layer components {
|
||||
ul[role='tablist'] {
|
||||
@apply flex items-center border-b border-border;
|
||||
}
|
||||
ul[role='tablist'] li {
|
||||
@apply flex items-center gap-x-2 -mb-[1px];
|
||||
}
|
||||
ul[role='tablist'] li [role='tab'] {
|
||||
@apply flex items-center gap-x-2 font-medium text-muted-foreground px-3 py-2.5 hover:text-foreground transition-colors border-b-2 border-transparent;
|
||||
}
|
||||
ul[role='tablist'] li [role='tab'][aria-selected='true'] {
|
||||
@apply text-foreground border-b-primary;
|
||||
}
|
||||
}
|
||||
Vendored
+136
@@ -0,0 +1,136 @@
|
||||
:root {
|
||||
--background: oklch(0.98 0.01 95.10);
|
||||
--foreground: oklch(0.34 0.03 95.72);
|
||||
--card: oklch(0.98 0.01 95.10);
|
||||
--card-foreground: oklch(0.19 0.00 106.59);
|
||||
--popover: oklch(1.00 0 0);
|
||||
--popover-foreground: oklch(0.27 0.02 98.94);
|
||||
--primary: oklch(0.62 0.14 39.04);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.92 0.01 92.99);
|
||||
--secondary-foreground: oklch(0.43 0.02 98.60);
|
||||
--muted: oklch(0.93 0.02 90.24);
|
||||
--muted-foreground: oklch(0.61 0.01 97.42);
|
||||
--accent: oklch(0.92 0.01 92.99);
|
||||
--accent-foreground: oklch(0.27 0.02 98.94);
|
||||
--destructive: oklch(0.19 0.00 106.59);
|
||||
--destructive-foreground: oklch(1.00 0 0);
|
||||
--border: oklch(0.88 0.01 97.36);
|
||||
--input: oklch(0.76 0.02 98.35);
|
||||
--ring: oklch(0.59 0.17 253.06);
|
||||
--chart-1: oklch(0.56 0.13 43.00);
|
||||
--chart-2: oklch(0.69 0.16 290.41);
|
||||
--chart-3: oklch(0.88 0.03 93.13);
|
||||
--chart-4: oklch(0.88 0.04 298.18);
|
||||
--chart-5: oklch(0.56 0.13 42.06);
|
||||
--sidebar: oklch(0.97 0.01 98.88);
|
||||
--sidebar-foreground: oklch(0.36 0.01 106.65);
|
||||
--sidebar-primary: oklch(0.62 0.14 39.04);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.92 0.01 92.99);
|
||||
--sidebar-accent-foreground: oklch(0.33 0 0);
|
||||
--sidebar-border: oklch(0.94 0 0);
|
||||
--sidebar-ring: oklch(0.77 0 0);
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.27 0.00 106.64);
|
||||
--foreground: oklch(0.81 0.01 93.01);
|
||||
--card: oklch(0.27 0.00 106.64);
|
||||
--card-foreground: oklch(0.98 0.01 95.10);
|
||||
--popover: oklch(0.31 0.00 106.60);
|
||||
--popover-foreground: oklch(0.92 0.00 106.48);
|
||||
--primary: oklch(0.67 0.13 38.76);
|
||||
--primary-foreground: oklch(1.00 0 0);
|
||||
--secondary: oklch(0.98 0.01 95.10);
|
||||
--secondary-foreground: oklch(0.31 0.00 106.60);
|
||||
--muted: oklch(0.22 0.00 106.71);
|
||||
--muted-foreground: oklch(0.77 0.02 99.07);
|
||||
--accent: oklch(0.21 0.01 95.42);
|
||||
--accent-foreground: oklch(0.97 0.01 98.88);
|
||||
--destructive: oklch(0.64 0.21 25.33);
|
||||
--destructive-foreground: oklch(1.00 0 0);
|
||||
--border: oklch(0.36 0.01 106.89);
|
||||
--input: oklch(0.43 0.01 100.22);
|
||||
--ring: oklch(0.59 0.17 253.06);
|
||||
--chart-1: oklch(0.56 0.13 43.00);
|
||||
--chart-2: oklch(0.69 0.16 290.41);
|
||||
--chart-3: oklch(0.21 0.01 95.42);
|
||||
--chart-4: oklch(0.31 0.05 289.32);
|
||||
--chart-5: oklch(0.56 0.13 42.06);
|
||||
--sidebar: oklch(0.24 0.00 67.71);
|
||||
--sidebar-foreground: oklch(0.81 0.01 93.01);
|
||||
--sidebar-primary: oklch(0.33 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.99 0 0);
|
||||
--sidebar-accent: oklch(0.17 0.00 106.62);
|
||||
--sidebar-accent-foreground: oklch(0.81 0.01 93.01);
|
||||
--sidebar-border: oklch(0.94 0 0);
|
||||
--sidebar-ring: oklch(0.77 0 0);
|
||||
--radius: 0.5rem;
|
||||
--shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10);
|
||||
--shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
--shadow-2xs: var(--shadow-2xs);
|
||||
--shadow-xs: var(--shadow-xs);
|
||||
--shadow-sm: var(--shadow-sm);
|
||||
--shadow: var(--shadow);
|
||||
--shadow-md: var(--shadow-md);
|
||||
--shadow-lg: var(--shadow-lg);
|
||||
--shadow-xl: var(--shadow-xl);
|
||||
--shadow-2xl: var(--shadow-2xl);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
{% macro dialog(
|
||||
id,
|
||||
trigger=None,
|
||||
title=None,
|
||||
description=None,
|
||||
footer=None,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
content_attrs={},
|
||||
content_header_attrs={},
|
||||
content_body_attrs={},
|
||||
content_footer_attrs={},
|
||||
open=false,
|
||||
close_button=true,
|
||||
close_on_overlay_click=true
|
||||
)
|
||||
%}
|
||||
{% if registerComponent('dialog') %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('dialog', (initialOpen = false, initialCloseOnOverlayClick = true) => ({
|
||||
id: null,
|
||||
open: false,
|
||||
closeOnOverlayClick: true,
|
||||
|
||||
init() {
|
||||
this.id = this.$el.id;
|
||||
this.open = initialOpen;
|
||||
this.closeOnOverlayClick = initialCloseOnOverlayClick;
|
||||
},
|
||||
|
||||
show() {
|
||||
if (!this.open) {
|
||||
this.open = true;
|
||||
this.$nextTick(() => this.$el.dispatchEvent(new CustomEvent('dialog:opened', { bubbles: true })));
|
||||
}
|
||||
},
|
||||
|
||||
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) {
|
||||
const action = detail.action || 'toggle';
|
||||
if (action === 'open') this.show();
|
||||
else if (action === 'close') this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
$main: {
|
||||
'@dialog:control.window'(e) {
|
||||
this.handleControl(e)
|
||||
},
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.show() },
|
||||
':aria-expanded'() { return this.open }
|
||||
},
|
||||
|
||||
$content: {
|
||||
':inert'() { return !this.open },
|
||||
'@click'(e) { e.target === this.$el && this.closeOnOverlayClick && this.hide() },
|
||||
'@keydown.escape.window'() { this.open && this.hide() },
|
||||
'x-cloak': ''
|
||||
}
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
<div
|
||||
id="{{ id }}"
|
||||
x-data="dialog({{ 'true' if open else 'false' }}, {{ 'true' if close_on_overlay_click else 'false' }})"
|
||||
x-bind="$main"
|
||||
class="dialog"
|
||||
{% for key, value in main_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if trigger %}
|
||||
<button
|
||||
{% if id %}aria-controls="{{ id }}-dialog"{% endif %}
|
||||
aria-expanded="false"
|
||||
aria-controls="{{ id }}-dialog"
|
||||
x-bind="$trigger"
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ trigger }}
|
||||
</button>
|
||||
{% endif %}
|
||||
<div
|
||||
role="dialog"
|
||||
id="{{ id }}-dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="{{ id }}-title"
|
||||
inert
|
||||
x-bind="$content"
|
||||
>
|
||||
<article
|
||||
{% for key, value in content_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if title or description %}
|
||||
<header
|
||||
{% for key, value in content_header_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<h2 id="{{ id }}-title">{{ title | safe }}</h2>
|
||||
<p>{{ description | safe }}</p>
|
||||
</header>
|
||||
{% endif %}
|
||||
{% if caller %}
|
||||
<section
|
||||
{% for key, value in content_body_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ caller() }}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if footer %}
|
||||
<footer
|
||||
{% for key, value in content_footer_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ footer | safe }}
|
||||
</footer>
|
||||
{% endif %}
|
||||
{% if close_button %}
|
||||
<button
|
||||
@click="hide()"
|
||||
aria-label="Close dialog"
|
||||
>
|
||||
{% lucide "x" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,121 @@
|
||||
{% macro dropdown(
|
||||
id=None,
|
||||
trigger=None,
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
menu_attrs=None
|
||||
)
|
||||
%}
|
||||
{% if registerComponent('dropdown') %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('dropdown', () => ({
|
||||
open: false,
|
||||
focusedIndex: -1,
|
||||
menuItems: [],
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
const menuElement = this.$el.querySelector('[role="menu"]');
|
||||
this.menuItems = Array.from(menuElement.querySelectorAll('[role=menuitem]'));
|
||||
});
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.handleClick() },
|
||||
'@keydown.down.prevent'() { this.open && this.nextItem() },
|
||||
':aria-expanded'() { return this.open },
|
||||
':aria-pressed'() { return this.open }
|
||||
},
|
||||
|
||||
$menu: {
|
||||
'@click'(e) { this.handleMenuItemClick(e) },
|
||||
'@keydown.escape.window'() { 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': ''
|
||||
},
|
||||
|
||||
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);
|
||||
});
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<div
|
||||
class="dropdown {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
x-data="dropdown"
|
||||
@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"
|
||||
aria-pressed="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 %}
|
||||
<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 }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ caller() }}
|
||||
</nav>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
{% macro select(
|
||||
id=None,
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
menu_attrs=None,
|
||||
value=None,
|
||||
input_name=None
|
||||
)
|
||||
%}
|
||||
{% if registerComponent('select') %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('select', (initialValue = null) => ({
|
||||
open: false,
|
||||
focusedIndex: -1,
|
||||
options: [],
|
||||
selectedLabel: initialValue,
|
||||
selectedValue: initialValue,
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
const listboxElement = this.$el.querySelector('[role="listbox"]');
|
||||
this.options = Array.from(listboxElement.querySelectorAll('[role=option]'));
|
||||
|
||||
this.selectOptionByValue(initialValue);
|
||||
});
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.handleClick() },
|
||||
'@keydown.down.prevent'() { this.open && this.nextOption() },
|
||||
':aria-expanded'() { return this.open },
|
||||
':aria-pressed'() { return this.open }
|
||||
},
|
||||
|
||||
$listbox: {
|
||||
'@click'(e) { this.handleOptionClick(e) },
|
||||
'@keydown.escape.window'() { this.handleEscape() },
|
||||
'@keydown.down.prevent'() { this.nextOption() },
|
||||
'@keydown.up.prevent'() { this.prevOption() },
|
||||
'@keydown.home.prevent'() { this.focusOption(0) },
|
||||
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
|
||||
selectOption(option) {
|
||||
if (option && !option.disabled) {
|
||||
this.options.forEach(opt => {
|
||||
opt.setAttribute('aria-selected', opt === option);
|
||||
});
|
||||
this.selectedLabel = option.innerHTML;
|
||||
this.selectedValue = option.getAttribute('data-value');
|
||||
option.blur();
|
||||
this.open = false;
|
||||
} else if (this.options.length > 0) {
|
||||
this.selectOption(this.options[0]);
|
||||
}
|
||||
},
|
||||
|
||||
selectOptionByValue(value) {
|
||||
console.log(value);
|
||||
const option = this.options.find(opt => opt.getAttribute('data-value') === value);
|
||||
this.selectOption(option);
|
||||
},
|
||||
|
||||
focusOption(index) {
|
||||
if (index >= 0 && index < this.options.length) {
|
||||
this.focusedIndex = index;
|
||||
this.options[index].focus();
|
||||
}
|
||||
},
|
||||
|
||||
nextOption() {
|
||||
this.focusOption((this.focusedIndex + 1) % this.options.length);
|
||||
},
|
||||
|
||||
prevOption() {
|
||||
this.focusOption(this.focusedIndex <= 0 ? this.options.length - 1 : this.focusedIndex - 1);
|
||||
},
|
||||
|
||||
handleClick() {
|
||||
this.open = !this.open;
|
||||
this.$nextTick(() => {
|
||||
if(this.open && this.options.length) this.focusOption(0);
|
||||
});
|
||||
},
|
||||
|
||||
handleEscape() {
|
||||
if (this.open) this.open = false;
|
||||
},
|
||||
|
||||
handleOptionClick(event) {
|
||||
const option = event.target.closest('[role=option]');
|
||||
this.selectOption(option);
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<div
|
||||
class="dropdown {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
x-data="select('{{ value if value else '' }}')"
|
||||
@click.away="open = false"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
x-bind="$trigger"
|
||||
class="btn-outline justify-between font-normal"
|
||||
{% if id %}
|
||||
id="{{ id }}-trigger"
|
||||
aria-controls="{{ id }}-menu"
|
||||
{% endif %}
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<span x-html="selectedLabel" class="flex items-center gap-x-2"></span>
|
||||
<span class="text-muted-foreground opacity-50">{% lucide "chevron-down" %}</span>
|
||||
</button>
|
||||
<div
|
||||
class="min-w-full w-fit"
|
||||
role="listbox"
|
||||
aria-orientation="vertical"
|
||||
aria-hidden="true"
|
||||
x-bind="$listbox"
|
||||
{% if id %}id="{{ id }}-menu"{% endif %}
|
||||
{% for key, value in menu_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ caller() }}
|
||||
</div>
|
||||
{% if input_name %}
|
||||
<input type="hidden" name="{{ input_name }}" x-model="selectedValue">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,121 @@
|
||||
{% macro dropdown(
|
||||
id=None,
|
||||
trigger=None,
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
menu_attrs=None
|
||||
)
|
||||
%}
|
||||
{% if registerComponent('dropdown') %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('dropdown', () => ({
|
||||
open: false,
|
||||
focusedIndex: -1,
|
||||
menuItems: [],
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
const menuElement = this.$el.querySelector('[role="menu"]');
|
||||
this.menuItems = Array.from(menuElement.querySelectorAll('[role=menuitem]'));
|
||||
});
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.handleClick() },
|
||||
'@keydown.down.prevent'() { this.open && this.nextItem() },
|
||||
':aria-expanded'() { return this.open },
|
||||
':aria-pressed'() { return this.open }
|
||||
},
|
||||
|
||||
$menu: {
|
||||
'@click'(e) { this.handleMenuItemClick(e) },
|
||||
'@keydown.escape.window'() { 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': ''
|
||||
},
|
||||
|
||||
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);
|
||||
});
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<div
|
||||
class="dropdown {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
x-data="dropdown"
|
||||
@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"
|
||||
aria-pressed="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 %}
|
||||
<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 }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ caller() }}
|
||||
</nav>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<section id="accordion" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Accordion</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="group grid w-full max-w-xl gap-4">
|
||||
<section
|
||||
x-init
|
||||
@click="
|
||||
const clickedSummary = $event.target.closest('summary');
|
||||
if (!clickedSummary) return;
|
||||
const clickedDetails = clickedSummary.closest('details');
|
||||
if (!clickedDetails) return;
|
||||
$el.querySelectorAll('details').forEach(detailsEl => {
|
||||
if (detailsEl !== clickedDetails) {
|
||||
detailsEl.removeAttribute('open');
|
||||
}
|
||||
});
|
||||
"
|
||||
>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full">
|
||||
<h2 class="focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50">
|
||||
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>
|
||||
<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">
|
||||
<h2 class="focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50">
|
||||
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>
|
||||
<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">
|
||||
<h2 class="focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50">
|
||||
Is it animated?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm">Yes. It's animated by default, but you can disable it if you prefer.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
<section>
|
||||
<details class="group border-b last:border-b-0">
|
||||
<summary class="w-full">
|
||||
<h2 class="focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50">
|
||||
What are the key considerations when implementing a comprehensive enterprise-level authentication system?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm">Implementing a robust enterprise authentication system requires careful consideration of multiple factors. This includes secure password hashing and storage, multi-factor authentication (MFA) implementation, session management, OAuth2 and SSO integration, regular security audits, rate limiting to prevent brute force attacks, and maintaining detailed audit logs. Additionally, you'll need to consider scalability, performance impact, and compliance with relevant data protection regulations such as GDPR or HIPAA.</p>
|
||||
</section>
|
||||
</details>
|
||||
<details class="group">
|
||||
<summary class="w-full">
|
||||
<h2 class="focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50">
|
||||
How does modern distributed system architecture handle eventual consistency and data synchronization across multiple regions?
|
||||
{% lucide "chevron-down", { "class": "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200 group-open:rotate-180" } %}
|
||||
</h2>
|
||||
</summary>
|
||||
<section>
|
||||
<p class="text-sm">Modern distributed systems employ various strategies to maintain data consistency across regions. This often involves using techniques like CRDT (Conflict-Free Replicated Data Types), vector clocks, and gossip protocols. Systems might implement event sourcing patterns, utilize message queues for asynchronous updates, and employ sophisticated conflict resolution strategies. Popular solutions like Amazon's DynamoDB and Google's Spanner demonstrate different approaches to solving these challenges, balancing between consistency, availability, and partition tolerance as described in the CAP theorem.</p>
|
||||
</section>
|
||||
</details>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,23 @@
|
||||
<section id="alert-dialog" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Alert Dialog</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn-primary" @click="open = false">Continue</button>
|
||||
{% endset %}
|
||||
{{ dialog(
|
||||
id="alert-dialog-demo",
|
||||
title="Are you absolutely sure?",
|
||||
description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
||||
trigger="Open dialog",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=false,
|
||||
close_on_overlay_click=false
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,63 @@
|
||||
<section id="alert" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Alert</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="grid max-w-xl items-start gap-4">
|
||||
<div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Success! Your changes have been saved</h2>
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "bookmark-check" %}
|
||||
<section>This is an alert with icon, title and description.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
<section>This one has a description only. No title. No icon.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "popcorn" %}
|
||||
<h2>Let's try one with icon and title.</h2>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "shield-alert" %}
|
||||
<h2>This is a very long alert title that demonstrates how the component handles extended text content and potentially wraps across multiple lines</h2>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "gift" %}
|
||||
<section>This is a very long alert description that demonstrates how the component handles extended text content and potentially wraps across multiple lines</section>
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>This is an extremely long alert title that spans multiple lines to demonstrate how the component handles very lengthy headings while maintaining readability and proper text wrapping behavior</h2>
|
||||
<section>This is an equally long description that contains detailed information about the alert. It shows how the component can accommodate extensive content while preserving proper spacing, alignment, and readability across different screen sizes and viewport widths. This helps ensure the user experience remains consistent regardless of the content length.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
</div>
|
||||
|
||||
<div class="alert-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>
|
||||
<p>Please verify your billing information and try again.</p>
|
||||
<ul>
|
||||
<li>Check your card details</li>
|
||||
<li>Ensure sufficient funds</li>
|
||||
<li>Verify billing address</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,44 @@
|
||||
<section id="badge" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Badge</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<span class="badge">Primary</span>
|
||||
<span class="badge-secondary">Secondary</span>
|
||||
<span class="badge-outline">Outline</span>
|
||||
<span class="badge-destructive">Destructive</span>
|
||||
<span class="badge-outline">
|
||||
{% lucide "check" %}
|
||||
Badge
|
||||
</span>
|
||||
<span class="badge-destructive">
|
||||
{% lucide "circle-alert" %}
|
||||
Alert
|
||||
</span>
|
||||
<span class="badge rounded-full min-w-5 px-1">8</span>
|
||||
<span class="badge-destructive rounded-full min-w-5 px-1">99</span>
|
||||
<span class="badge-outline rounded-full min-w-5 px-1 font-mono tabular-nums">20+</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<a href="#" class="badge">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-secondary">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-destructive">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
<a href="#" class="badge-outline">
|
||||
Link
|
||||
{% lucide "arrow-right" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,35 @@
|
||||
<section id="breadcrumb" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Breadcrumb</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<ol class="text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5">
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Home</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown(
|
||||
id="dropdown-menu",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4"}
|
||||
) %}
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
<button type="button" role="menuitem">Themes</button>
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
{% endcall %}
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<a href="#" class="hover:text-foreground transition-colors">Components</a>
|
||||
</li>
|
||||
<li>{% lucide "chevron-right", { "class": "size-3.5" } %}</li>
|
||||
<li class="inline-flex items-center gap-1.5">
|
||||
<span class="text-foreground font-normal">Breadcrumb</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,98 @@
|
||||
<section id="button" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Button</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-primary">Primary</button>
|
||||
<button class="btn-outline">Outline</button>
|
||||
<button class="btn-ghost">Ghost</button>
|
||||
<button class="btn-destructive">
|
||||
{% lucide "send" %}
|
||||
Danger
|
||||
</button>
|
||||
<button class="btn-secondary">Secondary</button>
|
||||
<button class="btn-link">Link</button>
|
||||
<button class="btn-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-sm-primary">Primary</button>
|
||||
<button class="btn-sm-outline">Outline</button>
|
||||
<button class="btn-sm-ghost">Ghost</button>
|
||||
<button class="btn-sm-destructive">Danger</button>
|
||||
<button class="btn-sm-secondary">Secondary</button>
|
||||
<button class="btn-sm-link">Link</button>
|
||||
<button class="btn-sm-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-sm-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-sm-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-lg-primary">Primary</button>
|
||||
<button class="btn-lg-outline">Outline</button>
|
||||
<button class="btn-lg-ghost">Ghost</button>
|
||||
<button class="btn-lg-destructive">
|
||||
{% lucide "send" %}
|
||||
Danger
|
||||
</button>
|
||||
<button class="btn-lg-secondary">Secondary</button>
|
||||
<button class="btn-lg-link">Link</button>
|
||||
<button class="btn-lg-outline">
|
||||
{% lucide "send" %}
|
||||
Send
|
||||
</button>
|
||||
<button class="btn-lg-outline">
|
||||
Learn more
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-lg-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 md:flex-row">
|
||||
<button class="btn-icon-primary">
|
||||
{% lucide "download" %}
|
||||
</button>
|
||||
<button class="btn-icon-secondary">
|
||||
{% lucide "upload" %}
|
||||
</button>
|
||||
<button class="btn-icon-outline">
|
||||
{% lucide "arrow-right" %}
|
||||
</button>
|
||||
<button class="btn-icon-ghost">
|
||||
{% lucide "ellipsis" %}
|
||||
</button>
|
||||
<button class="btn-icon-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
</button>
|
||||
<button class="btn-icon-outline" disabled>
|
||||
{% lucide "loader", { "class": "animate-spin" } %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,31 @@
|
||||
<section id="checkbox" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Checkbox</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
<label class="label gap-3">
|
||||
<input type="checkbox" class="input">
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
<div class="flex items-start gap-3">
|
||||
<input type="checkbox" id="checkbox-demo-label-and-description" class="input">
|
||||
<div class="grid gap-2">
|
||||
<label for="checkbox-demo-label-and-description" class="label">Accept terms and conditions</label>
|
||||
<p class="text-muted-foreground text-sm">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</div>
|
||||
<label class="label gap-3">
|
||||
<input type="checkbox" class="input" disabled>
|
||||
Enable notifications
|
||||
</label>
|
||||
<label class="flex items-start gap-3 border p-3 hover:bg-accent/50 rounded-lg has-[input[type='checkbox']:checked]:border-blue-600 has-[input[type='checkbox']:checked]:bg-blue-50 dark:has-[input[type='checkbox']:checked]:border-blue-900 dark:has-[input[type='checkbox']:checked]:bg-blue-950">
|
||||
<input type="checkbox" id="checkbox-demo-label-and-description" class="input checked:bg-blue-600 checked:border-blue-600 dark:checked:bg-blue-700 dark:checked:border-blue-700 checked:after:bg-white" checked>
|
||||
<div class="grid gap-2">
|
||||
<h2 class="text-sm leading-none font-medium">Accept terms and conditions</h2>
|
||||
<p class="text-muted-foreground text-sm">By clicking this checkbox, you agree to the terms and conditions.</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,71 @@
|
||||
<section id="dialog" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Dialog</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Cancel</button>
|
||||
<button class="btn" @click="open = false">Save changes</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="demo-dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label>Name</label>
|
||||
<input type="text" value="Pedro Duarte"/>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label>Username</label>
|
||||
<input type="email" value="@peduarte"/>
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-example",
|
||||
title="Scrollable Content",
|
||||
description="This is a dialog with scrollable content.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
content_body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
<button x-init @click="
|
||||
console.log('clicked');
|
||||
$dispatch('dialog:control', { targetId: 'dialog-example', action: 'open' })
|
||||
"
|
||||
type="button" class="btn-outline"
|
||||
>
|
||||
Trigger dialog:control
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,144 @@
|
||||
<section id="dropdown" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Dropdown</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% call dropdown(
|
||||
id="dropdown-menu",
|
||||
trigger="Open Menu",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
menu_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 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-semibold">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 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-semibold">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(
|
||||
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 src="https://avatar.vercel.sh/ronan_berder" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
{% endset %}
|
||||
{% call dropdown(
|
||||
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(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost"},
|
||||
menu_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>
|
||||
@@ -0,0 +1,106 @@
|
||||
<section id="form" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Form</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<form class="form grid w-full max-w-sm gap-6">
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-text">Username</label>
|
||||
<input type="text" id="demo-form-text" placeholder="hunvreus">
|
||||
<p class="text-muted-foreground text-sm">This is your public display name.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-select">Email</label>
|
||||
<select id="demo-form-select">
|
||||
<option value="bob@example.com">m@example.com</option>
|
||||
<option value="alice@example.com">m@google.com</option>
|
||||
<option value="john@example.com">m@support.com</option>
|
||||
</select>
|
||||
<p class="text-muted-foreground text-sm">You can manage email addresses in your email settings.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-text">Bio</label>
|
||||
<textarea id="demo-form-textarea" placeholder="I like to..." rows="3"></textarea>
|
||||
<p class="text-muted-foreground text-sm">You can @mention other users and organizations.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<label for="demo-form-radio">Notify me about...</label>
|
||||
<fieldset id="demo-form-radio" class="grid gap-3">
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="1">All new messages</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="2">Direct messages and mentions</label>
|
||||
<label class="font-normal"><input type="radio" name="demo-form-radio" value="3" >Nothing</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-start gap-3 rounded-md border p-4 shadow-xs">
|
||||
<input type="checkbox" id="demo-form-checkbox">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="demo-form-checkbox" class="leading-snug">Use different settings for my mobile devices</label>
|
||||
<p class="text-muted-foreground text-sm leading-snug">You can manage your mobile notifications in the mobile settings page.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<header>
|
||||
<label for="demo-form-checkboxes" class="text-base leading-normal">Sidebar</label>
|
||||
<p class="text-muted-foreground text-sm">Select the items you want to display in the sidebar.</p>
|
||||
</header>
|
||||
<fieldset id="demo-form-checkboxes" class="flex flex-col gap-2">
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="1" checked>
|
||||
Recents
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="2" checked>
|
||||
Home
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="3">
|
||||
Applications
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="4">
|
||||
Desktop
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="5">
|
||||
Download
|
||||
</label>
|
||||
<label class="font-normal leading-tight">
|
||||
<input type="checkbox" name="demo-form-checkboxes" value="6">
|
||||
Documents
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<label for="demo-form-date">Date of birth</label>
|
||||
<input type="date" id="demo-form-date">
|
||||
<p class="text-muted-foreground text-sm">Your date of birth is used to calculate your age.</p>
|
||||
</div>
|
||||
|
||||
<section class="grid gap-4">
|
||||
<h3 class="text-lg font-medium">Email Notifications</h3>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<label for="demo-form-switch" class="leading-normal">Marketing emails</label>
|
||||
<p class="text-muted-foreground text-sm">Receive emails about new products, features, and more.</p>
|
||||
</div>
|
||||
<input type="checkbox" id="demo-form-switch" role="switch">
|
||||
</div>
|
||||
<div class="gap-2 flex flex-row items-start justify-between rounded-lg border p-4 shadow-xs">
|
||||
<div class="flex flex-col gap-0.5 opacity-60">
|
||||
<label for="demo-form-switch" 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" disabled>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,24 @@
|
||||
<section id="input" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Input</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<input type="text" class="input" placeholder="Text">
|
||||
<input type="text" class="input" disabled placeholder="Disabled">
|
||||
<input type="text" class="input" aria-invalid="true" placeholder="Error">
|
||||
<input type="email" class="input" placeholder="Email">
|
||||
<input type="password" class="input" placeholder="Password">
|
||||
<input type="number" class="input" placeholder="Number">
|
||||
<input type="file" class="input">
|
||||
<input type="tel" class="input" placeholder="Tel">
|
||||
<input type="url" class="input" placeholder="URL">
|
||||
<input type="search" class="input" placeholder="Search">
|
||||
<input type="date" class="input">
|
||||
<input type="datetime-local" class="input">
|
||||
<input type="month" class="input">
|
||||
<input type="week" class="input">
|
||||
<input type="time" class="input">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,27 @@
|
||||
<section id="label" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Label</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<div class="grid w-full max-w-sm gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" id="label-demo-terms" class="input">
|
||||
<label for="label-demo-terms" class="label">Accept terms and conditions</label>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-text" class="label">Username</label>
|
||||
<input type="text" id="label-demo-text" class="input" placeholder="Username">
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-disabled" class="label">Disabled</label>
|
||||
<input type="text" id="label-demo-disabled" class="peer input" placeholder="Disabled" disabled>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="label-demo-textarea" class="label">Message</label>
|
||||
<textarea id="label-demo-textarea" class="textarea" placeholder="Message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,29 @@
|
||||
<section id="pagination" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Pagination</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<nav role="navigation" aria-label="pagination" class="mx-auto flex w-full justify-center">
|
||||
<ul class="flex flex-row items-center gap-1">
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">{% lucide "chevron-left" %} Previous</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost size-9">1</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-outline size-9">2</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost size-9">3</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-icon-ghost">{% lucide "ellipsis" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="btn-ghost">Next {% lucide "chevron-right" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,30 @@
|
||||
<section id="radio-group" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Radio Group</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<fieldset class="grid gap-3">
|
||||
<label class="label"><input type="radio" name="radio-demo" value="yes" class="input">Yes</label>
|
||||
<label class="label"><input type="radio" name="radio-demo" value="no" class="input">No</label>
|
||||
<label class="label"><input type="radio" name="radio-demo" value="maybe" class="input">Maybe</label>
|
||||
</fieldset>
|
||||
<fieldset class="grid gap-3 max-w-sm">
|
||||
<label class="label gap-3 items-start hover:bg-accent/50 rounded-lg border p-4 has-[input[type='radio']:checked]:border-green-600 has-[input[type='radio']:checked]:bg-green-50 dark:has-[input[type='radio']:checked]:border-green-900 dark:has-[input[type='radio']:checked]:bg-green-950">
|
||||
<input type="radio" name="radio-demo" value="no" class="input checked:border-green-600">
|
||||
<div class="grid gap-1 font-normal">
|
||||
<h2 class="font-medium">Starter Plan</h2>
|
||||
<p class="text-muted-foreground leading-snug">Perfect for small businesses getting started with our platform</p>
|
||||
</div>
|
||||
</label>
|
||||
<label class="label gap-3 items-start hover:bg-accent/50 rounded-lg border p-4 has-[input[type='radio']:checked]:border-green-600 has-[input[type='radio']:checked]:bg-green-50 dark:has-[input[type='radio']:checked]:border-green-900 dark:has-[input[type='radio']:checked]:bg-green-950">
|
||||
<input type="radio" name="radio-demo" value="no" class="input checked:border-green-600">
|
||||
<div class="grid gap-1 font-normal">
|
||||
<h2 class="font-medium">Pro Plan</h2>
|
||||
<p class="text-muted-foreground leading-snug">Advanced features for growing businesses with higher demands</p>
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,31 @@
|
||||
<section id="select" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Select</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<select class="select">
|
||||
<option>Orange</option>
|
||||
<option>Apple</option>
|
||||
<option>Strawberry</option>
|
||||
</select>
|
||||
|
||||
{% call select(
|
||||
value="banana",
|
||||
input_name="demo-food-select"
|
||||
) %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<button type="button" role="option" data-value="orange">🍊 Orange</button>
|
||||
<button type="button" role="option" data-value="apple">🍎 Apple</button>
|
||||
<button type="button" role="option" data-value="banana">🍌 Banana</button>
|
||||
</div>
|
||||
<div role="group" aria-labelledby="vegetable-options-label">
|
||||
<span id="vegetable-options-label" role="heading">Vegetables</span>
|
||||
<button type="button" role="option" data-value="carrot">🥕 Carrot</button>
|
||||
<button type="button" role="option" data-value="potato">🥔 Potato</button>
|
||||
<button type="button" role="option" data-value="broccoli">🥦 Broccoli</button>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,8 @@
|
||||
<section id="slider" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Slider</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<input type="range" class="input w-full" min="0" max="100" value="50">
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,24 @@
|
||||
<section id="switch" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Switch</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="inline-flex flex-col gap-y-6">
|
||||
<label class="label">
|
||||
<input type="checkbox" name="switch" role="switch" class="input">
|
||||
Airplane Mode
|
||||
</label>
|
||||
<label class="label">
|
||||
<input type="checkbox" name="switch" role="switch" class="input checked:bg-blue-500 dark:checked:bg-blue-600" checked>
|
||||
Bluetooth
|
||||
</label>
|
||||
<label class="label gap-6 leading-none border rounded-lg p-4 has-[input[type='checkbox']:checked]:border-blue-600">
|
||||
<div class="grid gap-1">
|
||||
<h2 class="font-medium text-sm">Share across devices</h2>
|
||||
<p class="text-muted-foreground text-sm">Focus is shared across devices, and turns off when you leave the app.</p>
|
||||
</div>
|
||||
<input type="checkbox" name="switch" role="switch" class="input checked:bg-blue-500 dark:checked:bg-blue-600">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,119 @@
|
||||
<section id="table" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Table</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
|
||||
<div class="relative w-full overflow-x-auto">
|
||||
<table class="table">
|
||||
<caption>List of users</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Role</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="font-medium flex items-center gap-x-2">
|
||||
<img src="https://avatar.vercel.sh/sarah_chen" alt="Avatar" class="w-5 h-5 rounded-full">
|
||||
Sarah Chen
|
||||
</span>
|
||||
</td>
|
||||
<td>sarah.chen@example.com</td>
|
||||
<td>Admin</td>
|
||||
<td>
|
||||
<a href="#" class="badge-outline">
|
||||
View profile
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="font-medium flex items-center gap-x-2">
|
||||
<img src="https://avatar.vercel.sh/marcus_rodriguez" alt="Avatar" class="w-5 h-5 rounded-full">
|
||||
Marcus Rodriguez
|
||||
</span>
|
||||
</td>
|
||||
<td>m.rodriguez@example.com</td>
|
||||
<td>Member</td>
|
||||
<td>
|
||||
<a href="#" class="badge-outline">
|
||||
View profile
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="font-medium flex items-center gap-x-2">
|
||||
<img src="https://avatar.vercel.sh/emma_thompson" alt="Avatar" class="w-5 h-5 rounded-full">
|
||||
Emma Thompson
|
||||
</span>
|
||||
</td>
|
||||
<td>emma.t@example.com</td>
|
||||
<td>Member</td>
|
||||
<td>
|
||||
<a href="#" class="badge-outline">
|
||||
View profile
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="font-medium flex items-center gap-x-2">
|
||||
<img src="https://avatar.vercel.sh/aiden_patel" alt="Avatar" class="w-5 h-5 rounded-full">
|
||||
Aiden Patel
|
||||
</span>
|
||||
</td>
|
||||
<td>a.patel@example.com</td>
|
||||
<td>Admin</td>
|
||||
<td>
|
||||
<a href="#" class="badge-outline">
|
||||
View profile
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="font-medium flex items-center gap-x-2">
|
||||
<img src="https://avatar.vercel.sh/lisa_kim" alt="Avatar" class="w-5 h-5 rounded-full">
|
||||
Lisa Kim
|
||||
</span>
|
||||
</td>
|
||||
<td>l.kim@example.com</td>
|
||||
<td>Guest</td>
|
||||
<td>
|
||||
<a href="#" class="badge-outline">
|
||||
View profile
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="font-medium flex items-center gap-x-2">
|
||||
<img src="https://avatar.vercel.sh/james_wilson" alt="Avatar" class="w-5 h-5 rounded-full">
|
||||
James Wilson
|
||||
</span>
|
||||
</td>
|
||||
<td>jwilson@example.com</td>
|
||||
<td>Member</td>
|
||||
<td>
|
||||
<a href="#" class="badge-outline">
|
||||
View profile
|
||||
{% lucide "arrow-up-right" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,24 @@
|
||||
<section id="textarea" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Textarea</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-10">
|
||||
<textarea class="textarea" placeholder="Type your message here"></textarea>
|
||||
<textarea class="textarea" placeholder="Type your message here" aria-invalid="true"></textarea>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-label" class="label">Label</label>
|
||||
<textarea id="textarea-demo-label" class="textarea" placeholder="Type your message here"></textarea>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-label-and-description" class="label">With label and description</label>
|
||||
<textarea id="textarea-demo-label-and-description" class="textarea" placeholder="Type your message here"></textarea>
|
||||
<p class="text-muted-foreground text-sm">Type your message and press enter to send.</p>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="textarea-demo-disabled" class="label">Disabled</label>
|
||||
<textarea id="textarea-demo-disabled" class="textarea" placeholder="Type your message here" disabled></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,21 @@
|
||||
<section id="tooltip" class="w-full rounded-lg border">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Tooltip</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<button type="button" class="btn-outline" data-tooltip="Top tooltip">
|
||||
Top
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Right tooltip" data-side="right">
|
||||
Right
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Bottom tooltip" data-side="bottom">
|
||||
Bottom
|
||||
</button>
|
||||
<button type="button" class="btn-outline" data-tooltip="Left tooltip" data-side="left">
|
||||
Left
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<!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>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>
|
||||
</head>
|
||||
<body>
|
||||
{% from "./_components/dialog.njk" import dialog %}
|
||||
{% from "./_components/dropdown.njk" import dropdown %}
|
||||
{% from "./_components/select.njk" import select %}
|
||||
<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">
|
||||
<span class="mr-auto">Basecoat</span>
|
||||
<select class="select h-8">
|
||||
<option value="light">Default</option>
|
||||
<option value="light">Other</option>
|
||||
</select>
|
||||
<button
|
||||
x-data
|
||||
@click="$store.theme.toggle()"
|
||||
aria-label="Toggle theme"
|
||||
data-tooltip="Toggle theme"
|
||||
data-side="bottom"
|
||||
data-align="end"
|
||||
type="button"
|
||||
class="btn-icon-outline size-8"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<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 flex-1 gap-4 p-4">
|
||||
{% include "./_includes/accordion.njk" %}
|
||||
{% include "./_includes/alert.njk" %}
|
||||
{% include "./_includes/alert-dialog.njk" %}
|
||||
{% include "./_includes/badge.njk" %}
|
||||
{% include "./_includes/breadcrumb.njk" %}
|
||||
{% include "./_includes/button.njk" %}
|
||||
{% include "./_includes/checkbox.njk" %}
|
||||
{% include "./_includes/dialog.njk" %}
|
||||
{% include "./_includes/dropdown.njk" %}
|
||||
{% include "./_includes/form.njk" %}
|
||||
{% include "./_includes/input.njk" %}
|
||||
{% include "./_includes/label.njk" %}
|
||||
{% include "./_includes/pagination.njk" %}
|
||||
{% include "./_includes/radio-group.njk" %}
|
||||
{% include "./_includes/select.njk" %}
|
||||
{% include "./_includes/slider.njk" %}
|
||||
{% include "./_includes/switch.njk" %}
|
||||
{% include "./_includes/table.njk" %}
|
||||
{% include "./_includes/textarea.njk" %}
|
||||
{% include "./_includes/tooltip.njk" %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+3629
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"css:dev": "tailwindcss -i ./dist/basecoat.tailwind.css -o ./dist/basecoat.css --watch",
|
||||
"css:build": "tailwindcss -i ./dist/basecoat.tailwind.css -o ./dist/basecoat.css --minify",
|
||||
"eleventy:serve": "eleventy --serve",
|
||||
"dev": "concurrently \"npm:css:dev\" \"npm:eleventy:serve\"",
|
||||
"build": "npm run css:build & eleventy"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"tailwindcss": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tw-animate-css": "^1.2.5"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user