Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checkbox help text #1860

Merged
merged 2 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/pages/components/checkbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,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
<sl-checkbox help-text="What should the user know about the checkbox?">Label</sl-checkbox>
```

```jsx:react
import SlCheckbox from '@shoelace-style/shoelace/dist/react/checkbox';

const App = () => <SlCheckbox help-text="What should the user know about the switch?">Label</SlCheckbox>;
```

### 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.
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/components/switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ Add descriptive help text to a switch with the `help-text` attribute. For help t
```

```jsx:react
import SlSwitch from '@shoelace-style/shoelace/dist/react/checkbox';

const App = () => <SlSwitch help-text="What should the user know about the switch?">Label</SlSwitch>;
```

Expand Down
2 changes: 2 additions & 0 deletions docs/pages/resources/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ New versions of Shoelace are released as-needed and generally occur when a criti

## 2.13.1

- Added help text to `<sl-checkbox>`
- Added help text to `<sl-switch>` [#1800]
- Fixed a bug where the safe triangle was always visible when selecting nested `<sl-menu>` elements [#1835]

## 2.13.0
Expand Down
133 changes: 82 additions & 51 deletions src/components/checkbox/checkbox.component.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -21,6 +22,7 @@ import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
* @dependency sl-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 sl-blur - Emitted when the checkbox loses focus.
* @event sl-change - Emitted when the checked state changes.
Expand All @@ -35,6 +37,7 @@ import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
* @csspart checked-icon - The checked icon, an `<sl-icon>` element.
* @csspart indeterminate-icon - The indeterminate icon, an `<sl-icon>` element.
* @csspart label - The container that wraps the checkbox's label.
* @csspart form-control-help-text - The help text's wrapper.
*/
export default class SlCheckbox extends ShoelaceElement implements ShoelaceFormControl {
static styles: CSSResultGroup = styles;
Expand All @@ -45,6 +48,7 @@ export default class SlCheckbox extends ShoelaceElement implements ShoelaceFormC
defaultValue: (control: SlCheckbox) => control.defaultChecked,
setValue: (control: SlCheckbox, checked: boolean) => (control.checked = checked)
});
private readonly hasSlotController = new HasSlotController(this, 'help-text');

@query('input[type="checkbox"]') input: HTMLInputElement;

Expand Down Expand Up @@ -86,6 +90,9 @@ export default class SlCheckbox extends ShoelaceElement implements ShoelaceFormC
/** 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;
Expand Down Expand Up @@ -178,68 +185,92 @@ export default class SlCheckbox extends ShoelaceElement implements ShoelaceFormC
}

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'
})}
>
<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"
>
${this.checked
? html`
<sl-icon part="checked-icon" class="checkbox__checked-icon" library="system" name="check"></sl-icon>
`
: ''}
${!this.checked && this.indeterminate
? html`
<sl-icon
part="indeterminate-icon"
class="checkbox__indeterminate-icon"
library="system"
name="indeterminate"
></sl-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"
>
${this.checked
? html`
<sl-icon part="checked-icon" class="checkbox__checked-icon" library="system" name="check"></sl-icon>
`
: ''}
${!this.checked && this.indeterminate
? html`
<sl-icon
part="indeterminate-icon"
class="checkbox__indeterminate-icon"
library="system"
name="indeterminate"
></sl-icon>
`
: ''}
</span>

<div part="label" class="checkbox__label">
<slot></slot>
<slot name="help-text">${this.helpText}</slot>
</div>
</label>
</div>
`;
}
}
2 changes: 2 additions & 0 deletions src/components/checkbox/checkbox.styles.ts
Original file line number Diff line number Diff line change
@@ -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 {
display: inline-block;
Expand Down
1 change: 1 addition & 0 deletions src/components/checkbox/checkbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('<sl-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 () => {
Expand Down
Loading