feat(terminal): add dynamic bright/dark theming for terminal components and terminal preview

This commit is contained in:
2025-12-31 14:01:42 +00:00
parent 62de004350
commit d0bd4027bb
7 changed files with 109 additions and 33 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -1,5 +1,15 @@
# Changelog # Changelog
## 2025-12-31 - 3.21.0 - feat(terminal)
add dynamic bright/dark theming for terminal components and terminal preview
- Add bright/dark theme PNG assets under .playwright-mcp for previews.
- Replace hardcoded terminal background/colors with cssManager.bdTheme in workspace terminal and preview styles.
- Introduce getTerminalTheme helper to compute xterm theme for bright/dark modes.
- Subscribe to themeManager.themeObservable and apply updates to xterm (terminal.options.theme) so terminals update live on theme change.
- Remove hardcoded background property/CSS var and unused background accessor from workspace terminal.
- Ensure proper cleanup: unsubscribe theme subscriptions and dispose terminals in disconnectedCallback.
## 2025-12-31 - 3.20.1 - fix(dees-workspace) ## 2025-12-31 - 3.20.1 - fix(dees-workspace)
fix demo wrapper and workspace layout; always render terminal preview fix demo wrapper and workspace layout; always render terminal preview

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-catalog', name: '@design.estate/dees-catalog',
version: '3.20.1', version: '3.21.0',
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
} }

View File

@@ -417,7 +417,6 @@ export class DeesSimpleAppDash extends DeesElement {
terminal.style.opacity = '0'; terminal.style.opacity = '0';
terminal.style.transform = 'translateY(8px) scale(0.99)'; terminal.style.transform = 'translateY(8px) scale(0.99)';
terminal.style.transition = 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)'; terminal.style.transition = 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)';
terminal.background = 'hsl(220 13% 8%)';
terminal.style.boxShadow = '0 25px 50px -12px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(255 255 255 / 0.05)'; terminal.style.boxShadow = '0 25px 50px -12px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(255 255 255 / 0.05)';
terminal.style.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`; terminal.style.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`;
terminal.style.maxHeight = `calc(${maincontainer.clientHeight}px - 24px)`; terminal.style.maxHeight = `calc(${maincontainer.clientHeight}px - 24px)`;

View File

