diff --git a/docs/components/form.md b/docs/components/form.md index f14f17e01..192a9c85d 100644 --- a/docs/components/form.md +++ b/docs/components/form.md @@ -8,9 +8,9 @@ All of Shoelace's components make use of the [shadow DOM](https://developer.mozi ```html preview - +
- + Birds Cats Dogs diff --git a/docs/components/select.md b/docs/components/select.md index eb6f6389e..4a192bcd1 100644 --- a/docs/components/select.md +++ b/docs/components/select.md @@ -22,6 +22,16 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i ## Examples +### Labels + +```html preview + + Option 1 + Option 2 + Option 3 + +``` + ### Multiple ```html preview diff --git a/src/components.d.ts b/src/components.d.ts index 9cfe04241..36c6a497a 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -568,6 +568,10 @@ export namespace Components { * Set to true to disable the select control. */ "disabled": boolean; + /** + * The select's label. + */ + "label": string; /** * The maximum number of tags to show when `multiple` is true. After the maximum, "+n" will be shown to indicate the number of additional items that are selected. Set to -1 to remove the limit. */ @@ -1670,6 +1674,10 @@ declare namespace LocalJSX { * Set to true to disable the select control. */ "disabled"?: boolean; + /** + * The select's label. + */ + "label"?: string; /** * The maximum number of tags to show when `multiple` is true. After the maximum, "+n" will be shown to indicate the number of additional items that are selected. Set to -1 to remove the limit. */ diff --git a/src/components/input/input.scss b/src/components/input/input.scss index 60c61db77..1a08dfa18 100644 --- a/src/components/input/input.scss +++ b/src/components/input/input.scss @@ -6,9 +6,6 @@ } .form-control { - display: flex; - flex-direction: column; - .label { display: none; } diff --git a/src/components/select/select.scss b/src/components/select/select.scss index 42510f4e0..6e4429dbc 100644 --- a/src/components/select/select.scss +++ b/src/components/select/select.scss @@ -4,6 +4,31 @@ display: block; } +.form-control { + .label { + display: none; + } +} + +.form-control--has-label { + .label { + display: inline-block; + margin-bottom: var(--sl-spacing-xx-small); + + &.label--small { + font-size: var(--sl-input-font-size-small); + } + + &.label--medium { + font-size: var(--sl-input-font-size-medium); + } + + &.label--large { + font-size: var(--sl-input-font-size-large); + } + } +} + .select { width: 100%; } diff --git a/src/components/select/select.tsx b/src/components/select/select.tsx index 0c63ea8ba..8fcdd0914 100644 --- a/src/components/select/select.tsx +++ b/src/components/select/select.tsx @@ -2,6 +2,8 @@ import { Component, Element, Event, EventEmitter, Prop, State, Watch, h } from ' import ResizeObserver from 'resize-observer-polyfill'; import { getTextContent } from '../../utilities/slot'; +let id = 0; + /** * @since 1.0 * @status stable @@ -17,6 +19,7 @@ export class Select { this.handleBlur = this.handleBlur.bind(this); this.handleFocus = this.handleFocus.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleLabelClick = this.handleLabelClick.bind(this); this.handleMenuKeyDown = this.handleMenuKeyDown.bind(this); this.handleMenuHide = this.handleMenuHide.bind(this); this.handleMenuShow = this.handleMenuShow.bind(this); @@ -26,6 +29,8 @@ export class Select { dropdown: HTMLSlDropdownElement; input: HTMLSlInputElement; + inputId = `input-${++id}`; + labelId = `input-label-${id}`; menu: HTMLSlMenuElement; resizeObserver: any; @@ -61,6 +66,9 @@ export class Select { /** The value of the control. This will be a string or an array depending on `multiple`. */ @Prop({ mutable: true }) value: string | Array = ''; + /** The select's label. */ + @Prop() label = ''; + @Watch('multiple') handleMultipleChange() { // Cast to array | string based on `this.multiple` @@ -130,6 +138,10 @@ export class Select { } } + handleLabelClick() { + this.input.setFocus(); + } + handleMenuKeyDown(event: KeyboardEvent) { event.stopPropagation(); @@ -252,55 +264,76 @@ export class Select { render() { return ( - (this.dropdown = el)} - closeOnSelect={!this.multiple} - containingElement={this.host} +
0 }} - onSlShow={this.handleMenuShow} - onSlHide={this.handleMenuHide} > - (this.input = el)} - class="select__input" - name={this.name} - value={this.displayLabel} - disabled={this.disabled} - placeholder={this.displayLabel === '' && this.displayTags.length === 0 ? this.placeholder : null} - readonly={true} - size={this.size} - onSlFocus={this.handleFocus} - onSlBlur={this.handleBlur} - onKeyDown={this.handleKeyDown} + - - (this.menu = el)} - class="select__menu" - onSlSelect={this.handleMenuSelect} - onKeyDown={this.handleMenuKeyDown} + {this.label} + + (this.dropdown = el)} + closeOnSelect={!this.multiple} + containingElement={this.host} + class={{ + select: true, + 'select--open': this.isOpen, + 'select--focused': this.hasFocus, + 'select--disabled': this.disabled, + 'select--multiple': this.multiple, + 'select--small': this.size === 'small', + 'select--medium': this.size === 'medium', + 'select--large': this.size === 'large' + }} + onSlShow={this.handleMenuShow} + onSlHide={this.handleMenuHide} > - - - + (this.input = el)} + id={this.inputId} + class="select__input" + name={this.name} + value={this.displayLabel} + disabled={this.disabled} + placeholder={this.displayLabel === '' && this.displayTags.length === 0 ? this.placeholder : null} + readonly={true} + size={this.size} + aria-labelledby={this.labelId} + onSlFocus={this.handleFocus} + onSlBlur={this.handleBlur} + onKeyDown={this.handleKeyDown} + > + {this.displayTags.length && ( + + {this.displayTags} + + )} + + + + + (this.menu = el)} + class="select__menu" + onSlSelect={this.handleMenuSelect} + onKeyDown={this.handleMenuKeyDown} + > + + + +
); } } diff --git a/src/components/textarea/textarea.scss b/src/components/textarea/textarea.scss index ae2afa85b..854e93434 100644 --- a/src/components/textarea/textarea.scss +++ b/src/components/textarea/textarea.scss @@ -6,9 +6,6 @@ } .form-control { - display: flex; - flex-direction: column; - .label { display: none; } diff --git a/src/components/textarea/textarea.tsx b/src/components/textarea/textarea.tsx index 8dc765b0c..ed05d3a51 100644 --- a/src/components/textarea/textarea.tsx +++ b/src/components/textarea/textarea.tsx @@ -226,6 +226,7 @@ export class Textarea { autoFocus={this.autofocus} required={this.required} inputMode={this.inputmode} + aria-labelledby={this.labelId} onChange={this.handleChange} onInput={this.handleInput} onFocus={this.handleFocus}