From 6532f8ca5210cd653bfbcb9d4a29879a8e2981d0 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 7 Apr 2026 13:50:46 -0700 Subject: [PATCH 01/30] Re-style the v0.9 lit renderer using the default CSS. --- renderers/lit/CHANGELOG.md | 8 +- .../catalogs/basic/components/AudioPlayer.ts | 20 +++- .../v0_9/catalogs/basic/components/Button.ts | 73 ++++++++++++++- .../v0_9/catalogs/basic/components/Card.ts | 34 +++++-- .../catalogs/basic/components/CheckBox.ts | 33 ++++++- .../catalogs/basic/components/ChoicePicker.ts | 51 +++++++---- .../v0_9/catalogs/basic/components/Column.ts | 65 ++++++++++--- .../basic/components/DateTimeInput.ts | 32 +++++-- .../v0_9/catalogs/basic/components/Divider.ts | 53 +++++++++-- .../v0_9/catalogs/basic/components/Icon.ts | 53 ++++++++++- .../v0_9/catalogs/basic/components/Image.ts | 73 ++++++++++++++- .../v0_9/catalogs/basic/components/List.ts | 40 ++++---- .../v0_9/catalogs/basic/components/Modal.ts | 39 ++++++-- .../src/v0_9/catalogs/basic/components/Row.ts | 62 +++++++++++-- .../v0_9/catalogs/basic/components/Slider.ts | 48 +++++++--- .../v0_9/catalogs/basic/components/Tabs.ts | 91 ++++++++++++++----- .../v0_9/catalogs/basic/components/Text.ts | 37 +++++++- .../catalogs/basic/components/TextField.ts | 53 ++++++++++- .../v0_9/catalogs/basic/components/Video.ts | 27 +++++- .../catalogs/minimal/components/Button.ts | 73 ++++++++++++++- .../catalogs/minimal/components/Column.ts | 51 ++++++++--- .../v0_9/catalogs/minimal/components/Row.ts | 47 +++++++--- .../v0_9/catalogs/minimal/components/Text.ts | 42 ++++++++- .../catalogs/minimal/components/TextField.ts | 57 ++++++++++-- 24 files changed, 979 insertions(+), 183 deletions(-) diff --git a/renderers/lit/CHANGELOG.md b/renderers/lit/CHANGELOG.md index 56bf693e4..c25f21a0e 100644 --- a/renderers/lit/CHANGELOG.md +++ b/renderers/lit/CHANGELOG.md @@ -1,8 +1,10 @@ ## 0.9.0 -- \[v0_9\] Modify Text widget from the basic catalog to support markdown. -- \[v0_9\] Add `Context.markdown` to the public API -- \[CI\] Fix post-build script. This pins the dependency on `@a2ui/web_core` to +- (v0_9) Modify Text widget from the basic catalog to support markdown. +- (v0_9) Add `Context.markdown` to the public API +- (v0_9) Re-style the v0_9 catalog components using the default theme from + `web_core`. +- (CI) Fix post-build script. This pins the dependency on `@a2ui/web_core` to the latest available in the repo when publishing. ## 0.8.4 diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/AudioPlayer.ts b/renderers/lit/src/v0_9/catalogs/basic/components/AudioPlayer.ts index 20a5ca62d..29dfd9cff 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/AudioPlayer.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/AudioPlayer.ts @@ -14,27 +14,41 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { AudioPlayerApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-audioplayer") export class A2uiAudioPlayerElement extends A2uiLitElement< typeof AudioPlayerApi > { + static styles = css` + :host { + display: flex; + flex-direction: column; + gap: var(--a2ui-spacing-xs, 0.25rem); + } + `; + protected createController() { return new A2uiController(this, AudioPlayerApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; - return html`
+ return html` ${props.description ? html`

${props.description}

