backport 1800, 1860

This commit is contained in:
Cory LaViska
2024-02-09 09:58:13 -05:00
parent 5f8c69064c
commit 93306c99ce
9 changed files with 180 additions and 91 deletions

View File

@@ -88,6 +88,18 @@ const App = () => (
);
```
### Help Text
Add descriptive help text to a switch with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
```html:preview
<wa-checkbox help-text="What should the user know about the checkbox?">Label</wa-checkbox>
```
````jsx:react
import WaCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';
const App = () => <WaCheckbox help-text="What should the user know about the switch?">Label</WaCheckbox>;
### Custom Validity
Use the `setCustomValidity()` method to set a custom validation message. This will prevent the form from submitting and make the browser display the error message you provide. To clear the error, call this function with an empty string.
@@ -122,7 +134,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
});
});
</script>
```
````
```jsx:react
import { useEffect, useRef } from 'react';

View File

@@ -74,6 +74,20 @@ const App = () => (
);
```
### Help Text
Add descriptive help text to a switch with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
```html:preview
<wa-switch help-text="What should the user know about the switch?">Label</wa-switch>
```
```jsx:react
import WaSwitch from '@shoelace-style/shoelace/dist/react/checkbox';
const App = () => <WaSwitch help-text="What should the user know about the switch?">Label</WaSwitch>;
```
### Custom Styles
Use the available custom properties to change how the switch is styled.

View File

