feat(theme,interfaces): Introduce a global theming system and unify menu/tab interfaces; migrate components to use themeDefaultStyles and update APIs accordingly

This commit is contained in:
2025-12-29 01:20:24 +00:00
parent 9175799ec6
commit e38d3cd42a
78 changed files with 1413 additions and 616 deletions

View File

@@ -0,0 +1,275 @@
import { html, css, cssManager } from '@design.estate/dees-element';
export const demoFunc = () => html`
<style>
.demo-container {
display: flex;
flex-direction: column;
gap: 32px;
padding: 48px;
background: ${cssManager.bdTheme('#f8f9fa', '#0a0a0a')};
min-height: 100vh;
}
.section {
background: ${cssManager.bdTheme('#ffffff', '#18181b')};
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 8px;
padding: 24px;
}
.section-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.section-description {
font-size: 14px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
margin-bottom: 24px;
}
.token-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
}
.token-item {
background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
border-radius: 6px;
padding: 16px;
}
.token-name {
font-family: 'Intel One Mono', monospace;
font-size: 12px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
margin-bottom: 8px;
}
.token-value {
font-size: 14px;
font-weight: 500;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.spacing-demo {
display: flex;
align-items: flex-end;
gap: 8px;
flex-wrap: wrap;
}
.spacing-box {
background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
border-radius: 4px;
}
.radius-demo {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.radius-box {
width: 60px;
height: 60px;
background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 11px;
font-weight: 500;
}
.shadow-demo {
display: flex;
gap: 24px;
flex-wrap: wrap;
}
.shadow-box {
width: 100px;
height: 100px;
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 500;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.height-demo {
display: flex;
gap: 16px;
align-items: flex-end;
flex-wrap: wrap;
}
.height-box {
background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
width: 120px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: 500;
}
</style>
<div class="demo-container">
<dees-theme>
<div class="section">
<div class="section-title">Spacing Scale</div>
<div class="section-description">
CSS variables: --dees-spacing-xs through --dees-spacing-3xl
</div>
<div class="spacing-demo">
<div>
<div class="spacing-box" style="width: var(--dees-spacing-xs); height: var(--dees-spacing-xs);"></div>
<div class="token-name">xs (4px)</div>
</div>
<div>
<div class="spacing-box" style="width: var(--dees-spacing-sm); height: var(--dees-spacing-sm);"></div>
<div class="token-name">sm (8px)</div>
</div>
<div>
<div class="spacing-box" style="width: var(--dees-spacing-md); height: var(--dees-spacing-md);"></div>
<div class="token-name">md (12px)</div>
</div>
<div>
<div class="spacing-box" style="width: var(--dees-spacing-lg); height: var(--dees-spacing-lg);"></div>
<div class="token-name">lg (16px)</div>
</div>
<div>
<div class="spacing-box" style="width: var(--dees-spacing-xl); height: var(--dees-spacing-xl);"></div>
<div class="token-name">xl (24px)</div>
</div>
<div>
<div class="spacing-box" style="width: var(--dees-spacing-2xl); height: var(--dees-spacing-2xl);"></div>
<div class="token-name">2xl (32px)</div>
</div>
<div>
<div class="spacing-box" style="width: var(--dees-spacing-3xl); height: var(--dees-spacing-3xl);"></div>
<div class="token-name">3xl (48px)</div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">Border Radius Scale</div>
<div class="section-description">
CSS variables: --dees-radius-xs through --dees-radius-full
</div>
<div class="radius-demo">
<div>
<div class="radius-box" style="border-radius: var(--dees-radius-xs);">xs</div>
<div class="token-name">2px</div>
</div>
<div>
<div class="radius-box" style="border-radius: var(--dees-radius-sm);">sm</div>
<div class="token-name">4px</div>
</div>
<div>
<div class="radius-box" style="border-radius: var(--dees-radius-md);">md</div>
<div class="token-name">6px</div>
</div>
<div>
<div class="radius-box" style="border-radius: var(--dees-radius-lg);">lg</div>
<div class="token-name">8px</div>
</div>
<div>
<div class="radius-box" style="border-radius: var(--dees-radius-xl);">xl</div>
<div class="token-name">12px</div>
</div>
<div>
<div class="radius-box" style="border-radius: var(--dees-radius-full);">full</div>
<div class="token-name">999px</div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">Shadow Elevation Scale</div>
<div class="section-description">
CSS variables: --dees-shadow-xs through --dees-shadow-lg
</div>
<div class="shadow-demo">
<div>
<div class="shadow-box" style="box-shadow: var(--dees-shadow-xs);">xs</div>
<div class="token-name">minimal</div>
</div>
<div>
<div class="shadow-box" style="box-shadow: var(--dees-shadow-sm);">sm</div>
<div class="token-name">subtle</div>
</div>
<div>
<div class="shadow-box" style="box-shadow: var(--dees-shadow-md);">md</div>
<div class="token-name">medium</div>
</div>
<div>
<div class="shadow-box" style="box-shadow: var(--dees-shadow-lg);">lg</div>
<div class="token-name">prominent</div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">Control Height Scale</div>
<div class="section-description">
CSS variables: --dees-control-height-sm through --dees-control-height-xl
</div>
<div class="height-demo">
<div>
<div class="height-box" style="height: var(--dees-control-height-sm);">sm</div>
<div class="token-name">32px</div>
</div>
<div>
<div class="height-box" style="height: var(--dees-control-height-md);">md</div>
<div class="token-name">36px</div>
</div>
<div>
<div class="height-box" style="height: var(--dees-control-height-lg);">lg</div>
<div class="token-name">40px</div>
</div>
<div>
<div class="height-box" style="height: var(--dees-control-height-xl);">xl</div>
<div class="token-name">48px</div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">Transition Durations</div>
<div class="section-description">
CSS variables: --dees-transition-fast through --dees-transition-slower
</div>
<div class="token-grid">
<div class="token-item">
<div class="token-name">--dees-transition-fast</div>
<div class="token-value">0.1s</div>
</div>
<div class="token-item">
<div class="token-name">--dees-transition-default</div>
<div class="token-value">0.15s</div>
</div>
<div class="token-item">
<div class="token-name">--dees-transition-slow</div>
<div class="token-value">0.2s</div>
</div>
<div class="token-item">
<div class="token-name">--dees-transition-slower</div>
<div class="token-value">0.3s</div>
</div>
</div>
</div>
</dees-theme>
</div>
`;

