mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user