@@ -24,6 +24,8 @@ New versions of Web Awesome are released as-needed and generally occur when a cr
## Next
- Added the Arabic translation [#1852]
- Added help text to `<sl-checkbox>` [#1860]
- Added help text to `<sl-switch>` [#1800]
- Fixed a bug in `<sl-option>` that caused HTML tags to be included in `getTextLabel()`
## 2.13.1

View File

@@ -1,6 +1,7 @@
import { classMap } from 'lit/directives/class-map.js';
import { defaultValue } from '../../internal/default-value.js';
import { FormControlController } from '../../internal/form.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
@@ -21,6 +22,7 @@ import type { WebAwesomeFormControl } from '../../internal/webawesome-element.js
* @dependency wa-icon
*
* @slot - The checkbox's label.
* @slot help-text - Text that describes how to use the checkbox. Alternatively, you can use the `help-text` attribute.
*
* @event wa-blur - Emitted when the checkbox loses focus.
* @event wa-change - Emitted when the checked state changes.
@@ -35,6 +37,7 @@ import type { WebAwesomeFormControl } from '../../internal/webawesome-element.js
* @csspart checked-icon - The checked icon, a `<wa-icon>` element.
* @csspart indeterminate-icon - The indeterminate icon, a `<wa-icon>` element.
* @csspart label - The container that wraps the checkbox's label.
* @csspart form-control-help-text - The help text's wrapper.
*
* @cssproperty --background - The checkbox's background styles.
* @cssproperty --background-checked - The checkbox's background styles when checked.
@@ -56,6 +59,7 @@ export default class WaCheckbox extends WebAwesomeElement implements WebAwesomeF
defaultValue: (control: WaCheckbox) => control.defaultChecked,
setValue: (control: WaCheckbox, checked: boolean) => (control.checked = checked)
});
private readonly hasSlotController = new HasSlotController(this, 'help-text');
@query('input[type="checkbox"]') input: HTMLInputElement;
@@ -97,6 +101,9 @@ export default class WaCheckbox extends WebAwesomeElement implements WebAwesomeF
/** Makes the checkbox a required field. */
@property({ type: Boolean, reflect: true }) required = false;
/** The checkbox's help text. If you need to display HTML, use the `help-text` slot instead. */
@property({ attribute: 'help-text' }) helpText = '';
/** Gets the validity state object */
get validity() {
return this.input.validity;
@@ -189,75 +196,93 @@ export default class WaCheckbox extends WebAwesomeElement implements WebAwesomeF
}
render() {
const hasHelpTextSlot = this.hasSlotController.test('help-text');
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
//
// NOTE: we use a <div> around the label slot because of this Chrome bug.
//
// https://bugs.chromium.org/p/chromium/issues/detail?id=1413733
//
return html`
<label
part="base"
<div
class=${classMap({
checkbox: true,
'checkbox--checked': this.checked,
'checkbox--disabled': this.disabled,
'checkbox--focused': this.hasFocus,
'checkbox--indeterminate': this.indeterminate,
'checkbox--small': this.size === 'small',
'checkbox--medium': this.size === 'medium',
'checkbox--large': this.size === 'large'
'form-control': true,
'form-control--small': this.size === 'small',
'form-control--medium': this.size === 'medium',
'form-control--large': this.size === 'large',
'form-control--has-help-text': hasHelpText
})}
>
<input
class="checkbox__input"
type="checkbox"
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${this.name}
value=${ifDefined(this.value)}
.indeterminate=${live(this.indeterminate)}
.checked=${live(this.checked)}
.disabled=${this.disabled}
.required=${this.required}
aria-checked=${this.checked ? 'true' : 'false'}
@click=${this.handleClick}
@input=${this.handleInput}
@invalid=${this.handleInvalid}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
/>
<span
part="control${this.checked ? ' control--checked' : ''}${this.indeterminate ? ' control--indeterminate' : ''}"
class="checkbox__control"
<label
part="base"
class=${classMap({
checkbox: true,
'checkbox--checked': this.checked,
'checkbox--disabled': this.disabled,
'checkbox--focused': this.hasFocus,
'checkbox--indeterminate': this.indeterminate,
'checkbox--small': this.size === 'small',
'checkbox--medium': this.size === 'medium',
'checkbox--large': this.size === 'large'
})}
>
${this.checked
? html`
<wa-icon
part="checked-icon"
class="checkbox__checked-icon"
library="system"
name="check"
variant="solid"
></wa-icon>
`
: ''}
${!this.checked && this.indeterminate
? html`
<wa-icon
part="indeterminate-icon"
class="checkbox__indeterminate-icon"
library="system"
name="minus"
variant="solid"
></wa-icon>
`
: ''}
</span>
<input
class="checkbox__input"
type="checkbox"
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${this.name}
value=${ifDefined(this.value)}
.indeterminate=${live(this.indeterminate)}
.checked=${live(this.checked)}
.disabled=${this.disabled}
.required=${this.required}
aria-checked=${this.checked ? 'true' : 'false'}
aria-describedby="help-text"
@click=${this.handleClick}
@input=${this.handleInput}
@invalid=${this.handleInvalid}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
/>
<div part="label" class="checkbox__label">
<slot></slot>
<span
part="control${this.checked ? ' control--checked' : ''}${this.indeterminate
? ' control--indeterminate'
: ''}"
class="checkbox__control"
>
${this.checked
? html`
<wa-icon part="checked-icon" class="checkbox__checked-icon" library="system" name="check"></wa-icon>
`
: ''}
${!this.checked && this.indeterminate
? html`
<wa-icon
part="indeterminate-icon"
class="checkbox__indeterminate-icon"
library="system"
name="indeterminate"
></wa-icon>
`
: ''}
</span>
<div part="label" class="checkbox__label">
<slot></slot>
</div>
</label>
<div
aria-hidden=${hasHelpText ? 'false' : 'true'}
class="form-control__help-text"
id="help-text"
part="form-control-help-text"
>
<slot name="help-text">${this.helpText}</slot>
</div>
</label>
</div>
`;
}
}

View File