@@ -55,6 +55,7 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
private fitAddon: FitAddon | null = null; private fitAddon: FitAddon | null = null;
private lastLineCount: number = 0; private lastLineCount: number = 0;
private resizeObserver: ResizeObserver | null = null; private resizeObserver: ResizeObserver | null = null;
private terminalThemeSubscription: any = null;
public static styles = [ public static styles = [
themeDefaultStyles, themeDefaultStyles,
@@ -69,8 +70,8 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
height: 100%; height: 100%;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
background: #000000; background: ${cssManager.bdTheme('#ffffff', '#000000')};
border: 1px solid hsl(0 0% 20%); border: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -80,20 +81,20 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 8px 12px; padding: 8px 12px;
background: hsl(0 0% 10%); background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 10%)')};
font-size: 12px; font-size: 12px;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace; font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
color: hsl(0 0% 60%); color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
border-bottom: 1px solid hsl(0 0% 20%); border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
flex-shrink: 0; flex-shrink: 0;
} }
.terminal-header-icon { .terminal-header-icon {
color: hsl(0 0% 50%); color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')};
} }
.terminal-header-command { .terminal-header-command {
color: hsl(0 0% 80%); color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
font-weight: 500; font-weight: 500;
} }
@@ -148,8 +149,8 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
} }
.xterm .composition-view { .xterm .composition-view {
background: #000000; background: ${cssManager.bdTheme('#ffffff', '#000000')};
color: #fff; color: ${cssManager.bdTheme('#333333', '#ffffff')};
display: none; display: none;
position: absolute; position: absolute;
white-space: nowrap; white-space: nowrap;
@@ -161,7 +162,7 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
} }
.xterm .xterm-viewport { .xterm .xterm-viewport {
background-color: #000000; background-color: ${cssManager.bdTheme('#ffffff', '#000000')};
overflow-y: scroll; overflow-y: scroll;
cursor: default; cursor: default;
position: absolute; position: absolute;
@@ -243,16 +244,16 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
} }
.xterm .xterm-viewport::-webkit-scrollbar-track { .xterm .xterm-viewport::-webkit-scrollbar-track {
background: hsl(0 0% 8%); background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 8%)')};
} }
.xterm .xterm-viewport::-webkit-scrollbar-thumb { .xterm .xterm-viewport::-webkit-scrollbar-thumb {
background: hsl(0 0% 25%); background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 25%)')};
border-radius: 4px; border-radius: 4px;
} }
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
background: hsl(0 0% 35%); background: ${cssManager.bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 35%)')};
} }
`, `,
]; ];
@@ -271,6 +272,27 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
`; `;
} }
/**
* Get terminal theme colors based on bright/dark mode
*/
private getTerminalTheme(isBright: boolean) {
return isBright
? {
background: '#ffffff',
foreground: '#333333',
cursor: '#333333',
cursorAccent: '#ffffff',
selectionBackground: 'rgba(0, 0, 0, 0.2)',
}
: {
background: '#000000',
foreground: '#cccccc',
cursor: '#cccccc',
cursorAccent: '#000000',
selectionBackground: 'rgba(255, 255, 255, 0.2)',
};
}
public async firstUpdated( public async firstUpdated(
_changedProperties: Map<string | number | symbol, unknown> _changedProperties: Map<string | number | symbol, unknown>
): Promise<void> { ): Promise<void> {
@@ -279,6 +301,10 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
const container = this.shadowRoot?.getElementById('xterm-container'); const container = this.shadowRoot?.getElementById('xterm-container');
if (!container) return; if (!container) return;
// Get current theme from domtools
const domtoolsInstance = await this.domtoolsPromise;
const isBright = domtoolsInstance.themeManager.goBrightBoolean;
// Create xterm terminal in read-only mode // Create xterm terminal in read-only mode
this.terminal = new Terminal({ this.terminal = new Terminal({
convertEol: true, convertEol: true,
@@ -286,13 +312,17 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
disableStdin: true, disableStdin: true,
fontSize: 12, fontSize: 12,
fontFamily: "'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace", fontFamily: "'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace",
theme: { theme: this.getTerminalTheme(isBright),
background: '#000000',
foreground: '#cccccc',
},
scrollback: 1000, scrollback: 1000,
}); });
// Subscribe to theme changes
this.terminalThemeSubscription = domtoolsInstance.themeManager.themeObservable.subscribe((goBright: boolean) => {
if (this.terminal) {
this.terminal.options.theme = this.getTerminalTheme(goBright);
}
});
this.fitAddon = new FitAddon(); this.fitAddon = new FitAddon();
this.terminal.loadAddon(this.fitAddon); this.terminal.loadAddon(this.fitAddon);
this.terminal.open(container); this.terminal.open(container);
@@ -334,6 +364,10 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
this.resizeObserver.disconnect(); this.resizeObserver.disconnect();
this.resizeObserver = null; this.resizeObserver = null;
} }
if (this.terminalThemeSubscription) {
this.terminalThemeSubscription.unsubscribe();
this.terminalThemeSubscription = null;
}
if (this.terminal) { if (this.terminal) {
this.terminal.dispose(); this.terminal.dispose();
this.terminal = null; this.terminal = null;

View File

@@ -47,9 +47,6 @@ export class DeesWorkspaceTerminal extends DeesElement {
@property() @property()
accessor environmentVariables: { [key: string]: string } = {}; accessor environmentVariables: { [key: string]: string } = {};
@property()
accessor background: string = '#000000';
/** /**
* Promise that resolves when the environment is ready. * Promise that resolves when the environment is ready.
* @deprecated Use executionEnvironment directly * @deprecated Use executionEnvironment directly
@@ -57,6 +54,9 @@ export class DeesWorkspaceTerminal extends DeesElement {
private environmentDeferred = new domtools.plugins.smartpromise.Deferred<IExecutionEnvironment>(); private environmentDeferred = new domtools.plugins.smartpromise.Deferred<IExecutionEnvironment>();
public environmentPromise = this.environmentDeferred.promise; public environmentPromise = this.environmentDeferred.promise;
// Theme subscription for dynamic theme updates
private terminalThemeSubscription: any = null;
constructor() { constructor() {
super(); super();
this.resizeObserver = new ResizeObserver((entries) => { this.resizeObserver = new ResizeObserver((entries) => {
@@ -72,10 +72,9 @@ export class DeesWorkspaceTerminal extends DeesElement {
themeDefaultStyles, themeDefaultStyles,
cssManager.defaultStyles, cssManager.defaultStyles,
css` css`
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
:host { :host {
padding: 20px; padding: 20px;
background: var(--dees-terminal-background, #000000); background: ${cssManager.bdTheme('#ffffff', '#000000')};
position: absolute; position: absolute;
height: 100%; height: 100%;
width: 100%; width: 100%;
@@ -170,8 +169,8 @@ export class DeesWorkspaceTerminal extends DeesElement {
.xterm .composition-view { .xterm .composition-view {
/* TODO: Composition position got messed up somewhere */ /* TODO: Composition position got messed up somewhere */
background: var(--dees-terminal-background, #000000); background: ${cssManager.bdTheme('#ffffff', '#000000')};
color: #fff; color: ${cssManager.bdTheme('#333333', '#ffffff')};
display: none; display: none;
position: absolute; position: absolute;
white-space: nowrap; white-space: nowrap;
@@ -184,7 +183,7 @@ export class DeesWorkspaceTerminal extends DeesElement {
.xterm .xterm-viewport { .xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */ /* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: var(--dees-terminal-background, #000000); background-color: ${cssManager.bdTheme('#ffffff', '#000000')};
overflow-y: scroll; overflow-y: scroll;
cursor: default; cursor: default;
position: absolute; position: absolute;
@@ -275,25 +274,51 @@ export class DeesWorkspaceTerminal extends DeesElement {
private fitAddon: FitAddon; private fitAddon: FitAddon;
private terminal: Terminal | null = null; private terminal: Terminal | null = null;
/**
* Get terminal theme colors based on bright/dark mode
*/
private getTerminalTheme(isBright: boolean) {
return isBright
? {
background: '#ffffff',
foreground: '#333333',
cursor: '#333333',
cursorAccent: '#ffffff',
selectionBackground: 'rgba(0, 0, 0, 0.2)',
}
: {
background: '#000000',
foreground: '#cccccc',
cursor: '#cccccc',
cursorAccent: '#000000',
selectionBackground: 'rgba(255, 255, 255, 0.2)',
};
}
public async firstUpdated( public async firstUpdated(
_changedProperties: Map<string | number | symbol, unknown> _changedProperties: Map<string | number | symbol, unknown>
): Promise<void> { ): Promise<void> {
const domtools = await this.domtoolsPromise; const domtoolsInstance = await this.domtoolsPromise;
super.firstUpdated(_changedProperties); super.firstUpdated(_changedProperties);
// Sync CSS variable with background property // Get current theme
this.style.setProperty('--dees-terminal-background', this.background); const isBright = domtoolsInstance.themeManager.goBrightBoolean;
const container = this.shadowRoot.getElementById('container'); const container = this.shadowRoot.getElementById('container');
const term = new Terminal({ const term = new Terminal({
convertEol: true, convertEol: true,
cursorBlink: true, cursorBlink: true,
theme: { theme: this.getTerminalTheme(isBright),
background: this.background,
},
}); });
this.terminal = term; this.terminal = term;
// Subscribe to theme changes
this.terminalThemeSubscription = domtoolsInstance.themeManager.themeObservable.subscribe((goBright: boolean) => {
if (this.terminal) {
this.terminal.options.theme = this.getTerminalTheme(goBright);
}
});
this.fitAddon = new FitAddon(); this.fitAddon = new FitAddon();
term.loadAddon(this.fitAddon); term.loadAddon(this.fitAddon);
@@ -383,6 +408,14 @@ export class DeesWorkspaceTerminal extends DeesElement {
async disconnectedCallback(): Promise<void> { async disconnectedCallback(): Promise<void> {
this.resizeObserver.unobserve(this); this.resizeObserver.unobserve(this);
if (this.terminalThemeSubscription) {
this.terminalThemeSubscription.unsubscribe();
this.terminalThemeSubscription = null;
}
if (this.terminal) {
this.terminal.dispose();
this.terminal = null;
}
await super.disconnectedCallback(); await super.disconnectedCallback();
} }