diff --git a/docs/docs/components/radio.md b/docs/docs/components/radio.md index 828c6c059..8453f1e7c 100644 --- a/docs/docs/components/radio.md +++ b/docs/docs/components/radio.md @@ -74,3 +74,15 @@ Add the `size` attribute to the [Radio Group](/docs/components/radio-group) to c Large 3 ``` + +### Hint + +Add descriptive hint to a switch with the `hint` attribute. For hints that contain HTML, use the `hint` slot instead. + +```html {.example} + + Option 1 + Option 2 + Option 3 + +``` diff --git a/src/components/checkbox/checkbox.ts b/src/components/checkbox/checkbox.ts index 779f6be77..d60ba8c29 100644 --- a/src/components/checkbox/checkbox.ts +++ b/src/components/checkbox/checkbox.ts @@ -127,7 +127,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement { @property({ type: Boolean, reflect: true }) required = false; /** The checkbox's hint. If you need to display HTML, use the `hint` slot instead. */ - @property({ attribute: 'hint' }) hint = ''; + @property() hint = ''; private handleClick() { this.hasInteracted = true; diff --git a/src/components/radio/radio.css b/src/components/radio/radio.css index 984e54f7b..df4281da8 100644 --- a/src/components/radio/radio.css +++ b/src/components/radio/radio.css @@ -1,15 +1,15 @@ -:host(:focus-visible) { - outline: none; -} - -.radio { - display: flex; +:host { + display: grid; + grid-template-columns: auto 1fr; align-items: top; - font: inherit; vertical-align: middle; cursor: pointer; } +:host(:focus-visible) { + outline: none; +} + .checked-icon { display: flex; fill: currentColor; @@ -19,6 +19,11 @@ } /* When the control isn't checked, hide the circle for Windows High Contrast mode a11y */ -.radio:not(.radio--checked) svg circle { +:host(:not(:state(checked))) svg circle { opacity: 0; } + +[part~='hint'] { + grid-column: 2; + margin-block-start: var(--wa-space-3xs); +} diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts index 11946b8ea..435a3a6cb 100644 --- a/src/components/radio/radio.ts +++ b/src/components/radio/radio.ts @@ -1,9 +1,11 @@ +import type { PropertyValues } from 'lit'; import { html, isServer } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; -import { watch } from '../../internal/watch.js'; +import { HasSlotController } from '../../internal/slot.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; import nativeStyles from '../../styles/native/radio.css'; +import formControlStyles from '../../styles/shadow/form-control.css'; import sizeStyles from '../../styles/utilities/size.css'; import '../icon/icon.js'; import styles from './radio.css'; @@ -17,14 +19,15 @@ import styles from './radio.css'; * @dependency wa-icon * * @slot - The radio's label. + * @slot hint - Text that describes how to use the checkbox. Alternatively, you can use the `hint` attribute. * * @event blur - Emitted when the control loses focus. * @event focus - Emitted when the control gains focus. * - * @csspart base - The component's base wrapper. * @csspart control - The circular container that wraps the radio's checked state. * @csspart checked-icon - The checked icon. * @csspart label - The container that wraps the radio's label. + * @csspart hint - The hint's wrapper. * * @cssproperty --background-color - The radio's background color. * @cssproperty --background-color-checked - The radio's background color when checked. @@ -42,7 +45,7 @@ import styles from './radio.css'; */ @customElement('wa-radio') export default class WaRadio extends WebAwesomeFormAssociatedElement { - static shadowStyle = [sizeStyles, nativeStyles, styles]; + static shadowStyle = [formControlStyles, sizeStyles, nativeStyles, styles]; @state() checked = false; @@ -63,6 +66,11 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement { /** Disables the radio. */ @property({ type: Boolean }) disabled = false; + /** The radio's hint. If you need to display HTML, use the `hint` slot instead. */ + @property() hint = ''; + + private readonly hasSlotController = new HasSlotController(this, 'hint'); + constructor() { super(); if (!isServer) { @@ -81,11 +89,19 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement { this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false'); } - @watch('checked') - handleCheckedChange() { - this.toggleCustomState('checked', this.checked); - this.setAttribute('aria-checked', this.checked ? 'true' : 'false'); - this.tabIndex = this.checked ? 0 : -1; + updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + + if (changedProperties.has('checked')) { + this.toggleCustomState('checked', this.checked); + this.setAttribute('aria-checked', this.checked ? 'true' : 'false'); + this.tabIndex = this.checked ? 0 : -1; + } + + if (changedProperties.has('disabled')) { + this.toggleCustomState('disabled', this.disabled); + this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false'); + } } /** @@ -95,12 +111,6 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement { // We override `setValue` because we don't want to set form values from here. We want to do that in "RadioGroup" itself. } - @watch('disabled', { waitUntilFirstUpdate: true }) - handleDisabledChange() { - this.toggleCustomState('disabled', this.disabled); - this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false'); - } - private handleClick = () => { if (!this.disabled) { this.checked = true; @@ -108,26 +118,30 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement { }; render() { - return html` - - - ${this.checked - ? html` - - - - ` - : ''} - + const hasHintSlot = isServer ? true : this.hasSlotController.test('hint'); + const hasHint = this.hint ? true : !!hasHintSlot; - + return html` + + ${this.checked + ? html` + + + + ` + : ''} + + + + ${this.hint} `; } } diff --git a/src/styles/native/radio.css b/src/styles/native/radio.css index 46f005a8a..dbd67fb55 100644 --- a/src/styles/native/radio.css +++ b/src/styles/native/radio.css @@ -71,13 +71,13 @@ input[type='radio'], input[type='radio'], label:has(input[type='radio']), input[type='radio'] + label, -:host [part~='base'] { +:host { cursor: pointer; } /* Checked */ input[type='radio']:checked, -:host .radio--checked .control { +:host(:state(checked)) .control { color: var(--checked-icon-color); border-color: var(--border-color-checked); background-color: var(--background-color-checked);