Compare commits

..

5 Commits

Author SHA1 Message Date
Lea Verou
3de76ef341 Do not reflect variant, fixes #454
Also a docs fix on callout (used it to make my testing easier)
2025-01-08 12:31:25 -05:00
Kelsey Jackson
31cfdf5704 Fixed radio button styling regression (#443)
* fixed styling regression

* Import missing appearance utilities

* Fix up radio button theme styles

* tweaked based on feedback

---------

Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-01-08 12:07:28 -05:00
Lindsay M
3511a60b93 Fix missing select styles, improve consistency (#450)
* Refactor for consistency, fix missing theme styles

* Remove unused custom properties from docs

* Add missing custom property to docs

* Add UI test case
2025-01-08 12:04:22 -05:00
Lindsay M
e55e091192 Fix caret color on slider tooltips (#448) 2025-01-08 12:03:16 -05:00
Lindsay M
09df23dff8 Use theme properties for color picker border (#449) 2025-01-08 10:02:18 -05:00
19 changed files with 123 additions and 293 deletions

View File

@@ -19,43 +19,40 @@ icon: callout
Set the `variant` attribute to change the callout's variant.
```html {.example}
<wa-callout variant="brand">
<wa-icon slot="icon" name="circle-info" variant="regular"></wa-icon>
<strong>This is super informative</strong><br />
You can tell by how pretty the callout is.
</wa-callout>
<br />
<wa-callout variant="success">
<wa-icon slot="icon" name="circle-check" variant="regular"></wa-icon>
<strong>Your changes have been saved</strong><br />
You can safely exit the app now.
</wa-callout>
<br />
<wa-callout variant="neutral">
<wa-icon slot="icon" name="gear" variant="regular"></wa-icon>
<strong>Your settings have been updated</strong><br />
Settings will take effect on next login.
</wa-callout>
<br />
<wa-callout variant="warning">
<wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon>
<strong>Your session has ended</strong><br />
Please login again to continue.
</wa-callout>
<br />
<wa-callout variant="danger">
<wa-icon slot="icon" name="circle-exclamation" variant="regular"></wa-icon>
<strong>Your account has been deleted</strong><br />
We're very sorry to see you go!
</wa-callout>
{% for variant, info in {
brand: {
title: 'This is super informative',
content: 'You can tell by how pretty the callout is.',
icon: 'circle-info'
},
success: {
title: 'Your changes have been saved',
content: 'You can safely exit the app now.',
icon: 'circle-check'
},
neutral: {
title: 'Your settings have been updated',
content: 'Settings will take effect on next login.',
icon: 'gear'
},
warning: {
title: 'Your session has ended',
content: 'Please login again to continue.',
icon: 'triangle-exclamation'
},
danger: {
title: 'Your account has been deleted',
content: 'Were very sorry to see you go!',
icon: 'circle-exclamation'
}
} %}
<wa-callout variant="{{ variant }}">
<wa-icon slot="icon" name="{{ info.icon }}" variant="regular"></wa-icon>
<strong>{{ info.title }}</strong><br />
{{ info.content }}
</wa-callout>
{% if not loop.last %}<br />{% endif %}
{% endfor %}
```
### Appearance

View File

@@ -24,63 +24,6 @@ Many Font Awesome Pro icon families have variants such as `thin`, `light`, `regu
<wa-icon family="brands" name="web-awesome"></wa-icon>
```
<div data-alpha="remove">
### Setting icon info via CSS
You can also set the icon's family, name, and variant via CSS custom properties.
This can be useful when you want to set the icon dynamically (e.g. in response to a CSS pseudo-class or media query) or set defaults for a group of icons (e.g. icons inside callouts or all icons for a given theme).
```html {.example}
<wa-callout>
<!-- Look ma, no attributes! -->
<wa-icon slot="icon"></wa-icon>
This is a callout.
</wa-callout>
<wa-callout variant="danger">
<wa-icon slot="icon" name="dumpster-fire" variant="solid"></wa-icon>
This is a callout with an explicit icon.
</wa-callout>
<wa-callout variant="warning">
<!-- Look ma, no attributes! -->
<wa-icon slot="icon"></wa-icon>
Here be dragons.
<button id="toggle_icon">Toggle&nbsp;<wa-icon name="circle-exclamation"></wa-icon></button>
</wa-callout>
<style>
wa-callout {
--wa-icon-variant: regular;
--wa-icon-name: info-circle;
&[variant="warning"] {
--wa-icon-name: triangle-exclamation;
}
}
</style>
<script>
toggle_icon.addEventListener('click', e => {
let callout = e.target.closest('wa-callout');
let value = callout.style.getPropertyValue('--wa-icon-name').trim();
if (value) {
callout.style.removeProperty('--wa-icon-name');
}
else {
callout.style.setProperty('--wa-icon-name', 'circle-exclamation');
}
});
</script>
```
Notes:
- If you specify attributes, they will override the CSS custom properties, which provides a way to set defaults and then override them as needed.
- CSS custom properties inherit — so if you set a `--wa-icon-*` custom property on an element, it will affect *all* icons within it that dont override these values (either via attributes or CSS custom properties).
- These CSS properties are currently not reactive and will only be read when the component is first connected.
</div>
### Colors
Icons inherit their color from the current text color. Thus, you can set the `color` property on the `<wa-icon>` element or an ancestor to change the color.

View File

@@ -172,7 +172,7 @@ layout: page-outline
<div class="shadow" style="box-shadow: var(--wa-shadow-l);"></div>
```
## Tests
## Alignment Tests
```html {.example}
<style>
@@ -254,6 +254,8 @@ layout: page-outline
</div>
```
## Custom Property Tests
```html {.example}
<style>
.wa-theme-test {
@@ -319,4 +321,32 @@ layout: page-outline
</wa-select>
</div>
```
## Text Controls Tests
```html {.example}
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
<label>Native Input <input type="text" value="value"></label>
<wa-input label="WA Input" type="text" value="value"></wa-input>
<label>
Native Select
<select value="option-1">
<option value="option-1">Option 1</option>
<option value="option-2">Option 2</option>
<option value="option-3">Option 3</option>
<option value="option-4">Option 4</option>
</select>
</label>
<wa-select label="WA Select" value="option-1">
<wa-option value="option-1">Option 1</wa-option>
<wa-option value="option-2">Option 2</wa-option>
<wa-option value="option-3">Option 3</wa-option>
<wa-option value="option-4">Option 4</wa-option>
</wa-select>
<label>Native Textarea <textarea>value</textarea></label>
<wa-textarea label="WA Textarea" value="value"></wa-textarea>
</div>
```

10
package-lock.json generated
View File

@@ -9,7 +9,6 @@
"version": "3.0.0-alpha.7",
"license": "MIT",
"dependencies": {
"@bramus/style-observer": "^1.3.0",
"@ctrl/tinycolor": "^4.1.0",
"@floating-ui/dom": "^1.6.12",
"@shoelace-style/animations": "^1.2.0",
@@ -677,15 +676,6 @@
"node": ">=4"
}
},
"node_modules/@bramus/style-observer": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@bramus/style-observer/-/style-observer-1.3.0.tgz",
"integrity": "sha512-IQjYId9X7xgz0NeKGatC37lqsdS+cE5bfdB9jKh7+zJnA9BqENee2C48boDIRQrTED4JxleRZGhTY86S1/l7QA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@cspell/cspell-bundled-dicts": {
"version": "6.31.3",
"resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-6.31.3.tgz",

View File

@@ -64,7 +64,6 @@
"node": ">=14.17.0"
},
"dependencies": {
"@bramus/style-observer": "^1.3.0",
"@ctrl/tinycolor": "^4.1.0",
"@floating-ui/dom": "^1.6.12",
"@shoelace-style/animations": "^1.2.0",

View File

@@ -69,7 +69,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
@property() title = ''; // make reactive to pass through
/** The button's theme variant. */
@property({ reflect: true }) variant: 'neutral' | 'brand' | 'success' | 'warning' | 'danger' = 'neutral';
@property() variant: 'neutral' | 'brand' | 'success' | 'warning' | 'danger' = 'neutral';
/** The button's visual appearance. */
@property({ reflect: true }) appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'accent';

View File

@@ -317,8 +317,8 @@
border-radius: inherit;
background-color: currentColor;
box-shadow:
inset 0 0 0 0.0625rem var(--wa-form-control-border-color),
inset 0 0 0 0.25rem var(--wa-color-surface-default);
inset 0 0 0 var(--border-width) var(--wa-form-control-border-color),
inset 0 0 0 calc(var(--border-width) * 2) var(--wa-color-surface-default);
}
.trigger--empty:before {

View File

@@ -31,24 +31,3 @@ svg {
width: 1em;
justify-content: center;
}
@property --wa-icon-family {
syntax: '<custom-ident> | auto';
inherits: true;
initial-value: 'auto';
}
@property --wa-icon-variant {
syntax: '<custom-ident> | auto';
inherits: true;
initial-value: 'auto';
}
@property --wa-icon-library {
syntax: '<custom-ident> | auto';
inherits: true;
initial-value: 'auto';
}
@property --wa-icon-name {
syntax: '<custom-ident> | auto';
inherits: true;
initial-value: 'auto';
}

View File

@@ -48,21 +48,21 @@ export default class WaIcon extends WebAwesomeElement {
@state() private svg: SVGElement | HTMLTemplateResult | null = null;
/** The name of the icon to draw. Available names depend on the icon library being used. */
@property({ cssProperty: true }) name?: string;
@property({ reflect: true }) name?: string;
/**
* The family of icons to choose from. For Font Awesome Free (default), valid options include `classic` and `brands`.
* For Font Awesome Pro subscribers, valid options include, `classic`, `sharp`, `duotone`, and `brands`. Custom icon
* libraries may or may not use this property.
*/
@property({ cssProperty: true }) family: string;
@property({ reflect: true }) family: string;
/**
* The name of the icon's variant. For Font Awesome, valid options include `thin`, `light`, `regular`, and `solid` for
* the `classic` and `sharp` families. Some variants require a Font Awesome Pro subscription. Custom icon libraries
* may or may not use this property.
*/
@property({ cssProperty: true }) variant: string;
@property({ reflect: true }) variant: string;
/** Draws the icon in a fixed-width both. */
@property({ attribute: 'fixed-width', type: Boolean, reflect: true }) fixedWidth: false;
@@ -80,11 +80,10 @@ export default class WaIcon extends WebAwesomeElement {
@property() label = '';
/** The name of a registered custom icon library. */
@property({ cssProperty: true }) library = 'default';
@property({ reflect: true }) library = 'default';
connectedCallback() {
super.connectedCallback();
watchIcon(this);
}

View File

@@ -54,6 +54,7 @@ import styles from './input.css';
* @csspart suffix - The container that wraps the suffix.
*
* @cssproperty --background-color - The input's background color.
* @cssproperty --border-color - The color of the input's borders.
* @cssproperty --border-width - The width of the input's borders. Expects a single value.
* @cssproperty --box-shadow - The shadow effects around the edges of the input.
*/

View File

@@ -34,14 +34,12 @@
*/
:host([checked]) {
--background-color: var(--wa-color-brand-fill-quiet);
--background-color-hover: var(--background-color);
--border-color: var(--wa-form-control-activated-color);
--text-color: var(--wa-color-brand-on-normal);
--indicator-color: var(--border-color);
--indicator-color: var(--wa-form-control-activated-color);
--indicator-width: var(--wa-border-width-s);
box-shadow:
var(--box-shadow, 0 0 transparent),
inset 0 0 0 var(--indicator-width) var(--indicator-color);
box-shadow: inset 0 0 0 var(--indicator-width) var(--indicator-color);
& button {
--border-color: var(--indicator-color);
}
}

View File

@@ -8,6 +8,7 @@ import { HasSlotController } from '../../internal/slot.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import nativeStyles from '../../styles/native/button.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
import variantStyles from '../../styles/utilities/variants.css';
import buttonStyles from '../button/button.css';
@@ -50,7 +51,7 @@ import styles from './radio-button.css';
*/
@customElement('wa-radio-button')
export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
static shadowStyle = [variantStyles, sizeStyles, nativeStyles, buttonStyles, styles];
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, buttonStyles, styles];
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');

View File

@@ -81,8 +81,6 @@ import styles from './select.css';
*
* @cssproperty --background-color - The background color of the select's combobox.
* @cssproperty --border-color - The border color of the select's combobox.
* @cssproperty --border-radius - The border radius of the select's combobox.
* @cssproperty --border-style - The style of the select's borders, including the listbox.
* @cssproperty --border-width - The width of the select's borders, including the listbox.
* @cssproperty --box-shadow - The shadow effects around the edges of the select's combobox.
*/

View File

@@ -38,7 +38,7 @@
}
--inset-block: calc(50% + (var(--thumb-size) / 2) + var(--tooltip-offset));
--border-block: var(--wa-tooltip-arrow-size) solid var(--wa-color-neutral-fill-loud);
--border-block: var(--wa-tooltip-arrow-size) solid var(--wa-tooltip-background-color);
@media (forced-colors: active) {
border: solid 1px transparent;

View File

@@ -41,8 +41,6 @@ import styles from './textarea.css';
*
* @cssproperty --background-color - The textarea's background color.
* @cssproperty --border-color - The color of the textarea's borders.
* @cssproperty --border-radius - The border radius of the textarea's corners.
* @cssproperty --border-style - The style of the textarea's borders.
* @cssproperty --border-width - The width of the textarea's borders.
* @cssproperty --box-shadow - The shadow effects around the edges of the textarea.
*/

View File

@@ -1,19 +1,7 @@
import { CSSStyleObserver } from '@bramus/style-observer';
import type { CSSResult, CSSResultGroup, PropertyDeclaration, PropertyValues } from 'lit';
import type { CSSResult, CSSResultGroup, PropertyValues } from 'lit';
import { LitElement, isServer, unsafeCSS } from 'lit';
import { property } from 'lit/decorators.js';
import componentStyles from '../styles/shadow/component.css';
import { getComputedStyle } from './computedStyle.js';
// Augment Lit's module
declare module 'lit' {
interface PropertyDeclaration {
/**
* Indicates whether the property should reflect to a CSS custom property.
*/
cssProperty?: true | string;
}
}
export default class WebAwesomeElement extends LitElement {
constructor() {
@@ -64,18 +52,6 @@ export default class WebAwesomeElement extends LitElement {
internals: ElementInternals;
#computedStyle: CSSStyleDeclaration | null;
#setVia: Record<PropertyKey, 'css' | 'attribute' | 'js'> = {};
#setting = new Set<PropertyKey>();
connectedCallback() {
super.connectedCallback();
// FIXME this is currently static.
// It will only update when the element is connected, not when a relevant CSS property changes.
this.updateCSSProperties();
}
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
if (!this.#hasRecordedInitialProperties) {
(this.constructor as typeof WebAwesomeElement).elementProperties.forEach(
@@ -135,21 +111,6 @@ export default class WebAwesomeElement extends LitElement {
}
}
updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties);
let Self = this.constructor as typeof WebAwesomeElement;
if (Self.cssAttributeProperties.size > 0) {
for (let [name] of changedProperties) {
if (typeof name === 'string' && this.#setVia[name] === 'css' && !this.#setting.has(name)) {
// A property is being set via JS and its NOT because we're reflecting a CSS property
this.#setVia[name] = 'js';
}
}
}
}
/** Checks if states are supported by the element */
private hasStatesSupport(): boolean {
return Boolean(this.internals?.states);
@@ -187,69 +148,4 @@ export default class WebAwesomeElement extends LitElement {
hasCustomState(state: string): boolean {
return this.hasStatesSupport() ? this.internals.states.has(state) : false;
}
protected updateCSSProperties() {
const Self = this.constructor as typeof WebAwesomeElement;
if (Self.cssAttributeProperties.size === 0) {
return;
}
if (!Self.styleObserver) {
// First time, init stuff
// First, replace `true` with actual CSS property names
for (let [name, cssProperty] of Self.cssAttributeProperties) {
if (cssProperty === true) {
// Default name
cssProperty = `--${this.tagName.toLowerCase()}-${name}`;
Self.cssAttributeProperties.set(name, cssProperty);
}
}
// Then we observe them
let cssProperties = [...Self.cssAttributeProperties.values()] as string[];
Self.styleObserver = new CSSStyleObserver(cssProperties, (...args) => {
console.log(...args);
this.updateCSSProperties();
});
}
this.#computedStyle ??= getComputedStyle(this);
const tagName = this.tagName.toLowerCase();
for (let [name, cssProperty] of Self.cssAttributeProperties) {
if (typeof name === 'string' && !this.hasAttribute(name) && this.#setVia[name] !== 'js') {
// Check if supplied as a CSS custom property
// TODO !important should override attribute values
cssProperty = cssProperty === true ? `--${tagName}-${name}` : cssProperty;
const value = this.#computedStyle?.getPropertyValue(cssProperty);
if (value && value !== 'auto') {
this.#setVia[name] = 'css';
this.#setting.add(name);
// @ts-ignore
this[name] = value.trim();
this.updateComplete.then(() => {
this.#setting.delete(name);
});
}
}
}
}
// Subclasses will get their own copy automagically (see below)
protected static cssAttributeProperties = new Map<PropertyKey, true | string>();
protected static styleObserver: CSSStyleObserver | undefined;
static createProperty(name: PropertyKey, options?: PropertyDeclaration): void {
super.createProperty(name, options);
if (options?.cssProperty) {
if (this.cssAttributeProperties === WebAwesomeElement.cssAttributeProperties) {
// Each class needs its own, otherwise they'd share the same object
this.cssAttributeProperties = new Map();
}
this.cssAttributeProperties.set(name, options.cssProperty);
}
}
}

View File

@@ -2,12 +2,11 @@ input[type='color'] {
display: block;
border-radius: calc(infinity * 1px);
background: transparent;
padding: 3px;
padding: var(--wa-form-control-border-width);
width: calc(var(--wa-form-control-height) - 2px);
height: calc(var(--wa-form-control-height) - 2px);
border: var(--wa-border-width-s) var(--wa-border-style) var(--wa-form-control-border-color);
border: var(--wa-form-control-border-width) var(--wa-border-style) var(--wa-form-control-border-color);
cursor: pointer;
margin-block-start: 3px;
forced-color-adjust: none;
&::-webkit-color-swatch-wrapper {

View File

@@ -1,41 +1,45 @@
select,
label:has(select),
:host {
--background-color: var(--wa-form-control-background-color);
--border-color: var(--wa-form-control-border-color);
--border-radius: var(--wa-form-control-border-radius);
--border-style: var(--wa-form-control-border-style);
--border-width: var(--wa-form-control-border-width);
--box-shadow: initial;
/* Defaults for root element. */
--outlined-background-color: var(--wa-form-control-background-color);
--outlined-border-color: var(--wa-form-control-border-color);
--outlined-text-color: var(--wa-form-control-value-color);
:where(&) {
/* Defaults with 0 specificity.
* Do NOT reset --background-color and --border-color here so they trickle in from the appearance utils
* Instead we provide the fallback when setting
*/
--border-width: var(--wa-form-control-border-width);
--box-shadow: initial;
}
}
select,
:host [part~='combobox'] {
background-color: var(--background-color);
border-color: var(--border-color);
border-radius: var(--border-radius);
border-style: var(--border-style);
background-color: var(--background-color, var(--wa-form-control-background-color));
border-color: var(--border-color, var(--wa-form-control-border-color));
border-radius: var(--wa-form-control-border-radius);
border-style: var(--wa-form-control-border-style);
border-width: var(--border-width);
box-shadow: var(--box-shadow);
width: 100%;
min-width: 0;
position: relative;
color: var(--wa-form-control-value-color);
font-size: var(--wa-size);
cursor: pointer;
font-family: inherit;
font-size: var(--wa-size);
font-weight: var(--wa-form-control-value-font-weight);
line-height: var(--wa-form-control-value-line-height);
vertical-align: middle;
min-width: 0;
overflow: hidden;
cursor: pointer;
padding: var(--wa-space-smaller) var(--wa-space);
position: relative;
vertical-align: middle;
width: 100%;
transition:
background var(--wa-transition-normal),
background-color var(--wa-transition-normal),
border var(--wa-transition-normal),
box-shadow var(--wa-transition-normal),
color var(--wa-transition-normal),
outline var(--wa-transition-fast);
transition-timing-function: var(--wa-transition-easing);
padding: var(--wa-space-smaller) var(--wa-space);
}
/* Add ellipses to multi select options */

View File

@@ -247,10 +247,13 @@ wa-badge {
text-transform: uppercase;
}
:is(button, input:where([type='button'], [type='reset'], [type='submit']), wa-button, .wa-button):not(
[appearance='plain'],
.wa-plain
) {
:is(
button,
input:where([type='button'], [type='reset'], [type='submit']),
wa-button,
wa-radio-group > wa-radio-button,
.wa-button
):not([appearance='plain'], .wa-plain) {
--wa-transition-slow: 0;
--wa-transition-normal: 0;
--wa-transition-fast: 0;
@@ -269,7 +272,8 @@ wa-badge {
--background-color: var(--wa-color-surface-default);
}
&:not([disabled], [loading]):active {
&:not([disabled], [loading]):active,
&::part(checked) {
box-shadow: none;
transform: translateY(var(--wa-shadow-offset-y-s));
}
@@ -308,18 +312,12 @@ wa-checkbox {
}
wa-radio-group > wa-radio-button {
--background-color-active: var(--border-color);
--border-color-active: var(--background-color-active);
--box-shadow: var(--wa-shadow-offset-x-s) var(--wa-shadow-offset-y-s) var(--wa-shadow-blur-s) var(--border-color);
--label-color-active: var(--wa-color-surface-default);
&:active,
&[checked] {
&::part(base):active,
&::part(checked) {
--background-color: var(--border-color);
--background-color-hover: var(--border-color);
--border-color: var(--wa-color-neutral-border-loud);
--box-shadow: none;
--label-color: var(--wa-color-surface-default);
transform: translate(var(--wa-shadow-offset-x-s), var(--wa-shadow-offset-y-s));
--text-color: var(--wa-color-surface-default);
}
}