@@ -1,8 +1,10 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
import formControlStyles from '../../styles/form-control.styles.js';
export default css`
${componentStyles}
${formControlStyles}
:host {
--background: var(--wa-form-controls-background);

View File

@@ -23,6 +23,7 @@ describe('<wa-checkbox>', () => {
expect(el.checked).to.be.false;
expect(el.indeterminate).to.be.false;
expect(el.defaultChecked).to.be.false;
expect(el.helpText).to.equal('');
});
it('should have title if title attribute is set', async () => {

View File

@@ -1,6 +1,7 @@
import { classMap } from 'lit/directives/class-map.js';
import { defaultValue } from '../../internal/default-value.js';
import { FormControlController } from '../../internal/form.js';
import { HasSlotController } from '../../internal/slot.js';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
@@ -18,6 +19,7 @@ import type { WebAwesomeFormControl } from '../../internal/webawesome-element.js
* @since 2.0
*
* @slot - The switch's label.
* @slot help-text - Text that describes how to use the switch. Alternatively, you can use the `help-text` attribute.
*
* @event wa-blur - Emitted when the control loses focus.
* @event wa-change - Emitted when the control's checked state changes.
@@ -29,6 +31,7 @@ import type { WebAwesomeFormControl } from '../../internal/webawesome-element.js
* @csspart control - The control that houses the switch's thumb.
* @csspart thumb - The switch's thumb.
* @csspart label - The switch's label.
* @csspart form-control-help-text - The help text's wrapper.
*
* @cssproperty --background - The switch's background styles.
* @cssproperty --background-checked - The switch's background styles when checked.
@@ -52,6 +55,7 @@ export default class WaSwitch extends WebAwesomeElement implements WebAwesomeFor
defaultValue: (control: WaSwitch) => control.defaultChecked,
setValue: (control: WaSwitch, checked: boolean) => (control.checked = checked)
});
private readonly hasSlotController = new HasSlotController(this, 'help-text');
@query('input[type="checkbox"]') input: HTMLInputElement;
@@ -86,6 +90,9 @@ export default class WaSwitch extends WebAwesomeElement implements WebAwesomeFor
/** Makes the switch a required field. */
@property({ type: Boolean, reflect: true }) required = false;
/** The switch's help text. If you need to display HTML, use the `help-text` slot instead. */
@property({ attribute: 'help-text' }) helpText = '';
/** Gets the validity state object */
get validity() {
return this.input.validity;
@@ -189,46 +196,69 @@ export default class WaSwitch extends WebAwesomeElement implements WebAwesomeFor
}
render() {
const hasHelpTextSlot = this.hasSlotController.test('help-text');
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
return html`
<label
part="base"
<div
class=${classMap({
switch: true,
'switch--checked': this.checked,
'switch--disabled': this.disabled,
'switch--focused': this.hasFocus,
'switch--small': this.size === 'small',
'switch--medium': this.size === 'medium',
'switch--large': this.size === 'large'
'form-control': true,
'form-control--small': this.size === 'small',
'form-control--medium': this.size === 'medium',
'form-control--large': this.size === 'large',
'form-control--has-help-text': hasHelpText
})}
>
<input
class="switch__input"
type="checkbox"
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${this.name}
value=${ifDefined(this.value)}
.checked=${live(this.checked)}
.disabled=${this.disabled}
.required=${this.required}
role="switch"
aria-checked=${this.checked ? 'true' : 'false'}
@click=${this.handleClick}
@input=${this.handleInput}
@invalid=${this.handleInvalid}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@keydown=${this.handleKeyDown}
/>
<label
part="base"
class=${classMap({
switch: true,
'switch--checked': this.checked,
'switch--disabled': this.disabled,
'switch--focused': this.hasFocus,
'switch--small': this.size === 'small',
'switch--medium': this.size === 'medium',
'switch--large': this.size === 'large'
})}
>
<input
class="switch__input"
type="checkbox"
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${this.name}
value=${ifDefined(this.value)}
.checked=${live(this.checked)}
.disabled=${this.disabled}
.required=${this.required}
role="switch"
aria-checked=${this.checked ? 'true' : 'false'}
aria-describedby="help-text"
@click=${this.handleClick}
@input=${this.handleInput}
@invalid=${this.handleInvalid}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@keydown=${this.handleKeyDown}
/>
<span part="control" class="switch__control">
<span part="thumb" class="switch__thumb"></span>
</span>
<span part="control" class="switch__control">
<span part="thumb" class="switch__thumb"></span>
</span>
<div part="label" class="switch__label">
<slot></slot>
<div part="label" class="switch__label">
<slot></slot>
</div>
</label>
<div
aria-hidden=${hasHelpText ? 'false' : 'true'}
class="form-control__help-text"
id="help-text"
part="form-control-help-text"
>
<slot name="help-text">${this.helpText}</slot>
</div>
</label>
</div>
`;
}
}

View File

@@ -1,8 +1,10 @@
import { css } from 'lit';
import componentStyles from '../../styles/component.styles.js';
import formControlStyles from '../../styles/form-control.styles.js';
export default css`
${componentStyles}
${formControlStyles}
:host {
--background: var(--wa-form-controls-resting-color);

View File

@@ -21,6 +21,7 @@ describe('<wa-switch>', () => {
expect(el.required).to.be.false;
expect(el.checked).to.be.false;
expect(el.defaultChecked).to.be.false;
expect(el.helpText).to.equal('');
});
it('should have title if title attribute is set', async () => {