Revamp button styles

- More style sharing between native buttons & button component
- Fix radio button styling
- Better ability to style button from the outside (without `::part(base)`)
- Orthogonal variant + appearance
This commit is contained in:
Lea Verou
2024-12-17 02:02:30 -05:00
parent 426a242d26
commit 27c58637eb
6 changed files with 282 additions and 485 deletions

View File

@@ -1,409 +1,106 @@
:host {
display: inline-block;
display: contents;
position: relative;
width: auto;
cursor: pointer;
}
/*
* Filled buttons
*/
:host([appearance='filled'][variant='neutral']) {
--background-color: var(--wa-color-neutral-fill-loud);
--label-color: var(--wa-color-neutral-on-loud);
}
:host([appearance='filled'][variant='brand']) {
--background-color: var(--wa-color-brand-fill-loud);
--label-color: var(--wa-color-brand-on-loud);
}
:host([appearance='filled'][variant='success']) {
--background-color: var(--wa-color-success-fill-loud);
--label-color: var(--wa-color-success-on-loud);
}
:host([appearance='filled'][variant='warning']) {
--background-color: var(--wa-color-warning-fill-loud);
--label-color: var(--wa-color-warning-on-loud);
}
:host([appearance='filled'][variant='danger']) {
--background-color: var(--wa-color-danger-fill-loud);
--label-color: var(--wa-color-danger-on-loud);
}
/*
* Tinted buttons
*/
:host([appearance='tinted']) {
--background-color-hover: color-mix(in oklab, var(--background-color), transparent 10%);
--background-color-active: color-mix(in oklab, var(--background-color), transparent 20%);
}
:host([appearance='tinted'][variant='neutral']) {
--background-color: var(--wa-color-neutral-fill-normal);
--label-color: var(--wa-color-neutral-on-normal);
}
:host([appearance='tinted'][variant='brand']) {
--background-color: var(--wa-color-brand-fill-normal);
--label-color: var(--wa-color-brand-on-normal);
}
:host([appearance='tinted'][variant='success']) {
--background-color: var(--wa-color-success-fill-normal);
--label-color: var(--wa-color-success-on-normal);
}
:host([appearance='tinted'][variant='warning']) {
--background-color: var(--wa-color-warning-fill-normal);
--label-color: var(--wa-color-warning-on-normal);
}
:host([appearance='tinted'][variant='danger']) {
--background-color: var(--wa-color-danger-fill-normal);
--label-color: var(--wa-color-danger-on-normal);
}
/*
* Outlined buttons
*/
:host([appearance='outlined']),
:host(.wa-button-group__button--radio:not([checked])) {
--background-color: transparent;
--background-color-active: color-mix(in oklab, var(--background-color-hover), transparent 20%);
}
:host([appearance='outlined'][variant='neutral']),
:host(.wa-button-group__button--radio:not([checked])) {
--background-color-hover: var(--wa-color-neutral-fill-quiet);
--border-color: var(--wa-color-neutral-border-loud);
--label-color: var(--wa-color-neutral-on-quiet);
}
:host([appearance='outlined'][variant='brand']) {
--background-color-hover: var(--wa-color-brand-fill-quiet);
--border-color: var(--wa-color-brand-border-loud);
--label-color: var(--wa-color-brand-on-quiet);
}
:host([appearance='outlined'][variant='success']) {
--background-color-hover: var(--wa-color-success-fill-quiet);
--border-color: var(--wa-color-success-border-loud);
--label-color: var(--wa-color-success-on-quiet);
}
:host([appearance='outlined'][variant='warning']) {
--background-color-hover: var(--wa-color-warning-fill-quiet);
--border-color: var(--wa-color-warning-border-loud);
--label-color: var(--wa-color-warning-on-quiet);
}
:host([appearance='outlined'][variant='danger']) {
--background-color-hover: var(--wa-color-danger-fill-quiet);
--border-color: var(--wa-color-danger-border-loud);
--label-color: var(--wa-color-danger-on-quiet);
}
/*
* Text buttons
*/
:host([appearance='text']) {
--background-color: transparent;
--background-color-active: color-mix(in oklab, var(--background-color-hover), transparent 20%);
}
:host([appearance='text'][variant='neutral']) {
--label-color: var(--wa-color-neutral-on-quiet);
--background-color-hover: var(--wa-color-neutral-fill-quiet);
}
:host([appearance='text'][variant='brand']) {
--label-color: var(--wa-color-brand-on-quiet);
--background-color-hover: var(--wa-color-brand-fill-quiet);
}
:host([appearance='text'][variant='success']) {
--label-color: var(--wa-color-success-on-quiet);
--background-color-hover: var(--wa-color-success-fill-quiet);
}
:host([appearance='text'][variant='warning']) {
--label-color: var(--wa-color-warning-on-quiet);
--background-color-hover: var(--wa-color-warning-fill-quiet);
}
:host([appearance='text'][variant='danger']) {
--label-color: var(--wa-color-danger-on-quiet);
--background-color-hover: var(--wa-color-danger-fill-quiet);
}
/*
* Checked buttons
*/
:host([checked]) {
--background-color: var(--wa-color-brand-fill-quiet);
--background-color-hover: var(--background-color);
--border-color: var(--wa-form-control-activated-color);
--label-color: var(--wa-color-brand-on-normal);
--indicator-color: var(--border-color);
--indicator-width: var(--wa-border-width-s);
}
/*
* Internal
*/
.button {
:where([part~='base']) {
all: inherit;
display: inline-flex;
align-items: stretch;
justify-content: center;
width: 100%;
}
.button--checked {
box-shadow:
var(--box-shadow, 0 0 transparent),
inset 0 0 0 var(--indicator-width) var(--indicator-color);
}
/*
* States
*/
* Label
*/
.button::-moz-focus-inner {
border: 0;
}
.button:focus {
outline: none;
}
.button:focus-visible {
outline: var(--wa-focus-ring);
outline-offset: var(--wa-focus-ring-offset);
}
.button--disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* When disabled, prevent mouse events from bubbling up from children */
.button--disabled * {
pointer-events: none;
}
@media (forced-colors: active) {
.button.button--outlined.button--checked:not(.button--disabled) {
outline: solid 2px transparent;
}
}
/*
* Label
*/
.button__prefix,
.button__suffix {
.prefix,
.suffix {
flex: 0 0 auto;
display: flex;
align-items: center;
pointer-events: none;
}
.button__label {
.label {
display: inline-block;
}
.button__label::slotted(wa-icon) {
.label::slotted(wa-icon) {
vertical-align: -2px;
}
/*
* Size modifiers
*/
* Caret modifier
*/
.button--small {
font-size: var(--wa-font-size-s);
height: var(--wa-form-control-height-s);
line-height: calc(var(--wa-form-control-height-s) - var(--border-width) * 2);
}
.button--medium {
font-size: var(--wa-font-size-m);
height: var(--wa-form-control-height-m);
line-height: calc(var(--wa-form-control-height-m) - var(--border-width) * 2);
}
.button--large {
font-size: var(--wa-font-size-l);
height: var(--wa-form-control-height-l);
line-height: calc(var(--wa-form-control-height-l) - var(--border-width) * 2);
}
/*
* Pill modifier
*/
.button--pill {
border-radius: var(--wa-border-radius-pill);
}
/*
* Caret modifier
*/
.button--caret .button__suffix {
display: none;
}
.button--caret .button__caret {
wa-icon[part~='caret'] {
display: flex;
align-self: center;
align-items: center;
}
.button--caret .button__caret::part(svg) {
width: 0.875em;
height: 0.875em;
&::part(svg) {
width: 0.875em;
height: 0.875em;
}
.wa-button:has(&) .suffix {
display: none;
}
}
/*
* Loading modifier
*/
* Loading modifier
*/
.button--loading {
.loading {
position: relative;
cursor: wait;
}
.button--loading .button__prefix,
.button--loading .button__label,
.button--loading .button__suffix,
.button--loading .button__caret {
visibility: hidden;
}
.prefix,
.label,
.suffix,
.caret {
visibility: hidden;
}
.button--loading wa-spinner {
--indicator-color: currentColor;
--track-color: color-mix(in oklab, currentColor, transparent 90%);
position: absolute;
font-size: 1em;
height: 1em;
width: 1em;
top: calc(50% - 0.5em);
left: calc(50% - 0.5em);
wa-spinner {
--indicator-color: currentColor;
--track-color: color-mix(in oklab, currentColor, transparent 90%);
position: absolute;
font-size: 1em;
height: 1em;
width: 1em;
top: calc(50% - 0.5em);
left: calc(50% - 0.5em);
}
}
/*
* Badges
*/
* Badges
*/
.button ::slotted(wa-badge) {
--border-color: var(--wa-color-surface-default);
button ::slotted(wa-badge) {
border-color: var(--wa-color-surface-default);
position: absolute;
top: 0;
right: 0;
inset-block-start: 0;
inset-inline-end: 0;
translate: 50% -50%;
pointer-events: none;
}
.button--rtl ::slotted(wa-badge) {
right: auto;
left: 0;
:host(:dir(rtl)) ::slotted(wa-badge) {
translate: -50% -50%;
}
/*
* Button spacing
*/
* Button spacing
*/
.button--small {
padding: 0 var(--wa-space-s);
slot[name='prefix']::slotted(*) {
margin-inline-end: var(--space);
}
.button--medium {
padding: 0 var(--wa-space-m);
}
.button--large {
padding: 0 var(--wa-space-l);
}
.button--small ::slotted([slot='prefix']) {
margin-inline-end: var(--wa-space-s);
}
.button--medium ::slotted([slot='prefix']) {
margin-inline-end: var(--wa-space-m);
}
.button--large ::slotted([slot='prefix']) {
margin-inline-end: var(--wa-space-l);
}
.button--small ::slotted([slot='suffix']),
.button--small.button--caret:not(.button--visually-hidden-label) .button__caret {
margin-inline-start: var(--wa-space-s);
}
.button--medium ::slotted([slot='suffix']),
.button--medium.button--caret:not(.button--visually-hidden-label) .button__caret {
margin-inline-start: var(--wa-space-m);
}
.button--large ::slotted([slot='suffix']),
.button--large.button--caret:not(.button--visually-hidden-label) .button__caret {
margin-inline-start: var(--wa-space-l);
}
/*
* Button groups support a variety of button types (e.g. buttons with tooltips, buttons as dropdown triggers, etc.).
* This means buttons aren't always direct descendants of the button group, thus we can't target them with the
* ::slotted selector. To work around this, the button group component does some magic to add these special classes to
* buttons and we style them here instead.
*/
/*
:host([data-button-group-middle]) #button {
border-radius: 0;
}
:host([data-button-group-horizontal][data-button-group-first]) #button {
border-start-end-radius: 0;
border-end-end-radius: 0;
}
:host([data-button-group-horizontal][data-button-group-last]) #button {
border-start-start-radius: 0;
border-end-start-radius: 0;
}
:host([data-button-group-vertical][data-button-group-first]) #button {
border-end-start-radius: 0;
border-end-end-radius: 0;
}
:host([data-button-group-vertical][data-button-group-last]) #button {
border-start-start-radius: 0;
border-start-end-radius: 0;
}
*/
/* Bump hovered, focused, and checked buttons up so their focus ring isn't clipped */
:host(.wa-button-group__button--hover) {
z-index: 1;
}
/* Focus and checked are always on top */
:host(.wa-button-group__button--focus),
:host(.wa-button-group__button[checked]) {
z-index: 2;
slot[name='suffix']::slotted(*),
.wa-button:not(.visually-hidden-label) [part~='caret'] {
margin-inline-start: var(--space);
}

View File

@@ -9,6 +9,8 @@ import { MirrorValidator } from '../../internal/validators/mirror-validator.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/button.css';
import sizeStyles from '../../styles/shadow/size.css';
import variantStyles from '../../styles/utilities/variants.css';
import { LocalizeController } from '../../utilities/localize.js';
import '../icon/icon.js';
import '../spinner/spinner.js';
@@ -38,23 +40,19 @@ import styles from './button.css';
* @csspart caret - The button's caret icon, a `<wa-icon>` element.
* @csspart spinner - The spinner that shows when the button is in the loading state.
*
* @cssproperty --background-color - The button's background color.
* @cssproperty --background-color - The button's background color when the button is not being interacted with.
* @cssproperty --background-color-active - The button's background color when active.
* @cssproperty --background-color-hover - The button's background color on hover.
* @cssproperty --border-color - The color of the button's border.
* @cssproperty --border-color - The color of the button's border when the button is not being interacted with.
* @cssproperty --border-color-active - The color of the button's border when active.
* @cssproperty --border-color-hover - The color of the button's border on hover.
* @cssproperty --border-radius - The radius of the button's corners.
* @cssproperty --border-style - The style of the button's border.
* @cssproperty --border-width - The width of the button's border. Expects a single value.
* @cssproperty --box-shadow - The shadow effects around the edges of the button.
* @cssproperty --label-color - The color of the button's label.
* @cssproperty --label-color - The color of the button's label when the button is not being interacted with.
* @cssproperty --label-color-active - The color of the button's label when active.
* @cssproperty --label-color-hover - The color of the button's label on hover.
*/
@customElement('wa-button')
export default class WaButton extends WebAwesomeFormAssociatedElement {
static shadowStyle = [nativeStyles, styles];
static shadowStyle = [variantStyles, sizeStyles, nativeStyles, styles];
static get validators() {
return [...super.validators, MirrorValidator()];
@@ -63,7 +61,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
assumeInteractionOn = ['click'];
private readonly localize = new LocalizeController(this);
@query('.button') button: HTMLButtonElement | HTMLLinkElement;
@query('.wa-button') button: HTMLButtonElement | HTMLLinkElement;
@state() private hasFocus = false;
@state() visuallyHiddenLabel = false;
@@ -236,6 +234,17 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
this.button.blur();
}
getBoundingClientRect(): DOMRect {
let rect = super.getBoundingClientRect();
let buttonRect = this.button.getBoundingClientRect();
if (rect.width === 0 && buttonRect.width > 0) {
return buttonRect;
}
return rect;
}
render() {
const isLink = this.isLink();
const tag = isLink ? literal`a` : literal`button`;
@@ -247,25 +256,13 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
part="base"
class=${classMap({
button: true,
'button--brand': this.variant === 'brand',
'button--success': this.variant === 'success',
'button--neutral': this.variant === 'neutral',
'button--warning': this.variant === 'warning',
'button--danger': this.variant === 'danger',
'button--small': this.size === 'small',
'button--medium': this.size === 'medium',
'button--large': this.size === 'large',
'button--caret': this.caret,
'button--disabled': this.disabled,
'button--focused': this.hasFocus,
'button--loading': this.loading,
'button--filled': this.appearance === 'filled',
'button--tinted': this.appearance === 'tinted',
'button--outlined': this.appearance === 'outlined',
'button--text': this.appearance === 'text',
'button--pill': this.pill,
'button--rtl': this.localize.dir() === 'rtl',
'button--visually-hidden-label': this.visuallyHiddenLabel,
'wa-button': true,
caret: this.caret,
disabled: this.disabled,
focused: this.hasFocus,
loading: this.loading,
rtl: this.localize.dir() === 'rtl',
'visually-hidden-label': this.visuallyHiddenLabel,
})}
?disabled=${ifDefined(isLink ? undefined : this.disabled)}
type=${ifDefined(isLink ? undefined : this.type)}
@@ -284,27 +281,19 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
@invalid=${this.isButton() ? this.handleInvalid : null}
@click=${this.handleClick}
>
<slot name="prefix" part="prefix" class="button__prefix"></slot>
<slot part="label" class="button__label" @slotchange=${this.handleLabelSlotChange}></slot>
<slot name="suffix" part="suffix" class="button__suffix"></slot>
<slot name="prefix" part="prefix" class="prefix"></slot>
<slot part="label" class="label" @slotchange=${this.handleLabelSlotChange}></slot>
<slot name="suffix" part="suffix" class="suffix"></slot>
${
this.caret
? html`
<wa-icon
part="caret"
class="button__caret"
library="system"
name="chevron-down"
variant="solid"
></wa-icon>
<wa-icon part="caret" class="caret" library="system" name="chevron-down" variant="solid"></wa-icon>
`
: ''
}
${this.loading ? html`<wa-spinner part="spinner"></wa-spinner>` : ''}
</${tag}>
`;
/* eslint-enable lit/no-invalid-html */
/* eslint-enable lit/binding-positions */
}
}
declare global {

View File

@@ -1,6 +1,15 @@
.button__prefix,
.button__suffix,
.button__label {
:host {
display: contents;
}
:where([part~='base']) {
all: inherit;
display: inline-flex;
}
.prefix,
.suffix,
.label {
display: inline-flex;
position: relative;
align-items: center;
@@ -19,3 +28,20 @@
opacity: 0;
z-index: -1;
}
/*
* Checked buttons
*/
:host([checked]) {
--background-color: var(--wa-color-brand-fill-quiet);
--background-color-hover: var(--background-color);
--border-color: var(--wa-form-control-activated-color);
--label-color: var(--wa-color-brand-on-normal);
--indicator-color: var(--border-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);
}

View File

@@ -7,6 +7,9 @@ import { WaFocusEvent } from '../../events/focus.js';
import { HasSlotController } from '../../internal/slot.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/button.css';
import sizeStyles from '../../styles/shadow/size.css';
import variantStyles from '../../styles/utilities/variants.css';
import buttonStyles from '../button/button.css';
import styles from './radio-button.css';
@@ -39,20 +42,19 @@ import styles from './radio-button.css';
* @cssproperty --label-color-active - The color of the button's label when active.
* @cssproperty --label-color-hover - The color of the button's label on hover.
*
* @csspart base - The component's base wrapper.
* @csspart button - The internal `<button>` element.
* @csspart button--checked - The internal button element when the radio button is checked.
* @csspart base - The internal `<button>` element.
* @csspart checked - The internal button element when the radio button is checked.
* @csspart prefix - The container that wraps the prefix.
* @csspart label - The container that wraps the radio button's label.
* @csspart suffix - The container that wraps the suffix.
*/
@customElement('wa-radio-button')
export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
static shadowStyle = [buttonStyles, styles];
static shadowStyle = [variantStyles, sizeStyles, nativeStyles, buttonStyles, styles];
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
@query('.button') input: HTMLInputElement;
@query('button') input: HTMLButtonElement;
@query('.hidden-input') hiddenInput: HTMLInputElement;
@state() protected hasFocus = false;
@@ -147,39 +149,33 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
const hasSuffix = this.hasUpdated ? this.hasSlotController.test('suffix') : this.withSuffix;
return html`
<div part="base" role="presentation">
<button
part="${`button${this.checked ? ' button--checked' : ''}`}"
role="radio"
aria-checked="${this.checked}"
class=${classMap({
button: true,
'button--neutral': !this.checked,
'button--brand': this.checked,
'button--small': this.size === 'small',
'button--medium': this.size === 'medium',
'button--large': this.size === 'large',
'button--checked': this.checked,
'button--disabled': this.disabled,
'button--focused': this.hasFocus,
'button--outlined': true,
'button--pill': this.pill,
'button--has-label': hasLabel,
'button--has-prefix': hasPrefix,
'button--has-suffix': hasSuffix,
})}
aria-disabled=${this.disabled}
type="button"
value=${ifDefined(this.value)}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@click=${this.handleClick}
>
<slot name="prefix" part="prefix" class="button__prefix"></slot>
<slot part="label" class="button__label"></slot>
<slot name="suffix" part="suffix" class="button__suffix"></slot>
</button>
</div>
<button
part="base${this.checked ? ' checked' : ''}"
role="radio"
aria-checked="${this.checked}"
class=${classMap({
'wa-neutral': !this.checked,
'wa-brand': this.checked,
disabled: this.disabled,
focused: this.hasFocus,
'wa-outlined': true,
'wa-tinted': this.checked,
'wa-pill': this.pill,
'has-label': hasLabel,
'has-prefix': hasPrefix,
'has-suffix': hasSuffix,
})}
aria-disabled=${this.disabled}
type="button"
value=${ifDefined(this.value)}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@click=${this.handleClick}
>
<slot name="prefix" part="prefix" class="prefix"></slot>
<slot part="label" class="label"></slot>
<slot name="suffix" part="suffix" class="suffix"></slot>
</button>
`;
}
}

View File

@@ -1,59 +1,142 @@
:is(button, input:is([type='button'], [type='reset'], [type='submit'])):where(:not(:host button)),
:host {
--background-color: var(--wa-color-neutral-fill-loud);
:is(button, input:where([type='button'], [type='reset'], [type='submit'])):where(:not(:host *, .wa-off, .wa-off *)),
:host,
.wa-button:where(:not([part~='base'])) {
--background-color: var(--color-fill-loud, var(--wa-color-neutral-fill-loud));
--background-color-hover: color-mix(in oklab, var(--background-color), var(--wa-color-mix-hover));
--background-color-active: color-mix(in oklab, var(--background-color), var(--wa-color-mix-active));
--border-color: initial;
--border-color: transparent;
--border-color-hover: var(--border-color);
--border-color-active: var(--border-color);
--border-radius: var(--wa-form-control-border-radius);
--border-style: var(--wa-border-style);
--border-width: var(--wa-form-control-border-width);
--box-shadow: initial;
--label-color: var(--wa-color-neutral-on-loud);
--border-width: max(1px, var(--wa-form-control-border-width));
--label-color: var(--color-on-loud, var(--wa-color-neutral-on-loud));
--label-color-hover: var(--label-color);
--label-color-active: var(--label-color);
}
button,
input:is([type='button'], [type='reset'], [type='submit']),
:host([href]) a {
background-color: var(--background-color);
border-color: var(--border-color, var(--background-color));
border-radius: var(--border-radius);
border-style: var(--border-style);
border-width: max(1px, var(--border-width));
box-shadow: var(--box-shadow);
color: var(--label-color);
cursor: pointer;
font-family: inherit;
font-size: var(--wa-font-size-m);
font-weight: var(--wa-font-weight-action);
height: var(--wa-form-control-height-m);
line-height: calc(var(--wa-form-control-height-m) - var(--border-width) * 2);
padding: 0 var(--wa-space-m);
border-radius: var(--wa-form-control-border-radius);
border-style: var(--wa-border-style);
border-width: var(--border-width);
align-items: center;
justify-content: center;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
white-space: nowrap;
vertical-align: middle;
transition:
background var(--wa-transition-fast) var(--wa-transition-easing),
border var(--wa-transition-fast) var(--wa-transition-easing),
box-shadow var(--wa-transition-fast) var(--wa-transition-easing),
color var(--wa-transition-fast) var(--wa-transition-easing);
transition-property: background, border, box-shadow, color;
transition-duration: var(--wa-transition-fast);
transition-timing-function: var(--wa-transition-easing);
cursor: pointer;
padding: 0 var(--space, var(--wa-space-m));
font-family: inherit;
font-size: var(--size, var(--wa-font-size-m));
font-weight: var(--wa-font-weight-action);
--form-control-height: calc(
2 * var(--space-smaller, var(--wa-space-s)) + 1em * var(--wa-form-control-value-line-height)
);
height: var(--form-control-height);
line-height: calc(var(--form-control-height) - var(--border-width) * 2);
}
:is(button, input:is([type='button'], [type='reset'], [type='submit'])):where(:not(:host button)):hover,
:is(:host button, :host([href]) a):hover:not(.button--disabled, .button--loading) {
background-color: var(--background-color-hover, var(--background-color));
border-color: var(--border-color-hover, var(--border-color, var(--background-color-hover)));
color: var(--label-color-hover, var(--label-color));
button,
input:is([type='button'], [type='reset'], [type='submit']),
.wa-button:not([part~='base']) {
display: inline-flex;
background-color: var(--background-color);
border-color: var(--border-color, var(--background-color));
color: var(--label-color);
/* Interactive */
&:where(:not(:disabled, [disabled], :host([disabled]) *, .loading)) {
&:hover {
background-color: var(--background-color-hover, var(--background-color));
border-color: var(--border-color-hover, var(--border-color, var(--background-color-hover)));
color: var(--label-color-hover, var(--label-color));
}
&:active {
background-color: var(--background-color-active, var(--background-color));
border-color: var(--border-color-active, var(--border-color, var(--background-color-active)));
color: var(--label-color-active, var(--label-color));
}
}
/*
* States
*/
&::-moz-focus-inner {
border: 0;
}
&:focus {
outline: none;
}
&:focus-visible {
outline: var(--wa-focus-ring);
outline-offset: var(--wa-focus-ring-offset);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
/* When disabled, prevent mouse events from bubbling up from children */
* {
pointer-events: none;
}
}
}
:is(button, input:is([type='button'], [type='reset'], [type='submit'])):where(:not(:host button)):active,
:is(:host button, :host([href]) a):active:not(.button--disabled, .button--loading) {
background-color: var(--background-color-active, var(--background-color));
border-color: var(--border-color-active, var(--border-color, var(--background-color-active)));
color: var(--label-color-active, var(--label-color));
/**
* Utilities
*/
/* Styles */
.wa-outlined,
:host([appearance~='outlined']) {
--background-color: transparent;
--background-color-hover: var(--color-fill-quiet);
--background-color-active: color-mix(in oklab, var(--background-color-hover), transparent 20%);
--border-color: var(--color-border-loud);
--border-color-hover: var(--color-border-loud);
--label-color: var(--color-on-quiet);
--label-color-hover: var(--color-on-quiet);
}
.wa-filled,
:host([appearance~='filled']) {
--background-color: var(--color-fill-loud, var(--wa-color-neutral-fill-loud));
--background-color-hover: color-mix(in oklab, var(--background-color), var(--wa-color-mix-hover));
--background-color-active: color-mix(in oklab, var(--background-color), var(--wa-color-mix-active));
--label-color: var(--color-on-loud, var(--wa-color-neutral-on-loud));
}
.wa-tinted,
:host([appearance~='tinted']) {
--background-color: var(--color-fill-normal, var(--wa-color-neutral-fill-normal));
--label-color: var(--color-on-normal, var(--wa-color-neutral-on-normal));
--background-color-hover: color-mix(in oklab, var(--background-color), transparent 10%);
--background-color-active: color-mix(in oklab, var(--background-color), transparent 20%);
}
.wa-text,
:host([appearance~='text']) {
--background-color: transparent;
--label-color: var(--color-on-quiet);
--background-color-hover: var(--color-fill-quiet);
--background-color-active: color-mix(in oklab, var(--background-color-hover), transparent 20%);
}
.wa-pill,
:host([pill]) {
border-radius: var(--wa-border-radius-pill);
}

View File

@@ -1,4 +1,6 @@
.wa-neutral {
.wa-neutral,
:host([variant='neutral']),
:root {
--color-fill-loud: var(--wa-color-neutral-fill-loud);
--color-fill-normal: var(--wa-color-neutral-fill-normal);
--color-fill-quiet: var(--wa-color-neutral-fill-quiet);
@@ -12,7 +14,8 @@
--color-on-quiet: var(--wa-color-neutral-on-quiet);
}
.wa-brand {
.wa-brand,
:host([variant='brand']) {
--color-fill-loud: var(--wa-color-brand-fill-loud);
--color-fill-normal: var(--wa-color-brand-fill-normal);
--color-fill-quiet: var(--wa-color-brand-fill-quiet);
@@ -26,7 +29,8 @@
--color-on-quiet: var(--wa-color-brand-on-quiet);
}
.wa-success {
.wa-success,
:host([variant='success']) {
--color-fill-loud: var(--wa-color-success-fill-loud);
--color-fill-normal: var(--wa-color-success-fill-normal);
--color-fill-quiet: var(--wa-color-success-fill-quiet);
@@ -40,7 +44,8 @@
--color-on-quiet: var(--wa-color-success-on-quiet);
}
.wa-warning {
.wa-warning,
:host([variant='warning']) {
--color-fill-loud: var(--wa-color-warning-fill-loud);
--color-fill-normal: var(--wa-color-warning-fill-normal);
--color-fill-quiet: var(--wa-color-warning-fill-quiet);
@@ -54,7 +59,8 @@
--color-on-quiet: var(--wa-color-warning-on-quiet);
}
.wa-danger {
.wa-danger,
:host([variant='danger']) {
--color-fill-loud: var(--wa-color-danger-fill-loud);
--color-fill-normal: var(--wa-color-danger-fill-normal);
--color-fill-quiet: var(--wa-color-danger-fill-quiet);