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
## 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)
fix demo wrapper and workspace layout; always render terminal preview

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
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.'
}

View File

@@ -417,7 +417,6 @@ export class DeesSimpleAppDash extends DeesElement {
terminal.style.opacity = '0';
terminal.style.transform = 'translateY(8px) scale(0.99)';
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.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`;
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 lastLineCount: number = 0;
private resizeObserver: ResizeObserver | null = null;
private terminalThemeSubscription: any = null;
public static styles = [
themeDefaultStyles,
@@ -69,8 +70,8 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
height: 100%;
border-radius: 8px;
overflow: hidden;
background: #000000;
border: 1px solid hsl(0 0% 20%);
background: ${cssManager.bdTheme('#ffffff', '#000000')};
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
display: flex;
flex-direction: column;
}
@@ -80,20 +81,20 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
align-items: center;
gap: 8px;
padding: 8px 12px;
background: hsl(0 0% 10%);
background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 10%)')};
font-size: 12px;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
color: hsl(0 0% 60%);
border-bottom: 1px solid hsl(0 0% 20%);
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
flex-shrink: 0;
}
.terminal-header-icon {
color: hsl(0 0% 50%);
color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')};
}
.terminal-header-command {
color: hsl(0 0% 80%);
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
font-weight: 500;
}
@@ -148,8 +149,8 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
}
.xterm .composition-view {
background: #000000;
color: #fff;
background: ${cssManager.bdTheme('#ffffff', '#000000')};
color: ${cssManager.bdTheme('#333333', '#ffffff')};
display: none;
position: absolute;
white-space: nowrap;
@@ -161,7 +162,7 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
}
.xterm .xterm-viewport {
background-color: #000000;
background-color: ${cssManager.bdTheme('#ffffff', '#000000')};
overflow-y: scroll;
cursor: default;
position: absolute;
@@ -243,16 +244,16 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
}
.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 {
background: hsl(0 0% 25%);
background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 25%)')};
border-radius: 4px;
}
.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(
_changedProperties: Map<string | number | symbol, unknown>
): Promise<void> {
@@ -279,6 +301,10 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
const container = this.shadowRoot?.getElementById('xterm-container');
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
this.terminal = new Terminal({
convertEol: true,
@@ -286,13 +312,17 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
disableStdin: true,
fontSize: 12,
fontFamily: "'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace",
theme: {
background: '#000000',
foreground: '#cccccc',
},
theme: this.getTerminalTheme(isBright),
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.terminal.loadAddon(this.fitAddon);
this.terminal.open(container);
@@ -334,6 +364,10 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
if (this.terminalThemeSubscription) {
this.terminalThemeSubscription.unsubscribe();
this.terminalThemeSubscription = null;
}
if (this.terminal) {
this.terminal.dispose();
this.terminal = null;

View File

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