Skip to content
Open
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
1 change: 1 addition & 0 deletions renderers/lit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Unreleased

- (v0_9) Wire up agent-provided primary color to basic catalog components.
- (v0_9) Re-style the v0_9 catalog components using the default theme from
`web_core`. [#1079](https://github.com/google/A2UI/pull/1079)
- (v0_9) Add missing features to ChoicePicker and CheckBox. [#1145](https://github.com/google/A2UI/pull/1145)
Expand Down
2 changes: 1 addition & 1 deletion renderers/lit/a2ui_explorer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 47 additions & 4 deletions renderers/lit/a2ui_explorer/src/local-gallery.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,57 @@ export const appStyles = css`
background: #1e293b;
border-bottom: 1px solid rgba(148, 163, 184, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
align-items: stretch;
gap: 12px;

h2 {
margin: 0;
}
}

.stepper-controls {
.agent-controls {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;

fieldset {
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 8px;
}

.theme-controls {
font-size: 0.9rem;
margin-right: 8px;
color: #94a3b8;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}

.color-input-group {
display: flex;
gap: 8px;
align-items: center;
}

.color-input {
border: none;
padding: 0;
width: 24px;
height: 24px;
cursor: pointer;
background: none;
}

.message-controls {
display: flex;
gap: 8px;
align-items: center;
font-size: 0.9rem;
color: #94a3b8;
}
}

button {
Expand Down
104 changes: 85 additions & 19 deletions renderers/lit/a2ui_explorer/src/local-gallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import {LitElement, html, css, nothing} from 'lit';
import {provide} from '@lit/context';
import {customElement, state} from 'lit/decorators.js';
import {MessageProcessor} from '@a2ui/web_core/v0_9';
import {MessageProcessor, A2uiMessage} from '@a2ui/web_core/v0_9';
import {basicCatalog, Context} from '@a2ui/lit/v0_9';
import {renderMarkdown} from '@a2ui/markdown-it';
import {getDemoItems, DemoItem} from './examples';
Expand All @@ -30,6 +30,7 @@ export class LocalGallery extends LitElement {
@state() accessor activeItemIndex = 0;
@state() accessor processedMessageCount = 0;
@state() accessor currentDataModelText = '{}';
@state() accessor primaryColor = '#1177ee';

@provide({context: Context.markdown})
private accessor markdownRenderer = renderMarkdown;
Expand Down Expand Up @@ -67,8 +68,7 @@ export class LocalGallery extends LitElement {

selectItem(index: number) {
this.activeItemIndex = index;
this.resetSurface();
this.advanceMessages(true);
this.reloadExample();
}

resetSurface() {
Expand Down Expand Up @@ -98,7 +98,9 @@ export class LocalGallery extends LitElement {

if (toProcess.length === 0) return;

this.processor.processMessages(toProcess);
const modifiedToProcess = this.applyPrimaryColorToMessages(toProcess);

this.processor.processMessages(modifiedToProcess);
this.processedMessageCount += toProcess.length;

// Subscribe to data model on first advance if not already subscribed
Expand All @@ -112,6 +114,58 @@ export class LocalGallery extends LitElement {
}
}

/**
* Reloads the current example by resetting the surface and reprocessing all messages.
* This is used when switching examples or when theme properties change.
*/
private reloadExample() {
this.resetSurface();
this.advanceMessages(true);
}

/**
* Applies the user-selected primary color to `createSurface` messages.
*
* This is necessary for the explorer application to allow users to live-preview
* theme changes by injecting the selected color into the message stream.
* In a standard A2UI renderer deployment, this is not needed as the renderer
* simply processes messages as received from the agent, which is responsible
* for providing the correct theme.
*
* @param messages The list of messages to process.
* @returns A new list of messages with the primary color applied to `createSurface` messages.
*/
private applyPrimaryColorToMessages(messages: A2uiMessage[]): A2uiMessage[] {
return messages.map(msg => {
if ('createSurface' in msg && this.primaryColor) {
Comment thread
ditman marked this conversation as resolved.
return {
...msg,
createSurface: {
...msg.createSurface,
theme: {
...msg.createSurface.theme,
primaryColor: this.primaryColor,
},
},
};
}
return msg;
});
}

/** Handles color input events to update the primary color. */
private onColorInput(e: Event) {
const input = e.target as HTMLInputElement;
this.primaryColor = input.value;
this.reloadExample();
}

/** Clears the custom primary color and reloads the example. */
private clearColor() {
this.primaryColor = '';
this.reloadExample();
}

log(msg: string, detail?: any) {
const time = new Date().toLocaleTimeString();
const entry = detail ? `${msg}\n${JSON.stringify(detail, null, 2)}` : msg;
Expand Down Expand Up @@ -148,22 +202,34 @@ export class LocalGallery extends LitElement {
<section class="gallery-pane">
<div class="preview-header">
<div>
<h2 style="margin:0">${activeItem?.title || 'No selection'}</h2>
<p style="margin:4px 0 0 0; font-size:0.9rem; color:#94a3b8">
${activeItem?.description}
</p>
<h2>${activeItem?.title || 'No selection'}</h2>
<p class="subtitle">${activeItem?.description}</p>
</div>
<div class="stepper-controls">
<span style="font-size:0.9rem; margin-right:8px; color:#94a3b8">
Messages: ${this.processedMessageCount} / ${activeItem?.messages.length || 0}
</span>
<button @click=${() => this.resetSurface()}>Reset</button>
<button @click=${() => this.advanceMessages(false)} ?disabled=${!canAdvance}>
+1 Message
</button>
<button @click=${() => this.advanceMessages(true)} ?disabled=${!canAdvance}>
All Messages
</button>
<div class="agent-controls">
<fieldset class="message-controls">
<legend>
Messages: ${this.processedMessageCount} / ${activeItem?.messages.length || 0}
</legend>
<button @click=${() => this.resetSurface()}>Reset</button>
<button @click=${() => this.advanceMessages(false)} ?disabled=${!canAdvance}>
+1 Message
</button>
<button @click=${() => this.advanceMessages(true)} ?disabled=${!canAdvance}>
All Messages
</button>
</fieldset>
<fieldset class="theme-controls">
<legend>Primary color</legend>
<div class="color-input-group">
<input
type="color"
.value=${this.primaryColor || '#1177ee'}
@input=${this.onColorInput}
class="color-input"
/>
<button @click=${this.clearColor} class="clear-btn">Clear</button>
</div>
</fieldset>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import {ComponentApi} from '@a2ui/web_core/v0_9';
import {A2uiLitElement} from '../../a2ui-lit-element.js';
import {injectBasicCatalogStyles} from '@a2ui/web_core/v0_9/basic_catalog';
import {injectBasicCatalogStyles, computeColorVariant} from '@a2ui/web_core/v0_9/basic_catalog';

/**
* A base class for A2UI basic catalog components.
Expand All @@ -33,13 +33,39 @@ export abstract class BasicCatalogA2uiLitElement<
injectBasicCatalogStyles();
}

updated(changedProperties: Map<PropertyKey, unknown>) {
super.updated(changedProperties);
willUpdate(changedProperties: Map<string, any>) {
super.willUpdate(changedProperties);

const props = this.controller?.props as any;
if (props && props.weight !== undefined) {
this.style.flex = String(props.weight);
} else {
this.style.removeProperty('flex');
}

const primaryColor = this.context?.theme?.primaryColor;
if (primaryColor) {
Comment thread
ditman marked this conversation as resolved.
this.style.setProperty('--a2ui-color-primary', primaryColor);
this.style.setProperty(
'--a2ui-color-primary-light',
computeColorVariant('light', {colorVar: '--a2ui-color-primary'}),
);
this.style.setProperty(
'--a2ui-color-primary-dark',
computeColorVariant('dark', {colorVar: '--a2ui-color-primary'}),
);
this.style.setProperty(
'--a2ui-color-primary-hover',
computeColorVariant('hover', {
darkVar: '--a2ui-color-primary-dark',
lightVar: '--a2ui-color-primary-light',
}),
);
} else {
this.style.removeProperty('--a2ui-color-primary');
this.style.removeProperty('--a2ui-color-primary-light');
this.style.removeProperty('--a2ui-color-primary-dark');
this.style.removeProperty('--a2ui-color-primary-hover');
Comment on lines +66 to +68
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we can compute light/dark/hover programmatically, would it make sense to remove them from the core stylesheet, and let the components that need light/dark/hover variants of a color use the formulas inline with per-widget overrides? That would simplify this a lot, and make the set of CSS variables exposed from web_core smaller. WDYT @josemontespg?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me. Would that affect React also?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josemontespg once we land the formulas to compute light/dark/hover modes for the colors, we'd go into each basic catalog implementation and use that based on the defined --a2ui-xxx-color variable, and remove the usages of the specific "hover"/"dark"/"light" variants. Then we can remove the variables from the core css :)

}
}
}
Loading
Loading