View File

@@ -0,0 +1,224 @@
import {
DeesElement,
type TemplateResult,
property,
customElement,
html,
css,
cssManager,
} from '@design.estate/dees-element';
import {
type ITheme,
type IThemeColors,
type IThemeSpacing,
type IThemeRadius,
type IThemeShadows,
type IThemeTransitions,
type IThemeControlHeights,
themeDefaults,
themeDefaultStyles,
} from '../00theme.js';
import { demoFunc } from './dees-theme.demo.js';
/**
* A theme provider component that wraps children and provides CSS custom properties.
* Can be used at the app root or around specific sections to customize theming.
*
* Usage:
* ```html
* <dees-theme>
* <my-app></my-app>
* </dees-theme>
* ```
*
* With custom overrides:
* ```html
* <dees-theme .customSpacing=${{ lg: '20px' }}>
* <my-section></my-section>
* </dees-theme>
* ```
*/
@customElement('dees-theme')
export class DeesTheme extends DeesElement {
public static demo = demoFunc;
// ============================================
// Properties for theme overrides
// ============================================
@property({ type: Object })
accessor customSpacing: Partial<IThemeSpacing> | null = null;
@property({ type: Object })
accessor customRadius: Partial<IThemeRadius> | null = null;
@property({ type: Object })
accessor customShadows: Partial<IThemeShadows> | null = null;
@property({ type: Object })
accessor customTransitions: Partial<IThemeTransitions> | null = null;
@property({ type: Object })
accessor customControlHeights: Partial<IThemeControlHeights> | null = null;
// ============================================
// Styles
// ============================================
public static styles = [
themeDefaultStyles,
cssManager.defaultStyles,
css`
:host {
display: contents;
}
`,
];
// ============================================
// Render
// ============================================
public render(): TemplateResult {
return html`
<style>
${this.generateCustomStyles()}
</style>
<slot></slot>
`;
}
// ============================================
// Private Methods
// ============================================
private generateCustomStyles(): string {
const styles: string[] = [':host {'];
// Custom spacing
if (this.customSpacing) {
for (const [key, value] of Object.entries(this.customSpacing)) {
if (value) {
styles.push(` --dees-spacing-${key}: ${value};`);
}
}
}
// Custom radius
if (this.customRadius) {
for (const [key, value] of Object.entries(this.customRadius)) {
if (value) {
styles.push(` --dees-radius-${key}: ${value};`);
}
}
}
// Custom shadows
if (this.customShadows) {
for (const [key, value] of Object.entries(this.customShadows)) {
if (value) {
styles.push(` --dees-shadow-${key}: ${value};`);
}
}
}
// Custom transitions
if (this.customTransitions) {
for (const [key, value] of Object.entries(this.customTransitions)) {
if (value) {
const cssKey = key === 'default' ? 'default' : key;
styles.push(` --dees-transition-${cssKey}: ${value};`);
}
}
}
// Custom control heights
if (this.customControlHeights) {
for (const [key, value] of Object.entries(this.customControlHeights)) {
if (value) {
styles.push(` --dees-control-height-${key}: ${value};`);
}
}
}
styles.push('}');
return styles.join('\n');
}
// ============================================
// Public API Methods
// ============================================
/**
* Set a spacing value dynamically
*/
public setSpacing(key: keyof IThemeSpacing, value: string): void {
this.customSpacing = { ...this.customSpacing, [key]: value };
}
/**
* Set a radius value dynamically
*/
public setRadius(key: keyof IThemeRadius, value: string): void {
this.customRadius = { ...this.customRadius, [key]: value };
}
/**
* Set a shadow value dynamically
*/
public setShadow(key: keyof IThemeShadows, value: string): void {
this.customShadows = { ...this.customShadows, [key]: value };
}
/**
* Set a transition value dynamically
*/
public setTransition(key: keyof IThemeTransitions, value: string): void {
this.customTransitions = { ...this.customTransitions, [key]: value };
}
/**
* Set a control height value dynamically
*/
public setControlHeight(key: keyof IThemeControlHeights, value: string): void {
this.customControlHeights = { ...this.customControlHeights, [key]: value };
}
/**
* Get the current theme configuration (defaults + overrides)
*/
public getTheme(): ITheme {
return {
colors: themeDefaults.colors,
spacing: { ...themeDefaults.spacing, ...this.customSpacing },
radius: { ...themeDefaults.radius, ...this.customRadius },
shadows: { ...themeDefaults.shadows, ...this.customShadows },
transitions: { ...themeDefaults.transitions, ...this.customTransitions },
controlHeights: { ...themeDefaults.controlHeights, ...this.customControlHeights },
};
}
/**
* Reset all custom overrides to defaults
*/
public resetToDefaults(): void {
this.customSpacing = null;
this.customRadius = null;
this.customShadows = null;
this.customTransitions = null;
this.customControlHeights = null;
}
/**
* Apply a complete theme object
*/
public applyTheme(theme: Partial<ITheme>): void {
if (theme.spacing) this.customSpacing = theme.spacing;
if (theme.radius) this.customRadius = theme.radius;
if (theme.shadows) this.customShadows = theme.shadows;
if (theme.transitions) this.customTransitions = theme.transitions;
if (theme.controlHeights) this.customControlHeights = theme.controlHeights;
}
}

View File

@@ -0,0 +1 @@
export * from './dees-theme.js';