Refactor label + help text logic

This commit is contained in:
Cory LaViska
2020-12-23 15:47:13 -05:00
parent e9b1463f0c
commit b16d262ec8
15 changed files with 274 additions and 270 deletions

View File

@@ -101,63 +101,21 @@ Use the `prefix` and `suffix` slots to add icons.
### Labels
Use the `label` attribute to give the input an accessible label.
Use the `label` attribute to give the input an accessible label. For labels that contain HTML, use the `label` slot instead.
```html preview
<sl-input label="Name"></sl-input>
<br>
<sl-input type="email" label="Email" placeholder="bob@example.com"></sl-input>
<sl-input label="What is your name?"></sl-input>
```
### Help Text
Add descriptive help text to an input with the `help-text` slot.
Add descriptive help text to an input with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
```html preview
<sl-input label="Nickname">
<div slot="help-text">What would you like people to call you?</div>
</sl-input>
```
### Inputs with Dropdowns
Dropdowns can be used in the `prefix` or `suffix` slot to make inputs more versatile. Make sure to use the `hoist` prop so the dropdown breaks out of the input's overflow.
```html preview
<div class="input-dropdowns">
<sl-input type="tel" label="Phone">
<sl-icon slot="prefix" name="telephone"></sl-icon>
<sl-dropdown slot="suffix" placement="bottom-end" hoist>
<sl-button slot="trigger" caret type="text" size="small">Home</sl-button>
<sl-menu>
<sl-menu-item checked>Home</sl-menu-item>
<sl-menu-item>Mobile</sl-menu-item>
<sl-menu-item>Work</sl-menu-item>
</sl-menu>
</sl-dropdown>
<div slot="help-text">
Please enter a phone number where we can reach you.
</div>
</sl-input>
</div>
<script>
const container = document.querySelector('.input-dropdowns');
const dropdown = container.querySelector('sl-dropdown');
const trigger = dropdown.querySelector('sl-button');
dropdown.addEventListener('sl-select', event => {
const selectedItem = event.detail.item;
trigger.textContent = selectedItem.textContent;
[...dropdown.querySelectorAll('sl-menu-item')].map(item => item.checked = item === selectedItem);
});
</script>
<style>
.input-dropdowns sl-dropdown {
margin-right: .25rem;
}
</style>
<sl-input
label="Nickname"
help-text="What would you like people to call you?"
></sl-input>
```
[component-metadata:sl-input]

View File

@@ -163,7 +163,7 @@ The `value` prop is bound to the current selection. As the selection changes, so
### Labels
Use the `label` attribute to give the select an accessible label.
Use the `label` attribute to give the select an accessible label. For labels that contain HTML, use the `label` slot instead.
```html preview
<sl-select label="Select one">
@@ -175,15 +175,16 @@ Use the `label` attribute to give the select an accessible label.
### Help Text
Add descriptive help text to an input with the `help-text` slot.
Add descriptive help text to a select with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
```html preview
<sl-select label="Experience">
<sl-select
label="Experience"
help-text="Please tell us your skill level."
>
<sl-menu-item value="option-1">Novice</sl-menu-item>
<sl-menu-item value="option-2">Intermediate</sl-menu-item>
<sl-menu-item value="option-3">Advanced</sl-menu-item>
<div slot="help-text">Please tell us your skill level.</div>
</sl-select>
```

View File

@@ -52,7 +52,7 @@ Use the `size` attribute to change a textarea's size.
### Labels
Use the `label` attribute to give the textarea an accessible label.
Use the `label` attribute to give the textarea an accessible label. For labels that contain HTML, use the `label` slot instead.
```html preview
<sl-textarea label="Comments"></sl-textarea>
@@ -60,11 +60,13 @@ Use the `label` attribute to give the textarea an accessible label.
### Help Text
Add descriptive help text to a textarea with the `help-text` slot.
Add descriptive help text to a textarea with the `help-text` attribute. For help texts that contain HTML, use the `help-text` slot instead.
```html preview
<sl-textarea label="Feedback">
<div slot="help-text">Please tell us what you think.</div>
<sl-textarea
label="Feedback"
help-text="Please tell us what you think."
>
</sl-textarea>
```

