Files
tabler/shared/layouts/BaseLayout.astro
T
codecalm 6481499ccd Add folded sidebar with flyout menus, pin toggle and light default
- navbar-folded / navbar-folded-hover with flyout submenus and persisted pin toggle
- sidebar refresh: light by default, 16rem wide, rounded inset pills, zero-jump fold
- demo pages, Theme Settings section, docs and btn-action usage rule
2026-08-20 22:27:53 +02:00

216 lines
7.7 KiB
Plaintext

---
import ThemeSettings from '@shared/components/demo/ThemeSettings.astro'
import { site } from '@shared/lib/site'
import PageModals from '@shared/components/PageModals.astro'
import PageScripts from '@shared/components/PageScripts.astro'
import libs from '@tabler/core/libs.json'
interface Props {
title?: string | undefined
/** Third-party libs from libs.json, e.g. ['apexcharts', 'jsvectormap'] */
pageLibs?: string[] | undefined
/** Extra body classes, e.g. "layout-boxed" / "layout-fluid" */
bodyClass?: string | undefined
/** dir="rtl" + RTL variants of the tabler/plugin stylesheets */
rtl?: boolean | undefined
/** Relative asset base: '.' for root pages, '..' one level deep */
base?: string
/** <meta name="description"> — falls back to the site description */
description?: string | undefined
}
const { title, pageLibs = [], bodyClass, rtl = false, base = '.', description } = Astro.props
// Development build: unminified assets, dev favicon.
const environment = 'development'
const min = environment === 'development' ? '' : '.min'
// RTL swaps tabler/plugin stylesheets for their .rtl.css variants (demo stays LTR)
const rtlSuffix = rtl ? '.rtl' : ''
type Lib = { npm?: string; js?: string[]; css?: string[]; head?: boolean }
const pageLibEntries = Object.entries(libs as Record<string, Lib>).filter(([name]) => pageLibs.includes(name))
const googleMapsKey = site.googleMapsDevKey
// External URLs (http/https) are emitted verbatim (with the maps-key placeholder
// replaced); local files as ./dist/libs/{npm}/{file}.
const libHref = (lib: Lib, file: string) => (/^https?:\/\//.test(file) ? file.replace('GOOGLE_MAPS_KEY', googleMapsKey) : `${base}/dist/libs/${lib.npm}/${file}`)
const libCssFiles = pageLibEntries.flatMap(([, lib]) => (lib.css ?? []).map((file) => libHref(lib, file)))
const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean(lib.head) === head).flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)))
---
<!doctype html>
<html lang="en" dir={rtl ? 'rtl' : undefined}>
<head>
<!-- BEGIN META -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>{title ? `${title} - ` : ''}{site.title} - {site.descriptionShort}</title>
<meta name="description" content={description ?? site.descriptionShort} />
<link rel="icon" href={`${base}/favicon-dev.ico`} type="image/x-icon" />
<link rel="shortcut icon" href={`${base}/favicon-dev.ico`} type="image/x-icon" />
<!-- END META -->
{
libCssFiles.length > 0 && (
<Fragment>
<Fragment set:html={'<!-- BEGIN PAGE LEVEL STYLES -->'} />
{libCssFiles.map((href) => (
<link href={href} rel="stylesheet" />
))}
<Fragment set:html={'<!-- END PAGE LEVEL STYLES -->'} />
</Fragment>
)
}
<!-- BEGIN GLOBAL MANDATORY STYLES -->
<link href={`${base}/dist/css/tabler${rtlSuffix}${min}.css`} rel="stylesheet" />
<!-- END GLOBAL MANDATORY STYLES -->
<!-- BEGIN PLUGINS STYLES -->
{site.cssPlugins.map((plugin) => <link href={`${base}/dist/css/tabler-${plugin}${rtlSuffix}${min}.css`} rel="stylesheet" />)}
<!-- END PLUGINS STYLES -->
<!-- BEGIN DEMO STYLES -->
<link href={`${base}/preview/css/demo${min}.css`} rel="stylesheet" />
<!-- END DEMO STYLES -->
{
libJsFiles(true).length > 0 && (
<Fragment>
<Fragment set:html={'<!-- BEGIN PAGE LIBRARIES -->'} />
{libJsFiles(true).map((src) => (
<script is:inline src={src} defer />
))}
<Fragment set:html={'<!-- END PAGE LIBRARIES -->'} />
</Fragment>
)
}
<!-- BEGIN CUSTOM FONT -->
{/* is:inline: keep the tag verbatim — Astro's scoped-style pipeline would add data-astro-cid-* attributes across the page */}
<style is:inline>
@import url('https://rsms.me/inter/inter.css');
</style>
<!-- END CUSTOM FONT -->
</head>
<body class={bodyClass}>
<a href="#content" class="visually-hidden-focusable skip-link">Skip to main content</a>
<!-- BEGIN GLOBAL THEME SCRIPT -->
<script is:inline src={`${base}/dist/js/tabler-theme${min}.js`}></script>
<!-- END GLOBAL THEME SCRIPT -->
<slot />
<PageModals />
<ThemeSettings />
{
libJsFiles(false).length > 0 && (
<Fragment>
<Fragment set:html={'<!-- BEGIN PAGE LIBRARIES -->'} />
{libJsFiles(false).map((src) => (
<script is:inline src={src} defer />
))}
<Fragment set:html={'<!-- END PAGE LIBRARIES -->'} />
</Fragment>
)
}
<!-- BEGIN GLOBAL MANDATORY SCRIPTS -->
<script is:inline src={`${base}/dist/js/tabler${min}.js`} defer></script>
<!-- END GLOBAL MANDATORY SCRIPTS -->
<!-- BEGIN DEMO SCRIPTS -->
<script is:inline src={`${base}/preview/js/demo${min}.js`} defer></script>
<!-- END DEMO SCRIPTS -->
<PageScripts />
<!-- BEGIN PAGE SCRIPTS -->
<script is:inline>
/*
This script handles the theme settings offcanvas functionality
It saves the selected settings to localStorage and applies them to the document
It also updates the URL with the selected settings as query parameters
It also has a reset button to clear all settings and revert to default
This is just a demo script to show how to implement theme settings. You can modify it as needed.
*/
document.addEventListener('DOMContentLoaded', function () {
var themeConfig = {
'theme': 'light',
'theme-base': 'gray',
'theme-font': 'sans-serif',
'theme-primary': 'blue',
'theme-radius': '1',
'sidebar': 'default',
}
var url = new URL(window.location)
var form = document.getElementById('offcanvas-settings')
var resetButton = document.getElementById('reset-changes')
var checkItems = function () {
for (var key in themeConfig) {
var value = window.localStorage['tabler-' + key] || themeConfig[key]
if (!!value) {
var radios = form.querySelectorAll(`[name="${key}"]`)
if (!!radios) {
radios.forEach((radio) => {
radio.checked = radio.value === value
})
}
}
}
}
form.addEventListener('change', function (event) {
var target = event.target,
name = target.name,
value = target.value
for (var key in themeConfig) {
if (name === key) {
var applied = value
if (key === 'theme' && value === 'auto') {
applied = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
if (applied !== themeConfig[key]) {
document.documentElement.setAttribute('data-bs-' + key, applied)
} else {
document.documentElement.removeAttribute('data-bs-' + key)
}
window.localStorage.setItem('tabler-' + key, value)
url.searchParams.set(key, value)
}
}
window.history.pushState({}, '', url)
})
resetButton.addEventListener('click', function () {
for (var key in themeConfig) {
document.documentElement.removeAttribute('data-bs-' + key)
window.localStorage.removeItem('tabler-' + key)
url.searchParams.delete(key)
}
checkItems()
window.history.pushState({}, '', url)
})
checkItems()
})
</script>
<!-- END PAGE SCRIPTS -->
</body>
</html>