Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@
</select>
</label>

<label class="palette__group palette__group--model">
<span class="palette__label">Theme</span>
<select [value]="theme()" (change)="pickTheme($event)">
@for (opt of themeOptions(); track opt.value) {
<option [value]="opt.value" [selected]="opt.value === theme()">{{ opt.label }}</option>
}
</select>
</label>

<button
type="button"
class="palette__toggle"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ export class ControlPalette {
readonly effortOptions = input.required<readonly { value: string; label: string }[]>();
readonly genUiMode = input.required<string>();
readonly genUiOptions = input.required<readonly { value: string; label: string }[]>();
readonly theme = input.required<string>();
readonly themeOptions = input.required<readonly { value: string; label: string }[]>();
readonly debugOpen = input.required<boolean>();

readonly modeChange = output<DemoMode>();
readonly modelChange = output<string>();
readonly effortChange = output<string>();
readonly genUiModeChange = output<string>();
readonly themeChange = output<string>();
readonly debugOpenChange = output<boolean>();
readonly newConversation = output<void>();

Expand Down Expand Up @@ -68,6 +71,11 @@ export class ControlPalette {
this.genUiModeChange.emit(value);
}

protected pickTheme(event: Event): void {
const value = (event.target as HTMLSelectElement).value;
this.themeChange.emit(value);
}

protected toggleDebug(): void {
this.debugOpenChange.emit(!this.debugOpen());
}
Expand Down
3 changes: 3 additions & 0 deletions examples/chat/angular/src/app/shell/demo-shell.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
[effortOptions]="effortOptions()"
[genUiMode]="genUiMode()"
[genUiOptions]="genUiOptions()"
[theme]="theme()"
[themeOptions]="themeOptions()"
[debugOpen]="debugOpen()"
(modeChange)="onModeChange($event)"
(modelChange)="onModelChange($event)"
(effortChange)="onEffortChange($event)"
(genUiModeChange)="onGenUiModeChange($event)"
(themeChange)="onThemeChange($event)"
(debugOpenChange)="onDebugChange($event)"
(newConversation)="onNewConversation()"
/>
Expand Down
31 changes: 31 additions & 0 deletions examples/chat/angular/src/app/shell/demo-shell.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import {
Component,
ChangeDetectionStrategy,
DOCUMENT,
effect,
signal,
inject,
} from '@angular/core';
Expand Down Expand Up @@ -37,6 +39,16 @@ function modeFromUrl(url: string): DemoMode {
export class DemoShell {
private readonly router = inject(Router);
private readonly persistence = inject(PalettePersistence);
private readonly document = inject(DOCUMENT);

constructor() {
// Reflect the chosen theme onto <html data-theme="..."> so the
// global stylesheet's scoped --a2ui-* overrides activate. Runs on
// signal change including initial mount (read from persistence).
effect(() => {
this.document.documentElement.setAttribute('data-theme', this.theme());
});
}

protected readonly mode = toSignal(
this.router.events.pipe(
Expand Down Expand Up @@ -64,6 +76,13 @@ export class DemoShell {
*/
readonly genUiMode = signal<string>(this.persistence.read('genUiMode') ?? 'a2ui');

/**
* A2UI theme preset for the rendered surface. Toggles a `data-theme`
* attribute on the document root which the global stylesheet keys
* scoped `--a2ui-*` overrides off. Persisted across reloads.
*/
readonly theme = signal<string>(this.persistence.read('theme') ?? 'default-dark');

protected readonly debugOpen = signal<boolean>(this.persistence.read('debug') ?? false);

protected readonly modelOptions = signal<readonly { value: string; label: string }[]>([
Expand All @@ -84,6 +103,13 @@ export class DemoShell {
{ value: 'json-render', label: 'json-render' },
]);

protected readonly themeOptions = signal<readonly { value: string; label: string }[]>([
{ value: 'default-dark', label: 'Default dark' },
{ value: 'default-light', label: 'Default light' },
{ value: 'material-dark', label: 'Material dark' },
{ value: 'material-light', label: 'Material light' },
]);

/** Persisted thread id (null on first run). Reactive so reload reconnects to the same thread. */
private readonly threadIdSignal = signal<string | null>(this.persistence.read('threadId') ?? null);

Expand Down Expand Up @@ -145,6 +171,11 @@ export class DemoShell {
this.persistence.write('genUiMode', next);
}

protected onThemeChange(next: string): void {
this.theme.set(next);
this.persistence.write('theme', next);
}

protected onDebugChange(next: boolean): void {
this.debugOpen.set(next);
this.persistence.write('debug', next);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface PaletteState {
model?: string | null;
effort?: string | null;
genUiMode?: string | null;
theme?: string | null;
debug?: boolean | null;
threadId?: string | null;
collapsed?: boolean | null;
Expand Down
70 changes: 70 additions & 0 deletions examples/chat/angular/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,73 @@ html, body {
background: #0f1116;
color: #e6e9ef;
}

/*
* A2UI theme presets — switched at runtime via the palette "Theme"
* dropdown by toggling `data-theme` on the document root. Mirrors the
* preset CSS files shipped from `@ngaf/chat/themes/*.css`; we inline
* them here to avoid the workspace-vs-published-path resolution
* complexity of importing them dynamically. Default ('default-dark')
* uses the lib's :host defaults, so no override block is needed.
*/

html[data-theme='default-light'] {
--a2ui-primary: #4f8df5;
--a2ui-on-primary: #ffffff;
--a2ui-primary-hover: #3b7be3;
--a2ui-secondary: #5b6275;
--a2ui-on-secondary: #ffffff;
--a2ui-surface: #fafafa;
--a2ui-on-surface: #1a1d23;
--a2ui-surface-variant: rgba(0, 0, 0, 0.04);
--a2ui-on-surface-variant: rgba(0, 0, 0, 0.6);
--a2ui-outline: rgba(0, 0, 0, 0.12);
--a2ui-outline-variant: rgba(0, 0, 0, 0.06);
--a2ui-error: #d33533;
--a2ui-on-error: #ffffff;
--a2ui-scrim: rgba(0, 0, 0, 0.4);
--a2ui-elevation-1: 0 1px 2px rgba(0, 0, 0, 0.08);
--a2ui-elevation-2: 0 2px 4px rgba(0, 0, 0, 0.10);
--a2ui-elevation-3: 0 4px 8px rgba(0, 0, 0, 0.12);
--a2ui-elevation-4: 0 8px 16px rgba(0, 0, 0, 0.14);
--a2ui-elevation-5: 0 16px 32px rgba(0, 0, 0, 0.16);
}

html[data-theme='material-dark'] {
--a2ui-primary: #D0BCFF;
--a2ui-on-primary: #381E72;
--a2ui-primary-hover: #B69DF8;
--a2ui-secondary: #CCC2DC;
--a2ui-on-secondary: #332D41;
--a2ui-surface: #1C1B1F;
--a2ui-on-surface: #E6E1E5;
--a2ui-surface-variant: #49454F;
--a2ui-on-surface-variant: #CAC4D0;
--a2ui-outline: #938F99;
--a2ui-outline-variant: #49454F;
--a2ui-error: #F2B8B5;
--a2ui-on-error: #601410;
--a2ui-scrim: rgba(0, 0, 0, 0.6);
}

html[data-theme='material-light'] {
--a2ui-primary: #6750A4;
--a2ui-on-primary: #FFFFFF;
--a2ui-primary-hover: #533F8E;
--a2ui-secondary: #625B71;
--a2ui-on-secondary: #FFFFFF;
--a2ui-surface: #FFFBFE;
--a2ui-on-surface: #1C1B1F;
--a2ui-surface-variant: #E7E0EC;
--a2ui-on-surface-variant: #49454F;
--a2ui-outline: #79747E;
--a2ui-outline-variant: #CAC4D0;
--a2ui-error: #B3261E;
--a2ui-on-error: #FFFFFF;
--a2ui-scrim: rgba(0, 0, 0, 0.4);
--a2ui-elevation-1: 0 1px 2px rgba(0, 0, 0, 0.08);
--a2ui-elevation-2: 0 2px 4px rgba(0, 0, 0, 0.10);
--a2ui-elevation-3: 0 4px 8px rgba(0, 0, 0, 0.12);
--a2ui-elevation-4: 0 8px 16px rgba(0, 0, 0, 0.14);
--a2ui-elevation-5: 0 16px 32px rgba(0, 0, 0, 0.16);
}
Loading