mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-13 13:48:28 +00:00
Added sidebar
This commit is contained in:
+151
-36
@@ -1,60 +1,175 @@
|
||||
#!/usr/bin/env node
|
||||
// ^^^ Makes the script executable
|
||||
|
||||
import { Command } from 'commander';
|
||||
// We'll add fs-extra and inquirer later
|
||||
import inquirer from 'inquirer';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
let packageVersion = 'unknown';
|
||||
try {
|
||||
const cliScriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||
const packageJsonPath = path.resolve(cliScriptDir, '..', 'package.json');
|
||||
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8');
|
||||
packageVersion = JSON.parse(packageJsonContent).version;
|
||||
} catch (error) {
|
||||
console.warn('Could not read package version:', error.message);
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
templateEngine: null,
|
||||
templateDest: './components/basecoat',
|
||||
scriptDest: './static/js/basecoat'
|
||||
};
|
||||
let config = { ...DEFAULT_CONFIG };
|
||||
|
||||
async function getAvailableComponents() {
|
||||
try {
|
||||
const packageRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
|
||||
const jsSourceDir = path.join(packageRoot, 'src', 'js');
|
||||
const jsFiles = await fs.readdir(jsSourceDir);
|
||||
return jsFiles
|
||||
.filter(file => file.endsWith('.js'))
|
||||
.map(file => path.basename(file, '.js'));
|
||||
} catch (error) {
|
||||
console.error("Error reading component source directories:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureConfiguration() {
|
||||
console.log('\nChecking configuration...');
|
||||
|
||||
if (!config.templateEngine) {
|
||||
const engineChoice = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'engine',
|
||||
message: 'Which template engine are you using?',
|
||||
choices: ['nunjucks', 'jinja'],
|
||||
}
|
||||
]);
|
||||
config.templateEngine = engineChoice.engine;
|
||||
}
|
||||
|
||||
const pathChoices = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'templateDest',
|
||||
message: 'Where should template files be placed?',
|
||||
default: './',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'scriptDest',
|
||||
message: 'Where should script files be placed?',
|
||||
default: './',
|
||||
}
|
||||
]);
|
||||
config.templateDest = pathChoices.templateDest;
|
||||
config.scriptDest = pathChoices.scriptDest;
|
||||
|
||||
console.log(`Configuration set: Engine=${config.templateEngine}, Templates=${config.templateDest}, Scripts=${config.scriptDest}`);
|
||||
}
|
||||
|
||||
console.log('Basecoat CLI');
|
||||
|
||||
program
|
||||
.name('basecoat')
|
||||
.description('Add Basecoat components to your project')
|
||||
.version('0.1.0'); // TODO: Get version from package.json
|
||||
.version(packageVersion);
|
||||
|
||||
program
|
||||
.command('add')
|
||||
.description('Add one or more components to your project')
|
||||
.argument('[components...]', 'Names of components to add') // Optional arg for now
|
||||
.action(async (components) => {
|
||||
if (!components || components.length === 0) {
|
||||
console.log('Please specify which component(s) to add, or run add without args for a list (TODO).');
|
||||
// TODO: Implement interactive component selection if no args
|
||||
return;
|
||||
.argument('[components...]', 'Names of components to add')
|
||||
.action(async (componentsArg) => {
|
||||
let componentsToAdd = componentsArg;
|
||||
try {
|
||||
if (!componentsToAdd || componentsToAdd.length === 0) {
|
||||
const availableComponents = await getAvailableComponents();
|
||||
if (availableComponents.length === 0) {
|
||||
console.error('Error: Could not find any components.');
|
||||
return;
|
||||
}
|
||||
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'selectedComponents',
|
||||
message: 'Which component(s) would you like to add?',
|
||||
choices: availableComponents,
|
||||
validate: (input) => input.length > 0 ? true : 'Please select at least one component.'
|
||||
}
|
||||
]);
|
||||
|
||||
componentsToAdd = answers.selectedComponents;
|
||||
}
|
||||
|
||||
if (!componentsToAdd || componentsToAdd.length === 0) {
|
||||
console.log('No components selected. Exiting.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Attempting to add: ${componentsToAdd.join(', ')}`);
|
||||
|
||||
await ensureConfiguration();
|
||||
|
||||
for (const componentName of componentsToAdd) {
|
||||
await addComponent(componentName);
|
||||
}
|
||||
|
||||
console.log('\nComponent addition process finished.');
|
||||
} catch (error) {
|
||||
if (error.isTtyError || error.constructor.name === 'ExitPromptError') {
|
||||
console.log('\nOperation cancelled.');
|
||||
} else {
|
||||
console.error('\nAn unexpected error occurred:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Attempting to add: ${components.join(', ')}`);
|
||||
|
||||
for (const componentName of components) {
|
||||
await addComponent(componentName);
|
||||
}
|
||||
|
||||
console.log('\nComponent addition process finished.');
|
||||
});
|
||||
|
||||
// Placeholder function for adding a single component
|
||||
async function addComponent(componentName) {
|
||||
console.log(`\nProcessing component: ${componentName}`);
|
||||
console.log(`\nProcessing component: ${componentName}...`);
|
||||
|
||||
// 1. Check if config file exists (TODO)
|
||||
// 2. If not, run init command/ask questions (TODO)
|
||||
// 3. Read config (template engine, paths) (TODO)
|
||||
// 4. Determine source file paths based on component name and config (TODO)
|
||||
// 5. Check if source files exist in the package (TODO)
|
||||
// 6. Copy files to destination paths using fs-extra (TODO)
|
||||
// 1. Determine source paths (relative to package root)
|
||||
const cliScriptDir = path.dirname(new URL(import.meta.url).pathname); // Get directory of the current script file
|
||||
// Go UP from cli/, then DOWN into src/, then the specific folder
|
||||
const templateExt = config.templateEngine === 'jinja' ? '.html.jinja' : '.njk';
|
||||
const templateSource = path.resolve(cliScriptDir, '..', 'src', config.templateEngine, `${componentName}${templateExt}`);
|
||||
const scriptSource = path.resolve(cliScriptDir, '..', 'src', 'js', `${componentName}.js`);
|
||||
|
||||
console.log(` -> Placeholder: Logic to copy ${componentName} files would go here.`);
|
||||
// 2. Determine destination paths (relative to user's current working directory)
|
||||
const templateDestPath = path.resolve(process.cwd(), config.templateDest, `${componentName}${templateExt}`);
|
||||
const scriptDestPath = path.resolve(process.cwd(), config.scriptDest, `${componentName}.js`);
|
||||
|
||||
try {
|
||||
// 3. Check if source files exist
|
||||
const templateExists = await fs.pathExists(templateSource);
|
||||
const scriptExists = await fs.pathExists(scriptSource);
|
||||
|
||||
if (!templateExists || !scriptExists) {
|
||||
console.error(` Error: Source files for component '${componentName}' not found. Searched:`);
|
||||
if (!templateExists) console.error(` - ${templateSource}`);
|
||||
if (!scriptExists) console.error(` - ${scriptSource}`);
|
||||
return; // Skip this component
|
||||
}
|
||||
|
||||
// 4. Ensure destination directories exist
|
||||
await fs.ensureDir(path.dirname(templateDestPath));
|
||||
await fs.ensureDir(path.dirname(scriptDestPath));
|
||||
|
||||
// 5. Copy files
|
||||
await fs.copyFile(templateSource, templateDestPath);
|
||||
console.log(` -> Copied template to: ${templateDestPath}`);
|
||||
await fs.copyFile(scriptSource, scriptDestPath);
|
||||
console.log(` -> Copied script to: ${scriptDestPath}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(` Error processing component ${componentName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implement init command
|
||||
// program
|
||||
// .command('init')
|
||||
// .description('Initialize basecoat configuration')
|
||||
// .action(async () => {
|
||||
// console.log('Running init...');
|
||||
// // Ask config questions
|
||||
// // Save config file
|
||||
// });
|
||||
|
||||
program.parse(process.argv);
|
||||
+5
-2
@@ -1,3 +1,5 @@
|
||||
@source "../../src/css/basecoat.css";
|
||||
|
||||
@import url("https://fonts.googleapis.com/css2?family=Poppins&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Lora&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Fira+Code&display=swap");
|
||||
@@ -5,12 +7,13 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
|
||||
|
||||
@import "tailwindcss";
|
||||
@import "basecoat";
|
||||
@import "../../src/css/basecoat.css";
|
||||
@import "./custom.css";
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Inter';
|
||||
--font-mono: 'JetBrains Mono';
|
||||
|
||||
}
|
||||
|
||||
/* @import "./theme-orange.css"; */
|
||||
/* @import "./theme-orange.css"; */
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<script src="/assets/js/dropdown-menu.js" defer></script>
|
||||
<script src="/assets/js/popover.js" defer></script>
|
||||
<script src="/assets/js/select.js" defer></script>
|
||||
<script src="/assets/js/sidebar.js" defer></script>
|
||||
<script src="/assets/js/tabs.js" defer></script>
|
||||
<script src="/assets/js/toast.js" defer></script>
|
||||
<script>
|
||||
@@ -57,6 +58,7 @@
|
||||
window.basecoat.registerDropdownMenu(Alpine);
|
||||
window.basecoat.registerPopover(Alpine);
|
||||
window.basecoat.registerSelect(Alpine);
|
||||
window.basecoat.registerSidebar(Alpine);
|
||||
window.basecoat.registerTabs(Alpine);
|
||||
window.basecoat.registerToast(Alpine);
|
||||
});
|
||||
@@ -79,21 +81,84 @@
|
||||
{% if head %}{{ head | safe }}{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="sidebar"
|
||||
class="fixed inset-y-0 left-0 z-50 h-svh border-r bg-sidebar text-sidebar-foreground flex flex-col w-[var(--sidebar-width)] transition-transform ease-in-out peer aria-[hidden=true]:-translate-x-full"
|
||||
aria-hidden="false"
|
||||
:aria-hidden="open ? 'false' : 'true'"
|
||||
@sidebar:toggle.window="toggle()"
|
||||
x-data="{
|
||||
open: true,
|
||||
toggle() { this.open = !this.open }
|
||||
}"
|
||||
>
|
||||
{% include "partials/sidebar.njk" %}
|
||||
</div>
|
||||
|
||||
<main id="content" class="relative ml-(--sidebar-width) peer-aria-hidden:ml-0 transition-[margin-left] ease-in-out">
|
||||
{% from "sidebar.njk" import sidebar %}
|
||||
{% set footer %}
|
||||
<a href="/logout">
|
||||
<span class="icon" aria-hidden="true">🚪</span>
|
||||
<span>Logout</span>
|
||||
</a>
|
||||
{% endset %}
|
||||
|
||||
{% set header %}
|
||||
<a href="/" class="btn-ghost p-2 h-12 w-full justify-start">
|
||||
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Basecoat</span>
|
||||
<span class="truncate text-xs">v0.1.0</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex flex-col gap-2 px-2 text-sm">
|
||||
<form class="form relative">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
|
||||
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
|
||||
</form>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% set icon_info %}{% lucide "info" %}{% endset %}
|
||||
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
|
||||
{% set icon_layout_dashboard %}{% lucide "layout-dashboard" %}{% endset %}
|
||||
|
||||
{% set menu = [
|
||||
{ type: "group", label: "Getting started", items: [
|
||||
{ type: "item", label: "Introduction", url: "/", icon: icon_info, current: true },
|
||||
{ type: "item", label: "Installation", url: "/installation", icon: icon_square_terminal },
|
||||
{ type: "item", label: "Kitchen sink", url: "/kitchen-sink", icon: icon_layout_dashboard }
|
||||
]},
|
||||
{ type: 'group', label: 'Components', items: [
|
||||
{ type: "item", url: "/components/accordion", label: "Accordion" },
|
||||
{ type: "item", url: "/components/alert", label: "Alert" },
|
||||
{ type: "item", url: "/components/alert-dialog", label: "Alert Dialog" },
|
||||
{ type: "item", url: "/components/avatar", label: "Avatar" },
|
||||
{ type: "item", url: "/components/badge", label: "Badge" },
|
||||
{ type: "item", url: "/components/breadcrumb", label: "Breadcrumb" },
|
||||
{ type: "item", url: "/components/button", label: "Button" },
|
||||
{ type: "item", url: "/components/card", label: "Card" },
|
||||
{ type: "item", url: "/components/checkbox", label: "Checkbox" },
|
||||
{ type: "item", url: "/components/combobox", label: "Combobox" },
|
||||
{ type: "item", url: "/components/dialog", label: "Dialog" },
|
||||
{ type: "item", url: "/components/dropdown-menu", label: "Dropdown Menu" },
|
||||
{ type: "item", url: "/components/form", label: "Form" },
|
||||
{ type: "item", url: "/components/input", label: "Input" },
|
||||
{ type: "item", url: "/components/label", label: "Label" },
|
||||
{ type: "item", url: "/components/pagination", label: "Pagination" },
|
||||
{ type: "item", url: "/components/popover", label: "Popover" },
|
||||
{ type: "item", url: "/components/radio-group", label: "Radio Group" },
|
||||
{ type: "item", url: "/components/select", label: "Select" },
|
||||
{ type: "item", url: "/components/skeleton", label: "Skeleton" },
|
||||
{ type: "item", url: "/components/sidebar", label: "Sidebar" },
|
||||
{ type: "item", url: "/components/slider", label: "Slider" },
|
||||
{ type: "item", url: "/components/switch", label: "Switch" },
|
||||
{ type: "item", url: "/components/table", label: "Table" },
|
||||
{ type: "item", url: "/components/tabs", label: "Tabs" },
|
||||
{ type: "item", url: "/components/textarea", label: "Textarea" },
|
||||
{ type: "item", url: "/components/toast", label: "Toast" },
|
||||
{ type: "item", url: "/components/tooltip", label: "Tooltip" }
|
||||
]}
|
||||
] %}
|
||||
|
||||
{{ sidebar(
|
||||
label="Sidebar navigation",
|
||||
header=header,
|
||||
footer=footer,
|
||||
menu=menu
|
||||
) }}
|
||||
|
||||
<main id="content">
|
||||
<header class="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
||||
<div class="flex h-14 w-full items-center gap-2 px-4">
|
||||
<button
|
||||
@@ -103,7 +168,7 @@
|
||||
:data-tooltip="sidebarOpen ? 'Hide sidebar' : 'Show sidebar'"
|
||||
data-side="bottom"
|
||||
data-align="start"
|
||||
class="btn-icon-ghost size-8 mr-auto"
|
||||
class="btn-sm-icon-ghost mr-auto"
|
||||
x-init
|
||||
x-data="{ sidebarOpen: true }"
|
||||
@click="
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
{ "id": "radio-group", "name": "Radio Group" },
|
||||
{ "id": "select", "name": "Select" },
|
||||
{ "id": "skeleton", "name": "Skeleton" },
|
||||
{ "id": "sidebar", "name": "Sidebar" },
|
||||
{ "id": "slider", "name": "Slider" },
|
||||
{ "id": "switch", "name": "Switch" },
|
||||
{ "id": "table", "name": "Table" },
|
||||
|
||||
+344
-60
@@ -103,6 +103,7 @@
|
||||
--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);
|
||||
}
|
||||
}
|
||||
@@ -272,9 +273,6 @@
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
@@ -284,9 +282,6 @@
|
||||
.inset-x-0 {
|
||||
inset-inline: calc(var(--spacing) * 0);
|
||||
}
|
||||
.inset-y-0 {
|
||||
inset-block: calc(var(--spacing) * 0);
|
||||
}
|
||||
.top-0 {
|
||||
top: calc(var(--spacing) * 0);
|
||||
}
|
||||
@@ -302,9 +297,6 @@
|
||||
.right-3 {
|
||||
right: calc(var(--spacing) * 3);
|
||||
}
|
||||
.left-0 {
|
||||
left: calc(var(--spacing) * 0);
|
||||
}
|
||||
.left-2 {
|
||||
left: calc(var(--spacing) * 2);
|
||||
}
|
||||
@@ -314,9 +306,6 @@
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
.col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
@@ -365,9 +354,6 @@
|
||||
.mb-12 {
|
||||
margin-bottom: calc(var(--spacing) * 12);
|
||||
}
|
||||
.ml-\(--sidebar-width\) {
|
||||
margin-left: var(--sidebar-width);
|
||||
}
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -444,9 +430,6 @@
|
||||
.h-14 {
|
||||
height: calc(var(--spacing) * 14);
|
||||
}
|
||||
.h-svh {
|
||||
height: 100svh;
|
||||
}
|
||||
.max-h-64 {
|
||||
max-height: calc(var(--spacing) * 64);
|
||||
}
|
||||
@@ -492,9 +475,6 @@
|
||||
.w-\[200px\] {
|
||||
width: 200px;
|
||||
}
|
||||
.w-\[var\(--sidebar-width\)\] {
|
||||
width: var(--sidebar-width);
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -694,10 +674,6 @@
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
}
|
||||
.border-r {
|
||||
border-right-style: var(--tw-border-style);
|
||||
border-right-width: 1px;
|
||||
}
|
||||
.border-b {
|
||||
border-bottom-style: var(--tw-border-style);
|
||||
border-bottom-width: 1px;
|
||||
@@ -723,9 +699,6 @@
|
||||
.bg-muted {
|
||||
background-color: var(--color-muted);
|
||||
}
|
||||
.bg-sidebar {
|
||||
background-color: var(--color-sidebar);
|
||||
}
|
||||
.bg-sidebar-primary {
|
||||
background-color: var(--color-sidebar-primary);
|
||||
}
|
||||
@@ -875,9 +848,6 @@
|
||||
.text-muted-foreground {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
.text-sidebar-foreground {
|
||||
color: var(--color-sidebar-foreground);
|
||||
}
|
||||
.text-sidebar-foreground\/70 {
|
||||
color: var(--color-sidebar-foreground);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -935,11 +905,6 @@
|
||||
.filter {
|
||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||
}
|
||||
.transition-\[margin-left\] {
|
||||
transition-property: margin-left;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
.transition-\[width\,height\,padding\] {
|
||||
transition-property: width,height,padding;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
@@ -964,10 +929,6 @@
|
||||
--tw-duration: 200ms;
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
.ease-in-out {
|
||||
--tw-ease: var(--ease-in-out);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
}
|
||||
.outline-none {
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
@@ -986,11 +947,6 @@
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.peer-aria-hidden\:ml-0 {
|
||||
&:is(:where(.peer)[aria-hidden="true"] ~ *) {
|
||||
margin-left: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
.last\:border-b-0 {
|
||||
&:last-child {
|
||||
border-bottom-style: var(--tw-border-style);
|
||||
@@ -1202,12 +1158,6 @@
|
||||
background-color: var(--color-green-50);
|
||||
}
|
||||
}
|
||||
.aria-\[hidden\=true\]\:-translate-x-full {
|
||||
&[aria-hidden="true"] {
|
||||
--tw-translate-x: -100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
}
|
||||
.sm\:max-w-\[425px\] {
|
||||
@media (width >= 40rem) {
|
||||
max-width: 425px;
|
||||
@@ -1606,7 +1556,8 @@
|
||||
--scrollbar-width: 6px;
|
||||
--scrollbar-radius: 6px;
|
||||
--sidebar-width: 16rem;
|
||||
--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>');
|
||||
--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)" 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>');
|
||||
--chevron-down-icon-50: 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>');
|
||||
--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>');
|
||||
}
|
||||
.dark {
|
||||
@@ -1645,7 +1596,8 @@
|
||||
--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>');
|
||||
--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)" 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>');
|
||||
--chevron-down-icon-50: 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>');
|
||||
--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>');
|
||||
color-scheme: dark;
|
||||
}
|
||||
@@ -2274,7 +2226,7 @@
|
||||
background-color: var(--color-primary-foreground);
|
||||
--tw-content: '';
|
||||
content: var(--tw-content);
|
||||
mask-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWNoZWNrLWljb24gbHVjaWRlLWNoZWNrIj48cGF0aCBkPSJNMjAgNiA5IDE3bC01LTUiLz48L3N2Zz4=');
|
||||
mask-image: var(--check-icon);
|
||||
mask-size: 0.875rem;
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
@@ -3138,7 +3090,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
background-image: var(--chevron-down-icon);
|
||||
background-image: var(--chevron-down-icon-50);
|
||||
background-size: 1rem;
|
||||
background-position: center right 0.75rem;
|
||||
background-repeat: no-repeat;
|
||||
@@ -3778,6 +3730,338 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
inset-block: calc(var(--spacing) * 0);
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
width: var(--sidebar-width);
|
||||
flex-direction: column;
|
||||
background-color: var(--color-sidebar);
|
||||
color: var(--color-sidebar-foreground);
|
||||
transition-property: transform, translate, scale, rotate;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
--tw-ease: var(--ease-in-out);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
&:not([data-side]), &[data-side=left] {
|
||||
left: calc(var(--spacing) * 0);
|
||||
border-right-style: var(--tw-border-style);
|
||||
border-right-width: 1px;
|
||||
&[aria-hidden="true"] {
|
||||
--tw-translate-x: -100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
& + * {
|
||||
position: relative;
|
||||
margin-left: var(--sidebar-width);
|
||||
transition-property: margin;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
--tw-ease: var(--ease-in-out);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
}
|
||||
&[aria-hidden=true] + * {
|
||||
margin-left: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
&[data-side=right] {
|
||||
right: calc(var(--spacing) * 0);
|
||||
border-left-style: var(--tw-border-style);
|
||||
border-left-width: 1px;
|
||||
&[aria-hidden="true"] {
|
||||
--tw-translate-x: 100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
& + * {
|
||||
position: relative;
|
||||
margin-right: var(--sidebar-width);
|
||||
transition-property: margin;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
--tw-ease: var(--ease-in-out);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
}
|
||||
&[aria-hidden=true] + * {
|
||||
margin-right: calc(var(--spacing) * 0);
|
||||
}
|
||||
}
|
||||
> header, > footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
padding: calc(var(--spacing) * 2);
|
||||
}
|
||||
[role=separator] {
|
||||
margin-inline: calc(var(--spacing) * 2);
|
||||
width: auto;
|
||||
border-color: var(--color-sidebar-border);
|
||||
}
|
||||
> section {
|
||||
display: flex;
|
||||
min-height: calc(var(--spacing) * 0);
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
overflow-y: auto;
|
||||
> [role=group] {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: calc(var(--spacing) * 0);
|
||||
flex-direction: column;
|
||||
padding: calc(var(--spacing) * 2);
|
||||
}
|
||||
h3 {
|
||||
display: flex;
|
||||
height: calc(var(--spacing) * 8);
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
border-radius: var(--radius-md);
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--tw-leading, var(--text-xs--line-height));
|
||||
--tw-font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-sidebar-foreground);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
color: color-mix(in oklab, var(--color-sidebar-foreground) 70%, transparent);
|
||||
}
|
||||
--tw-ring-color: var(--color-sidebar-ring);
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
@media (forced-colors: active) {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
transition-property: margin,opacity;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
--tw-duration: 200ms;
|
||||
transition-duration: 200ms;
|
||||
--tw-ease: linear;
|
||||
transition-timing-function: linear;
|
||||
&:focus-visible {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
&>svg {
|
||||
width: calc(var(--spacing) * 4);
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
&>svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: calc(var(--spacing) * 0);
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing) * 1);
|
||||
li {
|
||||
position: relative;
|
||||
> a, > details > summary {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: calc(var(--spacing) * 2);
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
padding: calc(var(--spacing) * 2);
|
||||
text-align: left;
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
--tw-ring-color: var(--color-sidebar-ring);
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
@media (forced-colors: active) {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
transition-property: width,height,padding;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
&:is(:where(.group\/menu-item):has(*[data-sidebar="menu-action"]) *) {
|
||||
padding-right: calc(var(--spacing) * 8);
|
||||
}
|
||||
&:is(:where(.group)[data-collapsible="icon"] *) {
|
||||
width: calc(var(--spacing) * 8) !important;
|
||||
height: calc(var(--spacing) * 8) !important;
|
||||
}
|
||||
&:is(:where(.group)[data-collapsible="icon"] *) {
|
||||
padding: calc(var(--spacing) * 2) !important;
|
||||
}
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-sidebar-accent);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-sidebar-accent-foreground);
|
||||
}
|
||||
}
|
||||
&:focus-visible {
|
||||
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-sidebar-accent);
|
||||
}
|
||||
&:active {
|
||||
color: var(--color-sidebar-accent-foreground);
|
||||
}
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
}
|
||||
&[aria-disabled="true"] {
|
||||
pointer-events: none;
|
||||
}
|
||||
&[aria-disabled="true"] {
|
||||
opacity: 50%;
|
||||
}
|
||||
&[data-active="true"] {
|
||||
background-color: var(--color-sidebar-accent);
|
||||
}
|
||||
&[data-active="true"] {
|
||||
--tw-font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
&[data-active="true"] {
|
||||
color: var(--color-sidebar-accent-foreground);
|
||||
}
|
||||
&[data-state="open"] {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-sidebar-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
&[data-state="open"] {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-sidebar-accent-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
&>span:last-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
&>svg {
|
||||
width: calc(var(--spacing) * 4);
|
||||
height: calc(var(--spacing) * 4);
|
||||
}
|
||||
&>svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
&:not([data-variant]), &[data-variant=default] {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-sidebar-accent);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-sidebar-accent-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
&[data-variant=outline] {
|
||||
background-color: var(--color-background);
|
||||
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-border)));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-sidebar-accent);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
color: var(--color-sidebar-accent-foreground);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-accent)));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not([data-size]), &[data-size=default] {
|
||||
height: calc(var(--spacing) * 8);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
}
|
||||
&[data-size=sm] {
|
||||
height: calc(var(--spacing) * 7);
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--tw-leading, var(--text-xs--line-height));
|
||||
}
|
||||
&[data-size=lg] {
|
||||
height: calc(var(--spacing) * 12);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
&:is(:where(.group)[data-collapsible="icon"] *) {
|
||||
padding: calc(var(--spacing) * 0) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
> details {
|
||||
&:not([open]) {
|
||||
> summary {
|
||||
&::after {
|
||||
rotate: calc(90deg * -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
> summary {
|
||||
&::after {
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
width: calc(var(--spacing) * 3.5);
|
||||
height: calc(var(--spacing) * 3.5);
|
||||
background-color: var(--color-primary);
|
||||
transition-property: transform, translate, scale, rotate;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
--tw-ease: linear;
|
||||
transition-timing-function: linear;
|
||||
--tw-content: '';
|
||||
content: var(--tw-content);
|
||||
mask-image: var(--chevron-down-icon);
|
||||
mask-size: 1rem;
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
&::details-content {
|
||||
padding-inline: calc(var(--spacing) * 3.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: calc(var(--spacing) * 0);
|
||||
--tw-translate-x: 1px;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing) * 1);
|
||||
border-left-style: var(--tw-border-style);
|
||||
border-left-width: 1px;
|
||||
border-color: var(--color-sidebar-border);
|
||||
padding-inline: calc(var(--spacing) * 2.5);
|
||||
padding-block: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
> h2 {
|
||||
margin-top: calc(var(--spacing) * 16);
|
||||
@@ -4087,15 +4371,15 @@
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-ease {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-content {
|
||||
syntax: "*";
|
||||
initial-value: "";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-ease {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-scale-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -4167,8 +4451,8 @@
|
||||
--tw-drop-shadow-alpha: 100%;
|
||||
--tw-drop-shadow-size: initial;
|
||||
--tw-duration: initial;
|
||||
--tw-ease: initial;
|
||||
--tw-content: "";
|
||||
--tw-ease: initial;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-scale-z: 1;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: layouts/page.njk
|
||||
title: Sidebar
|
||||
description: A responsive table component.
|
||||
---
|
||||
|
||||
{% from "sidebar.njk" import sidebar %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ toc:
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "jinja") }}
|
||||
</div>
|
||||
|
||||
<h2 id="install-theming"><a href="#install-theming">Theming</a></h2>
|
||||
|
||||
|
||||
+112
-4
@@ -38,7 +38,8 @@
|
||||
--scrollbar-width: 6px;
|
||||
--scrollbar-radius: 6px;
|
||||
--sidebar-width: 16rem;
|
||||
--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 */
|
||||
--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)" 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 */
|
||||
--chevron-down-icon-50: 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 */
|
||||
}
|
||||
|
||||
@@ -78,7 +79,8 @@
|
||||
--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 */
|
||||
--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)" 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 */
|
||||
--chevron-down-icon-50: 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 */
|
||||
color-scheme: dark;
|
||||
}
|
||||
@@ -404,6 +406,7 @@
|
||||
|
||||
> header {
|
||||
@apply @container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6;
|
||||
/* TODO: CLEAN has-data-[slot=card-action] */
|
||||
|
||||
h2 {
|
||||
@apply leading-none font-semibold;
|
||||
@@ -429,7 +432,7 @@
|
||||
|
||||
&: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;
|
||||
@apply mask-[image:var(--check-icon)] mask-size-[0.875rem] mask-no-repeat mask-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -739,7 +742,7 @@
|
||||
.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;
|
||||
@apply bg-[image:var(--chevron-down-icon)] bg-no-repeat bg-position-[center_right_0.75rem] bg-size-[1rem];
|
||||
@apply bg-[image:var(--chevron-down-icon-50)] bg-no-repeat bg-position-[center_right_0.75rem] bg-size-[1rem];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,4 +909,109 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
@apply bg-sidebar text-sidebar-foreground flex flex-col w-(--sidebar-width) fixed inset-y-0 transition-transform ease-in-out z-50;
|
||||
/* h-svh */
|
||||
|
||||
&:not([data-side]),
|
||||
&[data-side=left] {
|
||||
@apply left-0 border-r aria-[hidden=true]:-translate-x-full;
|
||||
|
||||
& + * {
|
||||
@apply relative ml-(--sidebar-width) transition-[margin] ease-in-out;
|
||||
}
|
||||
&[aria-hidden=true] + * {
|
||||
@apply ml-0;
|
||||
}
|
||||
}
|
||||
&[data-side=right] {
|
||||
@apply right-0 border-l aria-[hidden=true]:translate-x-full;
|
||||
|
||||
& + * {
|
||||
@apply relative mr-(--sidebar-width) transition-[margin] ease-in-out;
|
||||
}
|
||||
&[aria-hidden=true] + * {
|
||||
@apply mr-0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
> header,
|
||||
> footer {
|
||||
@apply flex flex-col gap-2 p-2;
|
||||
}
|
||||
[role=separator] {
|
||||
@apply border-sidebar-border mx-2 w-auto;
|
||||
}
|
||||
> section {
|
||||
@apply flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto;
|
||||
|
||||
> [role=group] {
|
||||
@apply relative flex w-full min-w-0 flex-col p-2;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0;
|
||||
}
|
||||
|
||||
/* GROUP CONTENT: w-full text-sm */
|
||||
ul {
|
||||
@apply flex w-full min-w-0 flex-col gap-1;
|
||||
|
||||
li {
|
||||
/* group/menu-item */
|
||||
@apply relative;
|
||||
|
||||
> a,
|
||||
> details > summary {
|
||||
/* peer/menu-button */
|
||||
@apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0;
|
||||
|
||||
&:not([data-variant]),
|
||||
&[data-variant=default] {
|
||||
@apply hover:bg-sidebar-accent hover:text-sidebar-accent-foreground;
|
||||
}
|
||||
&[data-variant=outline] {
|
||||
@apply bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))];
|
||||
}
|
||||
&:not([data-size]),
|
||||
&[data-size=default] {
|
||||
@apply h-8 text-sm;
|
||||
}
|
||||
&[data-size=sm] {
|
||||
@apply h-7 text-xs;
|
||||
}
|
||||
&[data-size=lg] {
|
||||
@apply h-12 text-sm group-data-[collapsible=icon]:p-0!;
|
||||
}
|
||||
}
|
||||
|
||||
> details {
|
||||
&:not([open]) {
|
||||
> summary {
|
||||
&::after {
|
||||
@apply -rotate-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
> summary {
|
||||
&::after {
|
||||
@apply content-[''] block size-3.5 bg-primary ml-auto transition-transform ease-linear;
|
||||
@apply mask-[image:var(--chevron-down-icon)] mask-size-[1rem] mask-no-repeat mask-center;
|
||||
}
|
||||
}
|
||||
&::details-content {
|
||||
@apply px-3.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply border-sidebar-border flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5 w-full;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
{{ caller() }}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if footer %}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
{% endfor %}
|
||||
>
|
||||
<nav role="menu">
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
{{ caller() if caller }}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
{{ caller() if caller }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
{{ caller() if caller }}
|
||||
</div>
|
||||
</div>
|
||||
{% if name is defined %}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
window.basecoat = window.basecoat || {};
|
||||
window.basecoat.registerSidebar= function(Alpine) {
|
||||
if (Alpine.components && Alpine.components.sidebar) return;
|
||||
|
||||
Alpine.data('sidebar', (initialOpen = true) => ({
|
||||
id: null,
|
||||
open: true,
|
||||
|
||||
init() {
|
||||
this.id = this.$el.id;
|
||||
this.open = initialOpen;
|
||||
},
|
||||
|
||||
$main: {
|
||||
'@sidebar:open.window'(e) { if (!this.id || (e.detail && e.detail.id === this.id)) this.open = true },
|
||||
'@sidebar:close.window'(e) { if (!this.id || (e.detail && e.detail.id === this.id)) this.open = false },
|
||||
'@sidebar:toggle.window'(e) {
|
||||
if (!this.id || (e.detail && e.detail.id === this.id)) {
|
||||
this.open = !this.open;
|
||||
console.log('toggle', e);
|
||||
}
|
||||
},
|
||||
':aria-hidden'() { return !this.open },
|
||||
':inert'() { return !this.open },
|
||||
},
|
||||
}));
|
||||
};
|
||||
@@ -84,7 +84,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
{{ caller() }}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if footer %}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
{% endfor %}
|
||||
>
|
||||
<nav role="menu">
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
{{ caller() if caller }}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
{{ caller() if caller }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
{{ caller() if caller }}
|
||||
</div>
|
||||
</div>
|
||||
{% if name is defined %}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
{% macro render_menu_items(items, level=0, parent_id="") %}
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
{% set item_id = parent_id ~ loop.index %}
|
||||
{% if item.type == 'item' %}
|
||||
<li>
|
||||
<a href="{{ item.url }}" {{ 'aria-current="page"' if item.current }}>
|
||||
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
||||
<span>{{ item.label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% elif item.type == 'submenu' %}
|
||||
<li>
|
||||
<details {{ 'open' if item.open }} id="submenu-{{ item_id }}">
|
||||
<summary aria-controls="submenu-{{ item_id }}-content">
|
||||
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
||||
{{ item.label }}
|
||||
</summary>
|
||||
<div id="submenu-{{ item_id }}-content">
|
||||
{{ render_menu_items(item.items, level + 1, item_id + '-') }}
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro sidebar(
|
||||
id=None,
|
||||
label="Sidebar navigation",
|
||||
open=true,
|
||||
side=None,
|
||||
header=None,
|
||||
footer=None,
|
||||
menu=None,
|
||||
main_attrs={},
|
||||
header_attrs={},
|
||||
content_attrs={},
|
||||
footer_attrs={},
|
||||
content_wrapper_attrs=None
|
||||
) %}
|
||||
<nav
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
class="sidebar peer {{ main_attrs.class }}"
|
||||
aria-label="{{ label }}"
|
||||
aria-hidden="{{ 'false' if open else 'true' }}"
|
||||
data-side="{{ side if side else 'left' }}"
|
||||
{{ 'inert' if not open }}
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
x-data="sidebar({{ 'true' if open else 'false' }})"
|
||||
x-bind="$main"
|
||||
>
|
||||
{% if header %}
|
||||
<header
|
||||
{% for key, value in header_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ header | safe }}
|
||||
</header>
|
||||
{% endif %}
|
||||
<hr role="separator"/>
|
||||
|
||||
<section
|
||||
{% for key, value in content_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if menu %}
|
||||
{% for group_or_item in menu %}
|
||||
{% set group_id = loop.index %}
|
||||
{% if group_or_item.type == 'group' %}
|
||||
<div role="group" aria-labelledby="nav-group-{{ group_id }}">
|
||||
<h3 id="nav-group-{{ group_id }}">{{ group_or_item.label }}</h3>
|
||||
{{ render_menu_items(group_or_item.items, 0, group_id + '-') }}
|
||||
</div>
|
||||
{% elif group_or_item.type == 'separator' %}
|
||||
<hr role="separator"/>
|
||||
{% elif group_or_item.type == 'item' or group_or_item.type == 'submenu' %}
|
||||
{{ render_menu_items([group_or_item], 0, 'top-' + group_id + '-') }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{{ caller() if caller }}
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% if footer %}
|
||||
<footer
|
||||
{% for key, value in footer_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ footer | safe }}
|
||||
</footer>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endmacro %}
|
||||
Reference in New Issue
Block a user