` : nothing} -
`; + `; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Button.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Button.ts index 0ca9655fa..1a0ed5ed8 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Button.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Button.ts @@ -14,18 +14,89 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { ButtonApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; +/** + * A button component that can be used to trigger an action. + */ @customElement("a2ui-basic-button") export class A2uiBasicButtonElement extends A2uiLitElement { + /** + * The styles of the button can be customized by redefining the following + * CSS variables: + * + * - Primary variant: + * - `--a2ui-color-primary`: The color for the primary variant. + * - `--a2ui-color-on-primary`: The color of the text on the primary variant. + * - Standard/default variant: + * - `--a2ui-color-secondary`: The color for the default variant. + * - `--a2ui-color-on-secondary`: The color of the text on the default variant. + * - `--a2ui-button-border`: The styling for the button border. Defaults to `--a2ui-border-width` width and `--a2ui-color-border` color. + * - `--a2ui-button-border-radius`: The border radius of the button. Defaults to `--a2ui-border-radius`. + * - `--a2ui-button-padding`: The padding of the button. Defaults to `--a2ui-spacing-m`. + */ + static styles = css` + :where(:host) { + --_color-primary: var(--a2ui-color-primary, #17e); + --_button-border-radius: var( + --a2ui-button-border-radius, + var(--a2ui-spacing-s, 0.25rem) + ); + --_button-padding: var( + --a2ui-button-padding, + var(--a2ui-spacing-m, 0.5rem) var(--a2ui-spacing-l, 1rem) + ); + --_button-border: var( + --a2ui-button-border, + var(--a2ui-border-width, 1px) solid var(--a2ui-color-border, #ccc) + ); + } + .a2ui-button { + --_a2ui-text-margin: 0; + --_a2ui-text-color: var(--a2ui-color-on-secondary, #333); + padding: var(--_button-padding); + background-color: var(--a2ui-color-secondary, #ddd); + color: var(--a2ui-color-on-secondary, #333); + border: var(--_button-border); + border-radius: var(--_button-border-radius); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + } + .a2ui-button.a2ui-button-primary { + --_a2ui-text-color: var(--a2ui-color-on-primary, #fff); + border-color: var(--_color-primary); + background-color: var(--_color-primary); + color: var(--a2ui-color-on-primary, #fff); + } + .a2ui-button:hover { + background-color: var(--a2ui-color-secondary-hover, #ddd); + } + .a2ui-button.a2ui-button-primary:hover { + background-color: var(--a2ui-color-primary-hover, #fbd); + } + .a2ui-button.a2ui-button-borderless { + background: none; + padding: 0; + color: var(--_color-primary); + } + `; + protected createController() { return new A2uiController(this, ButtonApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Card.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Card.ts index 33e7e0350..772f96705 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Card.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Card.ts @@ -14,28 +14,48 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { CardApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-card") export class A2uiCardElement extends A2uiLitElement { + /** + * The styles of the card can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-card-border`: The styling for the card border. Defaults to `--a2ui-border-width` width and `--a2ui-color-border` color. + * - `--a2ui-card-border-radius`: The border radius of the card. Defaults to `--a2ui-border-radius`. + * - `--a2ui-card-padding`: The padding of the card. Defaults to `--a2ui-spacing-m`. + */ + static styles = css` + :host { + display: block; + border: var(--a2ui-card-border, var(--a2ui-border-width, 1px) solid var(--a2ui-color-border, #ccc)); + border-radius: var(--a2ui-card-border-radius, var(--a2ui-border-radius, 8px)); + padding: var(--a2ui-card-padding, var(--a2ui-spacing-m, 16px)); + background-color: var(--a2ui-color-surface, #fff); + color: var(--a2ui-color-on-surface, #333); + } + `; + protected createController() { return new A2uiController(this, CardApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; return html` -
- ${props.child ? html`${this.renderNode(props.child)}` : nothing} -
+ ${props.child ? html`${this.renderNode(props.child)}` : nothing} `; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/CheckBox.ts b/renderers/lit/src/v0_9/catalogs/basic/components/CheckBox.ts index 31bc8d4d9..76cc9acdc 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/CheckBox.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/CheckBox.ts @@ -14,17 +14,48 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { CheckBoxApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-checkbox") export class A2uiCheckBoxElement extends A2uiLitElement { + /** + * The styles of the checkbox can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-checkbox-size`: Size of the box. Defaults to `1rem`. + * - `--a2ui-checkbox-border-radius`: Default corner rounding of the box. + * - `--a2ui-checkbox-gap`: Spacing between the checkbox and its label. Defaults to `8px`. + */ + static styles = css` + :host { + display: inline-block; + } + label.a2ui-checkbox { + display: inline-flex; + align-items: center; + gap: var(--a2ui-checkbox-gap, var(--a2ui-spacing-s, 0.5rem)); + cursor: pointer; + } + input { + width: var(--a2ui-checkbox-size, 1rem); + height: var(--a2ui-checkbox-size, 1rem); + border-radius: var(--a2ui-checkbox-border-radius, 4px); + } + `; + protected createController() { return new A2uiController(this, CheckBoxApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/ChoicePicker.ts b/renderers/lit/src/v0_9/catalogs/basic/components/ChoicePicker.ts index 0385c1cd2..5d97f8f1b 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/ChoicePicker.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/ChoicePicker.ts @@ -14,19 +14,38 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { ChoicePickerApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-choicepicker") export class A2uiChoicePickerElement extends A2uiLitElement< typeof ChoicePickerApi > { + static styles = css` + :host { + display: flex; + flex-direction: column; + gap: var(--a2ui-spacing-xs, 0.25rem); + } + .options { + display: flex; + flex-direction: column; + gap: var(--a2ui-spacing-xs, 0.25rem); + } + `; + protected createController() { return new A2uiController(this, ChoicePickerApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; @@ -48,22 +67,20 @@ export class A2uiChoicePickerElement extends A2uiLitElement< }; return html` -
- ${props.label ? html`` : nothing} -
- ${props.options?.map( - (opt: any) => html` - - `, - )} -
+ ${props.label ? html`` : nothing} +
+ ${props.options?.map( + (opt: any) => html` + + `, + )}
`; } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Column.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Column.ts index 8377e6391..64c58f53c 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Column.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Column.ts @@ -14,38 +14,77 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css, PropertyValues } from "lit"; import { customElement } from "lit/decorators.js"; import { map } from "lit/directives/map.js"; import { styleMap } from "lit/directives/style-map.js"; import { ColumnApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; + +const JUSTIFY_MAP: Record = { + start: "flex-start", + center: "center", + end: "flex-end", + spaceBetween: "space-between", + spaceAround: "space-around", + spaceEvenly: "space-evenly", + stretch: "stretch", +}; + +const ALIGN_MAP: Record = { + start: "flex-start", + center: "center", + end: "flex-end", + stretch: "stretch", +}; @customElement("a2ui-basic-column") export class A2uiBasicColumnElement extends A2uiLitElement { + /** + * The styles of the column can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-column-gap`: The gap between items in the column. Defaults to `--a2ui-spacing-m`. + */ + static styles = css` + :host { + display: flex; + flex-direction: column; + gap: var(--a2ui-column-gap, var(--a2ui-spacing-m)); + } + :host > * { + display: flex; + } + `; + protected createController() { return new A2uiController(this, ColumnApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + + updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + const props = this.controller.props; + if (props) { + this.style.flex = props.weight !== undefined ? String(props.weight) : "initial"; + this.style.justifyContent = JUSTIFY_MAP[props.justify ?? ""] ?? "flex-start"; + this.style.alignItems = ALIGN_MAP[props.align ?? ""] ?? "stretch"; + } + } + render() { const props = this.controller.props; if (!props) return nothing; const children = Array.isArray(props.children) ? props.children : []; - const styles = { - display: "flex", - flexDirection: "column", - flex: props.weight !== undefined ? String(props.weight) : "initial", - gap: "8px", - }; return html` -
)} - > - ${map(children, (child: any) => html`${this.renderNode(child)}`)} -
+ ${map(children, (child: any) => html`${this.renderNode(child)}`)} `; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/DateTimeInput.ts b/renderers/lit/src/v0_9/catalogs/basic/components/DateTimeInput.ts index ea7012467..24051f486 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/DateTimeInput.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/DateTimeInput.ts @@ -14,19 +14,33 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { DateTimeInputApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-datetimeinput") export class A2uiDateTimeInputElement extends A2uiLitElement< typeof DateTimeInputApi > { + static styles = css` + :host { + display: flex; + flex-direction: column; + gap: var(--a2ui-spacing-xs, 0.25rem); + } + `; + protected createController() { return new A2uiController(this, DateTimeInputApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; @@ -38,15 +52,13 @@ export class A2uiDateTimeInputElement extends A2uiLitElement< ? "date" : "time"; return html` -
- ${props.label ? html`` : nothing} - - props.setValue?.((e.target as HTMLInputElement).value)} - /> -
+ ${props.label ? html`` : nothing} + + props.setValue?.((e.target as HTMLInputElement).value)} + /> `; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Divider.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Divider.ts index 98b6c3b6f..503c830f1 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Divider.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Divider.ts @@ -14,30 +14,63 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; import { DividerApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-divider") export class A2uiDividerElement extends A2uiLitElement { + /** + * The styles of the divider can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-divider-border`: The styling for the divider border. Defaults to `--a2ui-border-width` solid `--a2ui-color-border`. + * - `--a2ui-divider-spacing`: The spacing around the divider. Defaults to `--a2ui-spacing-m`. + */ + static styles = css` + :host { + display: block; + } + .a2ui-divider.horizontal { + height: 0; + overflow: hidden; + font-size: 0.1px; + line-height: 0; + border-top: var(--a2ui-divider-border, var(--a2ui-border-width, 1px) solid var(--a2ui-color-border, #ccc)); + margin: var(--a2ui-divider-spacing, var(--a2ui-spacing-m, 0.5rem)) 0; + flex: 1; + } + .a2ui-divider.vertical { + width: var(--a2ui-border-width, 1px); + background-color: var(--a2ui-color-border, #ccc); + height: 100%; + margin: 0 var(--a2ui-divider-spacing, var(--a2ui-spacing-m, 0.5rem)); + } + `; + protected createController() { return new A2uiController(this, DividerApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; - return props.axis === "vertical" - ? html`
` - : html`
`; + const classes = { + "a2ui-divider": true, + vertical: props.axis === "vertical", + horizontal: props.axis !== "vertical", + }; + + return html`
`; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Icon.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Icon.ts index 6069ae4ea..321b96c92 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Icon.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Icon.ts @@ -14,13 +14,58 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { IconApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; + +import { classMap } from "lit/directives/class-map.js"; + +const ICON_MAP: Record = { + favoriteOff: "favorite_border", + play: "play_arrow", + rewind: "fast_rewind", + starOff: "star_border", +}; @customElement("a2ui-icon") export class A2uiIconElement extends A2uiLitElement { + /** + * The icon component can be customized with the following CSS variables: + * + * - `--a2ui-icon-size`: Dimensions of the icon. + * - `--a2ui-icon-color`: Color tint applied to the icon. + */ + static styles = css` + :host { + display: inline-flex; + align-items: center; + justify-content: center; + } + .material-icons { + font-family: "Material Icons", sans-serif; + font-weight: normal; + font-style: normal; + font-size: var(--a2ui-icon-size, var(--a2ui-font-size-xl, 24px)); + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; + color: var(--a2ui-icon-color, inherit); + } + `; + + private getIconName(rawName: string): string { + if (ICON_MAP[rawName]) return ICON_MAP[rawName]; + return rawName.replace(/[A-Z]/g, (letter: string) => `_${letter.toLowerCase()}`); + } + protected createController() { return new A2uiController(this, IconApi); } @@ -29,9 +74,11 @@ export class A2uiIconElement extends A2uiLitElement { const props = this.controller.props; if (!props) return nothing; - const name = + const rawName = typeof props.name === "string" ? props.name : (props.name as any)?.path; - return html`${name}`; } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Image.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Image.ts index 13e1cc7e9..9089ecb6a 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Image.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Image.ts @@ -14,28 +14,91 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css, PropertyValues } from "lit"; import { customElement } from "lit/decorators.js"; -import { styleMap } from "lit/directives/style-map.js"; +import { classMap } from "lit/directives/class-map.js"; import { ImageApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-image") export class A2uiImageElement extends A2uiLitElement { + /** + * The styles of the image can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-image-border-radius`: Controls the rounded corners of the image. Defaults to `0`. + * - `--a2ui-image-icon-size`: Controls the size of the `icon` variant. Defaults to `24px`. + * - `--a2ui-image-avatar-size`: Controls the size of the `avatar` variant. Defaults to `40px`. + * - `--a2ui-image-small-feature-width`: Controls the max-width of the `smallFeature` variant. Defaults to `100px`. + * - `--a2ui-image-large-feature-height`: Controls the max-height of the `largeFeature` variant. Defaults to `400px`. + * - `--a2ui-image-header-height`: Controls the height of the `header` variant. Defaults to `200px`. + */ + static styles = css` + img { + display: block; + width: 100%; + height: auto; + border-radius: var(--a2ui-image-border-radius, 0); + } + :host(.icon), + img.icon { + width: var(--a2ui-image-icon-size, 24px); + height: var(--a2ui-image-icon-size, 24px); + } + img.avatar { + width: var(--a2ui-image-avatar-size, 40px); + height: var(--a2ui-image-avatar-size, 40px); + border-radius: 50%; + } + :host(.smallFeature), + img.smallFeature { + max-width: var(--a2ui-image-small-feature-size, 100px); + } + :host(.largeFeature), + img.largeFeature { + max-height: var(--a2ui-image-large-feature-size, 400px); + } + :host(.header), + img.header { + height: var(--a2ui-image-header-size, 200px); + object-fit: cover; + } + `; + protected createController() { return new A2uiController(this, ImageApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + + updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + const props = this.controller.props; + if (props) { + const img = this.shadowRoot?.querySelector("img"); + if (img) { + img.style.objectFit = props.fit || "fill"; + } + } + } + render() { const props = this.controller.props; if (!props) return nothing; - const styles = { objectFit: props.fit || "fill", width: "100%" }; + const classes = { + "a2ui-image": true, + [props.variant || ""]: !!props.variant, + }; + return html`${props.description`; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/List.ts b/renderers/lit/src/v0_9/catalogs/basic/components/List.ts index 01402dc3a..1f81967ba 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/List.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/List.ts @@ -14,38 +14,46 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css, PropertyValues } from "lit"; import { customElement } from "lit/decorators.js"; import { map } from "lit/directives/map.js"; -import { styleMap } from "lit/directives/style-map.js"; import { ListApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-list") export class A2uiListElement extends A2uiLitElement { + static styles = css` + :host { + display: flex; + overflow: auto; + gap: var(--a2ui-list-gap, var(--a2ui-spacing-m, 0.5rem)); + } + `; + protected createController() { return new A2uiController(this, ListApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + + updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + const props = this.controller.props; + if (props) { + this.style.flexDirection = props.direction === "horizontal" ? "row" : "column"; + } + } + render() { const props = this.controller.props; if (!props) return nothing; const children = Array.isArray(props.children) ? props.children : []; - const styles = { - display: "flex", - flexDirection: props.direction === "horizontal" ? "row" : "column", - overflow: "auto", - gap: "8px", - }; - return html` -
)} - > - ${map(children, (child: any) => html`${this.renderNode(child)}`)} -
- `; + return html`${map(children, (child: any) => html`${this.renderNode(child)}`)}`; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Modal.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Modal.ts index a16294d30..838db61f1 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Modal.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Modal.ts @@ -14,16 +14,46 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement, query } from "lit/decorators.js"; import { ModalApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-modal") export class A2uiLitModal extends A2uiLitElement { + /** + * The styles of the modal can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-modal-backdrop-bg`: Controls the backdrop color of the dialog. + * - `--a2ui-modal-padding`: Padding inside the dialog content area. Defaults to `24px`. + * - `--a2ui-modal-border-radius`: Border radius of the dialog. Defaults to `8px`. + */ + static styles = css` + :host { + display: inline-block; + } + dialog { + border: 1px solid var(--a2ui-color-border, #ccc); + border-radius: var(--a2ui-modal-border-radius, 8px); + padding: var(--a2ui-modal-padding, 24px); + min-width: 300px; + background: var(--a2ui-color-surface, #fff); + } + dialog::backdrop { + background: var(--a2ui-modal-backdrop-bg, rgba(0, 0, 0, 0.5)); + } + `; + protected createController() { return new A2uiController(this, ModalApi); } + + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } @query("dialog") accessor dialog!: HTMLDialogElement; render() { @@ -31,13 +61,10 @@ export class A2uiLitModal extends A2uiLitElement { if (!props) return nothing; return html` -
this.dialog?.showModal()}> +
this.dialog?.showModal()} style="display: contents;"> ${props.trigger ? html`${this.renderNode(props.trigger)}` : nothing}
- +
diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Row.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Row.ts index 6048f01ea..20d708316 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Row.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Row.ts @@ -14,35 +14,77 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css, PropertyValues } from "lit"; import { customElement } from "lit/decorators.js"; import { map } from "lit/directives/map.js"; import { styleMap } from "lit/directives/style-map.js"; import { RowApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; + +const JUSTIFY_MAP: Record = { + start: "flex-start", + center: "center", + end: "flex-end", + spaceBetween: "space-between", + spaceAround: "space-around", + spaceEvenly: "space-evenly", + stretch: "stretch", +}; + +const ALIGN_MAP: Record = { + start: "flex-start", + center: "center", + end: "flex-end", + stretch: "stretch", +}; @customElement("a2ui-basic-row") export class A2uiBasicRowElement extends A2uiLitElement { + /** + * The styles of the row can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-row-gap`: The gap between items in the row. Defaults to `--a2ui-spacing-m`. + */ + static styles = css` + :host { + display: flex; + flex-direction: row; + gap: var(--a2ui-row-gap, var(--a2ui-spacing-m)); + } + :host > * { + display: flex; + } + `; + protected createController() { return new A2uiController(this, RowApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + + updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + const props = this.controller.props; + if (props) { + this.style.flex = props.weight !== undefined ? String(props.weight) : "initial"; + this.style.justifyContent = JUSTIFY_MAP[props.justify ?? ""] ?? "flex-start"; + this.style.alignItems = ALIGN_MAP[props.align ?? ""] ?? "stretch"; + } + } + render() { const props = this.controller.props; if (!props) return nothing; const children = Array.isArray(props.children) ? props.children : []; - const styles = { - display: "flex", - flexDirection: "row", - flex: props.weight !== undefined ? String(props.weight) : "initial", - gap: "8px", - }; return html` -
)}> - ${map(children, (child: any) => html`${this.renderNode(child)}`)} -
+ ${map(children, (child: any) => html`${this.renderNode(child)}`)} `; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Slider.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Slider.ts index d4948ab74..6c2f96bde 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Slider.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Slider.ts @@ -14,34 +14,56 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { SliderApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-slider") export class A2uiSliderElement extends A2uiLitElement { + /** + * The slider can be customized with the following CSS variables: + * + * - `--a2ui-slider-track-color`: Color of the slider track. Defaults to `--a2ui-color-secondary`. + * - `--a2ui-slider-thumb-color`: Color of the slider thumb. Defaults to `--a2ui-color-primary`. + */ + static styles = css` + :host { + display: flex; + align-items: center; + gap: var(--a2ui-spacing-s, 0.5rem); + } + input[type="range"] { + accent-color: var(--a2ui-slider-thumb-color, var(--a2ui-color-primary, #007bff)); + background: var(--a2ui-slider-track-color, var(--a2ui-color-secondary, #e9ecef)); + } + `; + protected createController() { return new A2uiController(this, SliderApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; return html` -
- ${props.label ? html`` : nothing} - - props.setValue?.(Number((e.target as HTMLInputElement).value))} - /> - ${props.value} -
+ ${props.label ? html`` : nothing} + + props.setValue?.(Number((e.target as HTMLInputElement).value))} + /> + ${props.value} `; } } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Tabs.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Tabs.ts index f51e427cc..400e1bd6b 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Tabs.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Tabs.ts @@ -14,45 +14,88 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; import { TabsApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-tabs") export class A2uiLitTabs extends A2uiLitElement { + /** + * The styles of the tabs can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-tabs-header-background`: Default transparent. + * - `--a2ui-tabs-header-background-active`: Default `--a2ui-color-secondary`. + * - `--a2ui-tabs-header-color`: Default `--a2ui-color-on-surface`. + * - `--a2ui-tabs-header-color-active`: Default `--a2ui-color-on-secondary`. + * - `--a2ui-tabs-border`: Default `--a2ui-border-width` solid `--a2ui-color-border`. + * - `--a2ui-tabs-content-padding`: Default `0 var(--a2ui-spacing-m, 0.5rem)`. + */ + static styles = css` + :host { + display: block; + } + .a2ui-tabs-headers { + display: flex; + gap: var(--a2ui-spacing-xs, 0.25rem); + border-bottom: var(--a2ui-tabs-border, var(--a2ui-border-width, 1px) solid var(--a2ui-color-border, #ccc)); + margin-bottom: var(--a2ui-spacing-m, 0.5rem); + } + .a2ui-tabs-header { + padding: var(--a2ui-spacing-m, 0.5rem) var(--a2ui-spacing-l, 1rem); + background: var(--a2ui-tabs-header-background, transparent); + color: var(--a2ui-tabs-header-color, var(--a2ui-color-on-surface)); + border: none; + border-radius: var(--a2ui-border-radius, 0.25rem) var(--a2ui-border-radius, 0.25rem) 0 0; + cursor: pointer; + font-family: inherit; + } + .a2ui-tabs-header.active { + background: var(--a2ui-tabs-header-background-active, var(--a2ui-color-secondary, #eee)); + color: var(--a2ui-tabs-header-color-active, var(--a2ui-color-on-secondary, #333)); + } + .a2ui-tabs-content { + padding: var(--a2ui-tabs-content-padding, 0 var(--a2ui-spacing-m, 0.5rem)); + } + `; + protected createController() { return new A2uiController(this, TabsApi); } + + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + @state() accessor activeIndex = 0; render() { const props = this.controller.props; if (!props || !props.tabs) return nothing; return html` -
-
- ${props.tabs.map( - (tab: any, i: number) => html` - - `, - )} -
-
- ${props.tabs[this.activeIndex] - ? html`${this.renderNode(props.tabs[this.activeIndex].child)}` - : nothing} -
+
+ ${props.tabs.map( + (tab: any, i: number) => html` + + `, + )} +
+
+ ${props.tabs[this.activeIndex] + ? html`${this.renderNode(props.tabs[this.activeIndex].child)}` + : nothing}
`; } diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/Text.ts b/renderers/lit/src/v0_9/catalogs/basic/components/Text.ts index 73ea577b9..f983d4c7a 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/Text.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/Text.ts @@ -14,17 +14,47 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { consume } from "@lit/context"; import { TextApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController, Context } from "@a2ui/lit/v0_9"; import * as Types from "@a2ui/web_core/types/types"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; import { markdown } from "../../../directives/directives.js"; @customElement("a2ui-basic-text") export class A2uiBasicTextElement extends A2uiLitElement { + /** + * The styles of the text component can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-text-color-text`: The color of the text. Defaults to `--a2ui-color-on-background`. + * + * It also supports `--_a2ui-text-color` override from parent components (like Button). + */ + static styles = css` + :host { + color: var(--_a2ui-text-color, var(--a2ui-text-color-text, var(--a2ui-color-on-background))); + } + p, h1, h2, h3, h4, h5, h6 { + margin: var(--_a2ui-text-margin, 0); + } + h1, h2, h3, h4, h5 { + font-family: var(--a2ui-font-family-title, inherit); + line-height: var(--a2ui-line-height-headings, 1.2); + } + h1 { font-size: var(--a2ui-font-size-2xl); } + h2 { font-size: var(--a2ui-font-size-xl); } + h3 { font-size: var(--a2ui-font-size-l); } + p, h4 { font-size: var(--a2ui-font-size-m); } + h5 { font-size: var(--a2ui-font-size-s); } + p, .a2ui-caption { + line-height: var(--a2ui-line-height-body, 1.5); + } + .a2ui-caption, .a2ui-caption > *, .a2ui-caption ::slotted(*) { font-size: var(--a2ui-font-size-xs); } + `; // Retrieve a MarkdownRenderer provided by the application. @consume({ context: Context.markdown, subscribe: true }) @@ -34,6 +64,11 @@ export class A2uiBasicTextElement extends A2uiLitElement { return new A2uiController(this, TextApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; diff --git a/renderers/lit/src/v0_9/catalogs/basic/components/TextField.ts b/renderers/lit/src/v0_9/catalogs/basic/components/TextField.ts index 2efbe79c3..453d0ea67 100644 --- a/renderers/lit/src/v0_9/catalogs/basic/components/TextField.ts +++ b/renderers/lit/src/v0_9/catalogs/basic/components/TextField.ts @@ -14,20 +14,67 @@ * limitations under the License. */ -import { html, nothing } from "lit"; +import { html, nothing, css } from "lit"; import { customElement } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { TextFieldApi } from "@a2ui/web_core/v0_9/basic_catalog"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; +import { injectDefaultA2uiTheme } from "@a2ui/web_core/v0_9"; @customElement("a2ui-basic-textfield") export class A2uiBasicTextFieldElement extends A2uiLitElement< typeof TextFieldApi > { + /** + * The styles of the text field can be customized by redefining the following + * CSS variables: + * + * - `--a2ui-textfield-border`: The styling for the text field border. Defaults to `--a2ui-border-width` width and `--a2ui-color-border` color. + * - `--a2ui-textfield-border-radius`: The border radius of the text field. Defaults to `--a2ui-border-radius`. + * - `--a2ui-textfield-padding`: The padding of the text field. Defaults to `--a2ui-spacing-s`. + * - `--a2ui-textfield-color-border-focus`: The border color on focus. Defaults to `--a2ui-color-primary`. + * - `--a2ui-textfield-color-error`: The color for both invalid border and error text. Defaults to red. + * + * It also inherits global input variables: + * - `--a2ui-color-input`: Background color. + * - `--a2ui-color-on-input`: Text color. + */ + static styles = css` + :host { + display: flex; + flex-direction: column; + gap: var(--a2ui-spacing-xs, 0.25rem); + } + .a2ui-textfield { + background-color: var(--a2ui-color-input, #fff); + color: var(--a2ui-color-on-input, #333); + border: var(--a2ui-textfield-border, var(--a2ui-border-width, 1px) solid var(--a2ui-color-border, #ccc)); + border-radius: var(--a2ui-textfield-border-radius, var(--a2ui-border-radius, 0.25rem)); + padding: var(--a2ui-textfield-padding, var(--a2ui-spacing-s, 0.25rem)); + font-family: inherit; + } + .a2ui-textfield:focus { + outline: none; + border-color: var(--a2ui-textfield-color-border-focus, var(--a2ui-color-primary, #17e)); + } + .a2ui-textfield.invalid { + border-color: var(--a2ui-textfield-color-error, red); + } + .error { + color: var(--a2ui-textfield-color-error, red); + font-size: var(--a2ui-font-size-xs, 0.75rem); + } + `; + protected createController() { return new A2uiController(this, TextFieldApi); } + connectedCallback() { + super.connectedCallback(); + injectDefaultA2uiTheme(); + } + render() { const props = this.controller.props; if (!props) return nothing; @@ -42,8 +89,7 @@ export class A2uiBasicTextFieldElement extends A2uiLitElement< const classes = { "a2ui-textfield": true, invalid: isInvalid }; return html` -
- ${props.label ? html`` : nothing} + ${props.label ? html`` : nothing} ${props.variant === "longText" ? html`` - : html` { ${props.validationErrors[0]}
` : nothing} -
`; } } From 27ced22cd90628f32e28fbff9f1031f44842ea5e Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Tue, 7 Apr 2026 13:52:51 -0700 Subject: [PATCH 02/30] Fix the lit 09 sample gallery to inject the material icons font --- samples/client/lit/gallery_v0_9/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/client/lit/gallery_v0_9/index.html b/samples/client/lit/gallery_v0_9/index.html index 9ac3b2c0e..a04314f9c 100644 --- a/samples/client/lit/gallery_v0_9/index.html +++ b/samples/client/lit/gallery_v0_9/index.html @@ -24,6 +24,7 @@ +