View File

@@ -21,6 +21,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
- 🚨 BREAKING CHANGE: Removed `copy-button` part from `sl-color-picker` since copying is now done by clicking the preview
- Added `getFormattedValue()` method to `sl-color-picker` so you can retrieve the current value in any format
- Added visual separators between solid buttons in `sl-button-group`
- Added `help-text` prop to `sl-input`, `sl-textarea`, and `sl-select`
- Fixed a bug where moving the mouse while `sl-dropdown` is closing would remove focus from the trigger
- Fixed a bug where `sl-menu-item` didn't set a default color in the dark theme
- Fixed a bug where `sl-color-picker` preview wouldn't update in Safari
@@ -31,6 +32,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
- Improved accessibility in `sl-tooltip` by allowing escape to dismiss it [#219](https://github.com/shoelace-style/shoelace/issues/219)
- Improved slot detection in `sl-card`, `sl-dialog`, and `sl-drawer`
- Made `@types/resize-observer-browser` a dependency so users don't have to install it manually
- Refactored internal label + help text logic into a functional component used by `sl-input`, `sl-textarea`, and `sl-select`
- Removed `sl-blur` and `sl-focus` events from `sl-menu` since menus can't have focus as of 2.0.0-beta.22
- Updated `sl-spinner` so the indicator is more obvious
- Updated to Bootstrap Icons 1.2.1

36
src/components.d.ts vendored
View File

@@ -676,6 +676,10 @@ export namespace Components {
* Set to true to disable the input.
*/
"disabled": boolean;
/**
* The input's help text. Alternatively, you can use the help-text slot.
*/
"helpText": string;
/**
* The input's inputmode attribute.
*/
@@ -685,7 +689,7 @@ export namespace Components {
*/
"invalid": boolean;
/**
* The input's label.
* The input's label. Alternatively, you can use the label slot.
*/
"label": string;
/**
@@ -998,6 +1002,10 @@ export namespace Components {
* Set to true to disable the select control.
*/
"disabled": boolean;
/**
* The select's help text. Alternatively, you can use the help-text slot.
*/
"helpText": string;
/**
* Enable this option to prevent the panel from being clipped when the component is placed inside a container with `overflow: auto|scroll`.
*/
@@ -1007,7 +1015,7 @@ export namespace Components {
*/
"invalid": boolean;
/**
* The select's label.
* The select's label. Alternatively, you can use the label slot.
*/
"label": string;
/**
@@ -1190,6 +1198,10 @@ export namespace Components {
* Set to true to disable the textarea.
*/
"disabled": boolean;
/**
* The textarea's help text. Alternatively, you can use the help-text slot.
*/
"helpText": string;
/**
* The textarea's inputmode attribute.
*/
@@ -1199,7 +1211,7 @@ export namespace Components {
*/
"invalid": boolean;
/**
* The textarea's label.
* The textarea's label. Alternatively, you can use the label slot.
*/
"label": string;
/**
@@ -2374,6 +2386,10 @@ declare namespace LocalJSX {
* Set to true to disable the input.
*/
"disabled"?: boolean;
/**
* The input's help text. Alternatively, you can use the help-text slot.
*/
"helpText"?: string;
/**
* The input's inputmode attribute.
*/
@@ -2383,7 +2399,7 @@ declare namespace LocalJSX {
*/
"invalid"?: boolean;
/**
* The input's label.
* The input's label. Alternatively, you can use the label slot.
*/
"label"?: string;
/**
@@ -2676,6 +2692,10 @@ declare namespace LocalJSX {
* Set to true to disable the select control.
*/
"disabled"?: boolean;
/**
* The select's help text. Alternatively, you can use the help-text slot.
*/
"helpText"?: string;
/**
* Enable this option to prevent the panel from being clipped when the component is placed inside a container with `overflow: auto|scroll`.
*/
@@ -2685,7 +2705,7 @@ declare namespace LocalJSX {
*/
"invalid"?: boolean;
/**
* The select's label.
* The select's label. Alternatively, you can use the label slot.
*/
"label"?: string;
/**
@@ -2872,6 +2892,10 @@ declare namespace LocalJSX {
* Set to true to disable the textarea.
*/
"disabled"?: boolean;
/**
* The textarea's help text. Alternatively, you can use the help-text slot.
*/
"helpText"?: string;
/**
* The textarea's inputmode attribute.
*/
@@ -2881,7 +2905,7 @@ declare namespace LocalJSX {
*/
"invalid"?: boolean;
/**
* The textarea's label.
* The textarea's label. Alternatively, you can use the label slot.
*/
"label"?: string;
/**

View File

@@ -1,6 +1,5 @@
@import 'component';
@import 'form-control-label';
@import 'form-control-help-text';
@import '../../functional-components/form-control/form-control';
/**
* @prop --focus-ring: The focus ring style to use when the control receives focus, a `box-shadow` property.

View File

@@ -1,4 +1,5 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch, h } from '@stencil/core';
import FormControl from '../../functional-components/form-control/form-control';
import { hasSlot } from '../../utilities/slot';
let id = 0;
@@ -13,10 +14,10 @@ let id = 0;
* @slot clear-icon - An icon to use in lieu of the default clear icon.
* @slot show-password-icon - An icon to use in lieu of the default show password icon.
* @slot hide-password-icon - An icon to use in lieu of the default hide password icon.
* @slot help-text - Help text that describes how to use the input.
* @slot help-text - Help text that describes how to use the input. Alternatively, you can use the help-text prop.
*
* @part base - The component's base wrapper.
* @part form-control - The form control that wraps the label and the input.
* @part form-control - The form control that wraps the label, input, and help-text.
* @part label - The input label.
* @part input - The input control.
* @part prefix - The input prefix container.
@@ -40,7 +41,8 @@ export class Input {
@Element() host: HTMLSlInputElement;
@State() hasFocus = false;
@State() hasLabel = false;
@State() hasHelpTextSlot = false;
@State() hasLabelSlot = false;
@State() isPasswordVisible = false;
/** The input's type. */
@@ -58,9 +60,12 @@ export class Input {
/** Set to true to draw a pill-style input with rounded edges. */
@Prop({ reflect: true }) pill = false;
/** The input's label. */
/** The input's label. Alternatively, you can use the label slot. */
@Prop() label = '';
/** The input's help text. Alternatively, you can use the help-text slot. */
@Prop() helpText = '';
/** The input's placeholder text. */
@Prop() placeholder: string;
@@ -123,7 +128,7 @@ export class Input {
@Watch('label')
handleLabelChange() {
this.detectLabel();
this.handleSlotChange();
}
@Watch('value')
@@ -147,7 +152,6 @@ export class Input {
@Event({ eventName: 'sl-blur' }) slBlur: EventEmitter;
connectedCallback() {
this.detectLabel = this.detectLabel.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleInput = this.handleInput.bind(this);
this.handleInvalid = this.handleInvalid.bind(this);
@@ -155,10 +159,17 @@ export class Input {
this.handleFocus = this.handleFocus.bind(this);
this.handleClearClick = this.handleClearClick.bind(this);
this.handlePasswordToggle = this.handlePasswordToggle.bind(this);
this.handleSlotChange = this.handleSlotChange.bind(this);
this.host.shadowRoot.addEventListener('slotchange', this.handleSlotChange);
}
componentWillLoad() {
this.detectLabel();
this.handleSlotChange();
}
disconnectedCallback() {
this.host.shadowRoot.removeEventListener('slotchange', this.handleSlotChange);
}
/** Sets focus on the input. */
@@ -219,10 +230,6 @@ export class Input {
this.invalid = !this.input.checkValidity();
}
detectLabel() {
this.hasLabel = this.label.length > 0 || hasSlot(this.host, 'label');
}
handleChange() {
this.value = this.input.value;
this.slChange.emit();
@@ -261,32 +268,23 @@ export class Input {
this.isPasswordVisible = !this.isPasswordVisible;
}
handleSlotChange() {
this.hasHelpTextSlot = hasSlot(this.host, 'help-text');
this.hasLabelSlot = hasSlot(this.host, 'label');
}
render() {
return (
<div
part="form-control"
class={{
'form-control': true,
'form-control--has-label': this.hasLabel,
'form-control--invalid': this.invalid
}}
<FormControl
inputId={this.inputId}
label={this.label}
labelId={this.labelId}
hasLabelSlot={this.hasLabelSlot}
helpTextId={this.helpTextId}
helpText={this.helpText}
hasHelpTextSlot={this.hasHelpTextSlot}
size={this.size}
>
<label
part="label"
class={{
label: true,
'label--small': this.size === 'small',
'label--medium': this.size === 'medium',
'label--large': this.size === 'large',
'label--invalid': this.invalid
}}
htmlFor={this.inputId}
>
<slot name="label" onSlotchange={this.detectLabel}>
{this.label}
</slot>
</label>
<div
part="base"
class={{
@@ -382,21 +380,7 @@ export class Input {
<slot name="suffix" />
</span>
</div>
<div
part="help-text"
id={this.helpTextId}
class={{
'help-text': true,
'help-text--small': this.size === 'small',
'help-text--medium': this.size === 'medium',
'help-text--large': this.size === 'large',
'help-text--invalid': this.invalid
}}
>
<slot name="help-text" />
</div>
</div>
</FormControl>
);
}
}

View File

@@ -1,6 +1,5 @@
@import 'component';
@import 'form-control-label';
@import 'form-control-help-text';
@import '../../functional-components/form-control/form-control';
@import 'mixins/hidden';
@import 'mixins/hide-scrollbar';

View File

@@ -1,4 +1,5 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch, h } from '@stencil/core';
import FormControl from '../../functional-components/form-control/form-control';
import { getTextContent } from '../../utilities/slot';
import { hasSlot } from '../../utilities/slot';
@@ -14,7 +15,7 @@ let id = 0;
*
* @part base - The component's base wrapper.
* @part clear-button - The input's clear button, exported from <sl-input>.
* @part form-control - The form control that wraps the label and the input.
* @part form-control - The form control that wraps the label, input, and help text.
* @part help-text - The select's help text.
* @part icon - The select's icon.
* @part label - The select's label.
@@ -41,7 +42,8 @@ export class Select {
@Element() host: HTMLSlSelectElement;
@State() hasFocus = false;
@State() hasLabel = false;
@State() hasHelpTextSlot = false;
@State() hasLabelSlot = false;
@State() isOpen = false;
@State() items = [];
@State() displayLabel = '';
@@ -80,9 +82,12 @@ export class Select {
/** Set to true to draw a pill-style select with rounded edges. */
@Prop() pill = false;
/** The select's label. */
/** The select's label. Alternatively, you can use the label slot. */
@Prop() label = '';
/** The select's help text. Alternatively, you can use the help-text slot. */
@Prop() helpText = '';
/** The select's required attribute. */
@Prop() required = false;
@@ -101,7 +106,7 @@ export class Select {
@Watch('label')
handleLabelChange() {
this.detectLabel();
this.handleSlotChange();
}
@Watch('multiple')
@@ -128,7 +133,6 @@ export class Select {
@Event({ eventName: 'sl-blur' }) slBlur: EventEmitter;
connectedCallback() {
this.detectLabel = this.detectLabel.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.handleClearClick = this.handleClearClick.bind(this);
@@ -139,10 +143,12 @@ export class Select {
this.handleMenuSelect = this.handleMenuSelect.bind(this);
this.handleSlotChange = this.handleSlotChange.bind(this);
this.handleTagInteraction = this.handleTagInteraction.bind(this);
this.host.shadowRoot.addEventListener('slotchange', this.handleSlotChange);
}
componentWillLoad() {
this.detectLabel();
this.handleSlotChange();
}
componentDidLoad() {
@@ -153,6 +159,10 @@ export class Select {
requestAnimationFrame(() => this.syncItemsFromValue());
}
disconnectedCallback() {
this.host.shadowRoot.removeEventListener('slotchange', this.handleSlotChange);
}
/** Checks for validity and shows the browser's validation message if the control is invalid. */
@Method()
async reportValidity() {
@@ -166,10 +176,6 @@ export class Select {
this.invalid = !this.input.checkValidity();
}
detectLabel() {
this.hasLabel = this.label.length > 0 || hasSlot(this.host, 'label');
}
getItemLabel(item: HTMLSlMenuItemElement) {
const slot = item.shadowRoot.querySelector('slot:not([name])') as HTMLSlotElement;
return getTextContent(slot);
@@ -283,6 +289,8 @@ export class Select {
}
handleSlotChange() {
this.hasHelpTextSlot = hasSlot(this.host, 'help-text');
this.hasLabelSlot = hasSlot(this.host, 'label');
this.syncItemsFromValue();
this.reportDuplicateItemValues();
}
@@ -384,32 +392,17 @@ export class Select {
const hasSelection = this.multiple ? this.value.length > 0 : this.value !== '';
return (
<div
part="form-control"
class={{
'form-control': true,
'form-control--has-label': this.hasLabel,
'form-control--invalid': this.invalid
}}
<FormControl
inputId={this.inputId}
label={this.label}
labelId={this.labelId}
hasLabelSlot={this.hasLabelSlot}
helpTextId={this.helpTextId}
helpText={this.helpText}
hasHelpTextSlot={this.hasHelpTextSlot}
size={this.size}
onLabelClick={this.handleLabelClick}
>
<label
part="label"
id={this.labelId}
class={{
label: true,
'label--small': this.size === 'small',
'label--medium': this.size === 'medium',
'label--large': this.size === 'large',
'label--invalid': this.invalid
}}
htmlFor={this.inputId}
onClick={this.handleLabelClick}
>
<slot name="label" onSlotchange={this.detectLabel}>
{this.label}
</slot>
</label>
<sl-dropdown
part="base"
ref={el => (this.dropdown = el)}
@@ -492,21 +485,7 @@ export class Select {
<slot onSlotchange={this.handleSlotChange} />
</sl-menu>
</sl-dropdown>
<div
part="help-text"
id={this.helpTextId}
class={{
'help-text': true,
'help-text--small': this.size === 'small',
'help-text--medium': this.size === 'medium',
'help-text--large': this.size === 'large',
'help-text--invalid': this.invalid
}}
>
<slot name="help-text" />
</div>
</div>
</FormControl>
);
}
}

View File

@@ -1,6 +1,5 @@
@import 'component';
@import 'form-control-label';
@import 'form-control-help-text';
@import '../../functional-components/form-control/form-control';
:host {
display: block;

View File

@@ -1,4 +1,5 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch, h } from '@stencil/core';
import FormControl from '../../functional-components/form-control/form-control';
import { hasSlot } from '../../utilities/slot';
let id = 0;
@@ -11,7 +12,7 @@ let id = 0;
* @slot help-text - Help text that describes how to use the input.
*
* @part base - The component's base wrapper.
* @part form-control - The form control that wraps the textarea and label.
* @part form-control - The form control that wraps the label, textarea, and help text.
* @part label - The textarea label.
* @part textarea - The textarea control.
* @part help-text - The textarea help text.
@@ -23,7 +24,7 @@ let id = 0;
shadow: true
})
export class Textarea {
textareaId = `textarea-${++id}`;
inputId = `textarea-${++id}`;
labelId = `textarea-label-${id}`;
helpTextId = `textarea-help-text-${id}`;
resizeObserver: ResizeObserver;
@@ -32,7 +33,8 @@ export class Textarea {
@Element() host: HTMLSlTextareaElement;
@State() hasFocus = false;
@State() hasLabel = false;
@State() hasHelpTextSlot = false;
@State() hasLabelSlot = false;
/** The textarea's size. */
@Prop({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
@@ -43,9 +45,12 @@ export class Textarea {
/** The textarea's value attribute. */
@Prop({ mutable: true, reflect: true }) value = '';
/** The textarea's label. */
/** The textarea's label. Alternatively, you can use the label slot. */
@Prop() label = '';
/** The textarea's help text. Alternatively, you can use the help-text slot. */
@Prop() helpText = '';
/** The textarea's placeholder text. */
@Prop() placeholder: string;
@@ -108,7 +113,7 @@ export class Textarea {
@Watch('label')
handleLabelChange() {
this.detectLabel();
this.handleSlotChange();
}
@Watch('rows')
@@ -122,15 +127,17 @@ export class Textarea {
}
connectedCallback() {
this.detectLabel = this.detectLabel.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleInput = this.handleInput.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.handleSlotChange = this.handleSlotChange.bind(this);
this.host.shadowRoot.addEventListener('slotchange', this.handleSlotChange);
}
componentWillLoad() {
this.detectLabel();
this.handleSlotChange();
}
componentDidLoad() {
@@ -141,6 +148,7 @@ export class Textarea {
disconnectedCallback() {
this.resizeObserver.unobserve(this.textarea);
this.host.shadowRoot.removeEventListener('slotchange', this.handleSlotChange);
}
/** Sets focus on the textarea. */
@@ -202,10 +210,6 @@ export class Textarea {
this.invalid = !this.textarea.checkValidity();
}
detectLabel() {
this.hasLabel = this.label.length > 0 || hasSlot(this.host, 'label');
}
handleChange() {
this.slChange.emit();
}
@@ -226,6 +230,11 @@ export class Textarea {
this.slFocus.emit();
}
handleSlotChange() {
this.hasLabelSlot = hasSlot(this.host, 'label');
this.hasHelpTextSlot = hasSlot(this.host, 'help-text');
}
setTextareaHeight() {
if (this.resize === 'auto') {
this.textarea.style.height = 'auto';
@@ -237,29 +246,16 @@ export class Textarea {
render() {
return (
<div
part="form-control"
class={{
'form-control': true,
'form-control--has-label': this.hasLabel,
'form-control--invalid': this.invalid
}}
<FormControl
inputId={this.inputId}
label={this.label}
labelId={this.labelId}
hasLabelSlot={this.hasLabelSlot}
helpTextId={this.helpTextId}
helpText={this.helpText}
hasHelpTextSlot={this.hasHelpTextSlot}
size={this.size}
>
<label
part="label"
class={{
label: true,
'label--small': this.size === 'small',
'label--medium': this.size === 'medium',
'label--large': this.size === 'large',
'label--invalid': this.invalid
}}
htmlFor={this.textareaId}
>
<slot name="label" onSlotchange={this.detectLabel}>
{this.label}
</slot>
</label>
<div
part="base"
class={{
@@ -285,7 +281,7 @@ export class Textarea {
<textarea
part="textarea"
ref={el => (this.textarea = el)}
id={this.textareaId}
id={this.inputId}
class="textarea__control"
name={this.name}
placeholder={this.placeholder}
@@ -308,21 +304,7 @@ export class Textarea {
onBlur={this.handleBlur}
/>
</div>
<div
part="help-text"
id={this.helpTextId}
class={{
'help-text': true,
'help-text--small': this.size === 'small',
'help-text--medium': this.size === 'medium',
'help-text--large': this.size === 'large',
'help-text--invalid': this.invalid
}}
>
<slot name="help-text" />
</div>
</div>
</FormControl>
);
}
}

View File

@@ -0,0 +1,54 @@
.form-control {
.form-control__label {
display: none;
}
.form-control__help-text {
display: none;
}
}
// Label
.form-control--has-label {
.form-control__label {
display: inline-block;
color: var(--sl-input-label-color);
margin-bottom: var(--sl-spacing-xxx-small);
}
&.form-control--small .form-control__label {
font-size: var(--sl-input-label-font-size-small);
}
&.form-control--medium .form-control__label {
font-size: var(--sl-input-label-font-size-medium);
}
&.form-control--large .form-control_label {
font-size: var(--sl-input-label-font-size-large);
}
}
// Help text
.form-control--has-help-text {
.form-control__help-text {
display: block;
color: var(--sl-input-help-text-color);
::slotted(*) {
margin-top: var(--sl-spacing-xxx-small);
}
}
&.form-control--small .form-control__help-text {
font-size: var(--sl-input-help-text-font-size-small);
}
&.form-control--medium .form-control__help-text {
font-size: var(--sl-input-help-text-font-size-medium);
}
&.form-control--large .form-control__help-text {
font-size: var(--sl-input-help-text-font-size-large);
}
}

View File

@@ -0,0 +1,73 @@
import { h } from '@stencil/core';
export interface FormControlProps {
/** The input id, used to map the input to the label */
inputId: string;
/** The size of the form control */
size: 'small' | 'medium' | 'large';
/** The label id, used to map the label to the input */
labelId?: string;
/** The label text (if the label slot isn't used) */
label?: string;
/** Whether or not a label slot has been provided. */
hasLabelSlot?: boolean;
/** The help text id, used to map the input to the help text */
helpTextId?: string;
/** The help text (if the help-text slot isn't used) */
helpText?: string;
/** Whether or not a help text slot has been provided. */
hasHelpTextSlot?: boolean;
/** A function that gets called when the label is clicked. */
onLabelClick?: (event: MouseEvent) => void;
}
const FormControl = (props: FormControlProps, children) => {
const hasLabel = props.label ? true : props.hasLabelSlot;
const hasHelpText = props.helpText ? true : props.hasHelpTextSlot;
return (
<div
part="form-control"
class={{
'form-control': true,
'form-control--small': props.size === 'small',
'form-control--medium': props.size === 'medium',
'form-control--large': props.size === 'large',
'form-control--has-label': hasLabel,
'form-control--has-help-text': hasHelpText
}}
>
<label
part="label"
id={props.labelId}
class="form-control__label"
htmlFor={props.inputId}
aria-hidden={hasLabel ? 'false' : 'true'}
onClick={props.onLabelClick}
>
<slot name="label">{props.label}</slot>
</label>
<div class="form-control__input">{children}</div>
<div
part="help-text"
id={props.helpTextId}
class="form-control__help-text"
aria-hidden={hasHelpText ? 'false' : 'true'}
>
<slot name="help-text">{props.helpText}</slot>
</div>
</div>
);
};
export default FormControl;

View File

@@ -1,23 +0,0 @@
.help-text {
color: var(--sl-input-help-text-color);
&.help-text--small {
font-size: var(--sl-input-help-text-font-size-small);
}
&.help-text--medium {
font-size: var(--sl-input-help-text-font-size-medium);
}
&.help-text--large {
font-size: var(--sl-input-help-text-font-size-large);
}
&.help-text--invalid {
color: var(--sl-input-help-text-color-invalid);
}
::slotted(*) {
margin-top: var(--sl-spacing-xxx-small);
}
}

View File

@@ -1,29 +0,0 @@
.form-control {
.label {
display: none;
}
}
.form-control--has-label {
.label {
display: inline-block;
color: var(--sl-input-label-color);
margin-bottom: var(--sl-spacing-xxx-small);
&.label--small {
font-size: var(--sl-input-label-font-size-small);
}
&.label--medium {
font-size: var(--sl-input-label-font-size-medium);
}
&.label--large {
font-size: var(--sl-input-label-font-size-large);
}
&.label--invalid {
color: var(--sl-input-label-color-invalid);
}
}
}