Define button-group util and use it in button-group and radio-group

This commit is contained in:
Lea Verou
2024-12-17 01:57:48 -05:00
parent 7a7a7abe78
commit 426a242d26
6 changed files with 76 additions and 105 deletions

View File

@@ -1,19 +1,3 @@
:host {
display: inline-flex;
}
.button-group {
display: flex;
position: relative;
flex-wrap: wrap;
isolation: isolate;
}
:host([orientation='vertical']) .button-group {
flex-direction: column;
}
/* Show the focus indicator above other buttons */
::slotted(:focus) {
z-index: 1 !important;
}

View File

@@ -1,6 +1,7 @@
import { html } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import buttonGroupStyles from '../../styles/utilities/button-group.css';
import styles from './button-group.css';
/**
@@ -15,7 +16,7 @@ import styles from './button-group.css';
*/
@customElement('wa-button-group')
export default class WaButtonGroup extends WebAwesomeElement {
static shadowStyle = styles;
static shadowStyle = [buttonGroupStyles, styles];
@query('slot') defaultSlot: HTMLSlotElement;
@@ -82,20 +83,19 @@ export default class WaButtonGroup extends WebAwesomeElement {
}
render() {
// eslint-disable-next-line lit-a11y/mouse-events-have-key-events
return html`
<div
<slot
part="base"
class="button-group"
class="wa-button-group"
role="${this.disableRole ? 'presentation' : 'group'}"
aria-label=${this.label}
aria-orientation=${this.orientation}
@focusout=${this.handleBlur}
@focusin=${this.handleFocus}
@mouseover=${this.handleMouseOver}
@mouseout=${this.handleMouseOut}
>
<slot @slotchange=${this.handleSlotChange}></slot>
</div>
@slotchange=${this.handleSlotChange}
></slot>
`;
}
}

View File

@@ -397,76 +397,6 @@
}
*/
:host(.wa-button-group__button--inner) .button {
border-radius: 0;
}
:host(.wa-button-group-horizontal.wa-button-group__button--first:not(.wa-button-group__button--last)) .button {
border-start-end-radius: 0;
border-end-end-radius: 0;
}
:host(.wa-button-group-horizontal.wa-button-group__button--last:not(.wa-button-group__button--first)) .button {
border-start-start-radius: 0;
border-end-start-radius: 0;
}
:host(.wa-button-group-vertical.wa-button-group__button--first:not(.wa-button-group__button--last)) .button {
border-end-start-radius: 0;
border-end-end-radius: 0;
}
:host(.wa-button-group-vertical.wa-button-group__button--last:not(.wa-button-group__button--first)) .button {
border-start-start-radius: 0;
border-start-end-radius: 0;
}
/* All except the first */
:host(
.wa-button-group-horizontal.wa-button-group-horizontal.wa-button-group__button:not(.wa-button-group__button--first)
) {
margin-inline-start: calc(-1 * var(--border-width));
}
:host(
.wa-button-group-vertical.wa-button-group-horizontal.wa-button-group__button:not(.wa-button-group__button--first)
) {
margin-block-start: calc(-1 * var(--border-width));
}
/* Add a visual separator between filled buttons */
:host(.wa-button-group__button:not(.wa-button-group__button--first, .wa-button-group__button--radio)) .button:after {
content: '';
position: absolute;
z-index: 2; /* Keep separators visible on hover */
}
:host(
.wa-button-group-horizontal.wa-button-group__button:not(
.wa-button-group__button--first,
.wa-button-group__button--radio
)
)
.button:after {
top: 0;
bottom: 0;
inset-inline-start: 0;
border-left: solid max(var(--border-width), 1px) var(--border-color, rgb(0 0 0 / 0.3));
}
:host(
.wa-button-group-vertical.wa-button-group__button:not(
.wa-button-group__button--first,
.wa-button-group__button--radio
)
)
.button:after {
left: 0;
right: 0;
inset-block-start: 0;
border-top: solid max(var(--border-width), 1px) var(--border-color, rgb(0 0 0 / 0.3));
}
/* Bump hovered, focused, and checked buttons up so their focus ring isn't clipped */
:host(.wa-button-group__button--hover) {
z-index: 1;

View File

@@ -17,3 +17,7 @@
content: var(--wa-form-control-required-content);
margin-inline-start: var(--wa-form-control-required-content-offset);
}
.wa-button-group {
display: flex;
}

View File

@@ -9,7 +9,8 @@ import { RequiredValidator } from '../../internal/validators/required-validator.
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js';
import formControlStyles from '../../styles/shadow/form-control.css';
import '../button-group/button-group.js';
import sizeStyles from '../../styles/shadow/size.css';
import buttonGroupStyles from '../../styles/utilities/button-group.css';
import type WaRadioButton from '../radio-button/radio-button.js';
import '../radio/radio.js';
import type WaRadio from '../radio/radio.js';
@@ -41,7 +42,7 @@ import styles from './radio-group.css';
*/
@customElement('wa-radio-group')
export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
static shadowStyle = [formControlStyles, styles];
static shadowStyle = [sizeStyles, buttonGroupStyles, formControlStyles, styles];
static get validators() {
const validators = isServer
@@ -314,7 +315,6 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
const hasHintSlot = this.hasUpdated ? this.hasSlotController.test('hint') : this.withHint;
const hasLabel = this.label ? true : !!hasLabelSlot;
const hasHint = this.hint ? true : !!hasHintSlot;
const defaultSlot = html` <slot @slotchange=${this.syncRadioElements}></slot> `;
return html`
<fieldset
@@ -342,15 +342,11 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
<slot name="label">${this.label}</slot>
</label>
<div part="form-control-input" class="form-control-input" role="presentation">
${this.hasButtonGroup
? html`
<wa-button-group part="button-group" exportparts="base:button-group__base" role="presentation">
${defaultSlot}
</wa-button-group>
`
: defaultSlot}
</div>
<slot
part="form-control-input"
class=${classMap({ 'wa-button-group': this.hasButtonGroup })}
@slotchange=${this.syncRadioElements}
></slot>
<slot
name="hint"

View File

@@ -0,0 +1,57 @@
.wa-button-group {
display: inline-flex;
position: relative;
flex-wrap: wrap;
isolation: isolate;
> :hover,
&::slotted(:hover) {
z-index: 1;
}
/* Focus and checked are always on top */
> :focus,
&::slotted(:focus),
> [aria-checked='true'],
> [checked],
&::slotted([aria-checked='true']),
&::slotted([checked]) {
z-index: 2 !important;
}
}
/* Horizontal */
.wa-button-group:not([aria-orientation='vertical']) {
> :not(:first-child),
&::slotted(:not(:first-child)) {
border-start-start-radius: 0 !important;
border-end-start-radius: 0 !important;
border-inline-start-color: var(--border-color, rgb(0 0 0 / 0.3));
margin-inline-start: calc(-1 * var(--border-width, 1px));
}
> :not(:last-child),
&::slotted(:not(:last-child)) {
border-start-end-radius: 0 !important;
border-end-end-radius: 0 !important;
}
}
/* Vertical */
.wa-button-group[aria-orientation='vertical'] {
flex-direction: column;
> :not(:first-child),
&::slotted(:not(:first-child)) {
border-start-start-radius: 0 !important;
border-start-end-radius: 0 !important;
border-block-start-color: var(--border-color, rgb(0 0 0 / 0.3));
margin-block-start: calc(-1 * var(--border-width, 1px));
}
> :not(:last-child),
&::slotted(:not(:last-child)) {
border-end-start-radius: 0 !important;
border-end-end-radius: 0 !important;
}
}