mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
205 lines
7.0 KiB
TypeScript
205 lines
7.0 KiB
TypeScript
// The theme switcher's settings, described once for both the panel markup and
|
|
// the demo script that applies them.
|
|
//
|
|
// The defaults come from the shipped switcher (@tabler/core/js/src/theme-config)
|
|
// so the panel can never disagree with the script about what "default" means.
|
|
// Each key maps to a `data-bs-<key>` attribute on <html>, written only when the
|
|
// value differs from the default.
|
|
import { themeDefaults, type ThemeKey } from '@tabler/core/js/src/theme-config'
|
|
import { site } from './site'
|
|
import { ucFirst } from './string-format'
|
|
|
|
export { themeDefaults }
|
|
export type { ThemeKey }
|
|
|
|
export interface ThemeOption {
|
|
value: string
|
|
label: string
|
|
}
|
|
|
|
/**
|
|
* A preset is one tile that sets several settings at once, the way AppStack's
|
|
* "color scheme" picks the page theme and the sidebar theme together. The
|
|
* switcher script knows nothing about presets: the panel expands one into
|
|
* the keys it stands for, and reads the tile back from those keys.
|
|
*/
|
|
export interface ThemePreset extends ThemeOption {
|
|
values: Partial<Record<ThemeKey, string>>
|
|
}
|
|
|
|
export interface ThemeSetting {
|
|
/** input name; a real switcher key, or the preset group's own name */
|
|
key: string
|
|
/** fieldset legend */
|
|
legend: string
|
|
/** help text under the legend */
|
|
hint: string
|
|
/**
|
|
* 'radio' renders chips, 'color' renders color swatches, 'font' renders "Aa"
|
|
* tiles in each font, 'tile' renders a thumbnail per value, 'preset' renders
|
|
* thumbnails that set several keys at once.
|
|
*/
|
|
control: 'radio' | 'color' | 'font' | 'tile' | 'preset'
|
|
options: ThemeOption[] | ThemePreset[]
|
|
/**
|
|
* Only meaningful while the sidebar is on screen; the panel hides these
|
|
* settings when the navigation is horizontal, the way Dashkit hides its
|
|
* sidebar sizing group.
|
|
*/
|
|
sidebarOnly?: boolean
|
|
/** Only meaningful with the top navbar; hidden while the navigation is vertical. */
|
|
navbarOnly?: boolean
|
|
}
|
|
|
|
export interface ThemeSection {
|
|
title: string
|
|
settings: ThemeSetting[]
|
|
}
|
|
|
|
/** Turns a list of raw values into options labelled by their capitalized value. */
|
|
function autoOptions(values: readonly string[]): ThemeOption[] {
|
|
return values.map((value) => ({ value, label: ucFirst(value) }))
|
|
}
|
|
|
|
export const themeSections: ThemeSection[] = [
|
|
{
|
|
title: 'Appearance',
|
|
settings: [
|
|
{
|
|
key: 'scheme',
|
|
legend: 'Color scheme',
|
|
hint: 'The page and its navigation, in one pick.',
|
|
control: 'preset',
|
|
options: [
|
|
{ value: 'auto', label: 'Auto', values: { 'theme': 'auto', 'navbar-theme': 'default' } },
|
|
{ value: 'light', label: 'Light', values: { 'theme': 'light', 'navbar-theme': 'default' } },
|
|
{ value: 'dark', label: 'Dark', values: { 'theme': 'dark', 'navbar-theme': 'default' } },
|
|
{ value: 'dark-nav', label: 'Dark nav', values: { 'theme': 'light', 'navbar-theme': 'dark' } },
|
|
{ value: 'colored', label: 'Colored', values: { 'theme': 'light', 'navbar-theme': 'primary' } },
|
|
],
|
|
},
|
|
{
|
|
key: 'theme-primary',
|
|
legend: 'Accent color',
|
|
hint: 'The accent color used across the app.',
|
|
control: 'color',
|
|
options: autoOptions([...site.themeColors, 'inverted']),
|
|
},
|
|
{
|
|
key: 'theme-font',
|
|
legend: 'Font family',
|
|
hint: 'The typeface used across the app.',
|
|
control: 'font',
|
|
options: autoOptions(site.themeFonts),
|
|
},
|
|
{
|
|
key: 'theme-base',
|
|
legend: 'Theme base',
|
|
hint: 'The gray palette behind every surface.',
|
|
control: 'tile',
|
|
options: autoOptions(site.themeBases),
|
|
},
|
|
{
|
|
key: 'theme-radius',
|
|
legend: 'Corner radius',
|
|
hint: 'How round corners are drawn.',
|
|
control: 'tile',
|
|
options: site.themeRadiuses.map((value) => ({ value, label: value })),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: 'Layout',
|
|
settings: [
|
|
{
|
|
key: 'navbar-position',
|
|
legend: 'Navigation',
|
|
hint: 'Where the main menu lives.',
|
|
control: 'tile',
|
|
options: [
|
|
{ value: 'horizontal', label: 'Navbar' },
|
|
{ value: 'vertical', label: 'Sidebar' },
|
|
],
|
|
},
|
|
{
|
|
key: 'layout',
|
|
legend: 'Container width',
|
|
hint: 'How wide the page content runs.',
|
|
control: 'tile',
|
|
options: autoOptions(['default', 'fluid', 'boxed']),
|
|
},
|
|
{
|
|
key: 'navbar',
|
|
legend: 'Navbar behavior',
|
|
hint: 'Keep the navbar visible while scrolling.',
|
|
control: 'tile',
|
|
navbarOnly: true,
|
|
options: [
|
|
{ value: 'default', label: 'Default' },
|
|
{ value: 'sticky', label: 'Sticky' },
|
|
],
|
|
},
|
|
{
|
|
key: 'navbar-style',
|
|
legend: 'Navigation style',
|
|
hint: 'Attach the navigation to the edges, or let it float.',
|
|
control: 'tile',
|
|
options: [
|
|
{ value: 'default', label: 'Attached' },
|
|
{ value: 'floating', label: 'Floating' },
|
|
],
|
|
},
|
|
{
|
|
key: 'offcanvas-style',
|
|
legend: 'Offcanvas style',
|
|
hint: 'Attach side panels to the edges, or let them float.',
|
|
control: 'tile',
|
|
options: [
|
|
{ value: 'default', label: 'Attached' },
|
|
{ value: 'floating', label: 'Floating' },
|
|
],
|
|
},
|
|
{
|
|
key: 'sidebar',
|
|
legend: 'Sidebar',
|
|
hint: 'How the sidebar behaves.',
|
|
control: 'tile',
|
|
sidebarOnly: true,
|
|
options: [
|
|
{ value: 'default', label: 'Expanded' },
|
|
{ value: 'folded', label: 'Folded' },
|
|
{ value: 'folded-hover', label: 'On hover' },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
]
|
|
|
|
/** Every setting, in panel order. */
|
|
export const themeSettings: ThemeSetting[] = themeSections.flatMap((section) => section.settings)
|
|
|
|
/** The preset groups, keyed by input name: tile value -> the keys and values it sets. */
|
|
export const themePresets: Record<string, Record<string, Partial<Record<ThemeKey, string>>>> = Object.fromEntries(themeSettings.filter((setting) => setting.control === 'preset').map((setting) => [setting.key, Object.fromEntries((setting.options as ThemePreset[]).map((option) => [option.value, option.values]))]))
|
|
|
|
/** Input names of the settings the panel hides while the navigation is horizontal. */
|
|
export const sidebarOnlyKeys: string[] = themeSettings.filter((setting) => setting.sidebarOnly).map((setting) => setting.key)
|
|
|
|
/** Input names of the settings the panel hides while the navigation is vertical. */
|
|
export const navbarOnlyKeys: string[] = themeSettings.filter((setting) => setting.navbarOnly).map((setting) => setting.key)
|
|
|
|
/**
|
|
* Builds the `data-bs-*` attributes a page renders on <html> to express its own
|
|
* layout. Defaults are omitted, matching what the switcher writes at runtime.
|
|
*/
|
|
export function themeDataAttributes(values: Partial<Record<ThemeKey, string | undefined>>): Record<string, string> {
|
|
const attributes: Record<string, string> = {}
|
|
|
|
for (const [key, value] of Object.entries(values)) {
|
|
if (value !== undefined && value !== themeDefaults[key as ThemeKey]) {
|
|
attributes[`data-bs-${key}`] = value
|
|
}
|
|
}
|
|
|
|
return attributes
|
|
}
|