diff --git a/src/components/multi-select/multi-select.mdx b/src/components/multi-select/multi-select.mdx deleted file mode 100644 index 43a9c94c8a..0000000000 --- a/src/components/multi-select/multi-select.mdx +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Multi Select -route: /multi-select -menu: Components ---- - -# Multi Select - - - -## Example - - diff --git a/src/components/select-multiple/select-multiple.scss b/src/components/select-multiple/select-multiple.scss new file mode 100644 index 0000000000..a4cdd31001 --- /dev/null +++ b/src/components/select-multiple/select-multiple.scss @@ -0,0 +1,22 @@ +@import '../../style/variables'; + +@import '@lime-material-16px/form-field/mdc-form-field'; +@import '@lime-material-16px/checkbox/mdc-checkbox'; +@import "@lime-material-16px/floating-label/mdc-floating-label"; + +.multi-select { + position: relative; + + .multi-select-label { + padding-left: pxToRem(15); + } + + .mdc-form-field { + display: flex; + } + + .mdc-checkbox { + @include mdc-checkbox-ink-color(primary); + @include mdc-checkbox-container-colors(secondary, on-primary, secondary, on-primary); + } +} diff --git a/src/components/select-multiple/select-multiple.tsx b/src/components/select-multiple/select-multiple.tsx new file mode 100644 index 0000000000..dcb44806ba --- /dev/null +++ b/src/components/select-multiple/select-multiple.tsx @@ -0,0 +1,137 @@ +import { MDCCheckbox } from '@lime-material-16px/checkbox'; +import { MDCFormField } from '@lime-material-16px/form-field'; +import { + Component, + Element, + Event, + EventEmitter, + Prop, + State, +} from '@stencil/core'; +import { Option } from '../../interface'; +import { createRandomString } from '../../util/random-string'; + +@Component({ + tag: 'limel-select-multiple', + shadow: true, + styleUrl: 'select-multiple.scss', +}) +export class SelectMultiple { + @Prop({ reflectToAttr: true }) + public disabled = false; + + @Prop({ reflectToAttr: true }) + public label: string; + + @Prop() + public value: Option[] = []; + + @Prop() + public options: Option[] = []; + + @Event() + private change: EventEmitter; + + @Element() + private limelMultiSelect: HTMLElement; + + @State() + private fieldId = createRandomString(); + + @State() + private mdcCheckboxes = []; + + public componentDidLoad() { + const elements = Array.from( + this.limelMultiSelect.shadowRoot.querySelectorAll( + '.multi-select .mdc-form-field' + ) + ); + + elements.forEach(element => { + const formField = new MDCFormField(element); + const checkbox = new MDCCheckbox(element.firstChild); + formField.input = checkbox; + this.mdcCheckboxes.push(checkbox); + }); + + this.onChange(); + } + + public render() { + return ( + + { return option.text }).join(', ')}> + + {this.options.map((option: Option, index: number) => { + return this.renderCheckbox(index, option); + })} + + + + ); + } + + private renderCheckbox(index: number, option: Option) { + return ( + + + + + + + + + + + + {option.text} + + + ); + } + + private isOptionChecked(option: Option) { + return this.value.find(checkedOption => { + return checkedOption.value === option.value; + }); + } + + private onChange = (event?) => { + if (event) { + event.stopPropagation(); + } + + const checked = this.options.filter(option => { + const optionChecked = this.mdcCheckboxes.some(mdcCheckbox => { + return ( + mdcCheckbox.checked && mdcCheckbox.value === option.value + ); + }); + if (optionChecked) { + return option; + } + }); + this.change.emit(checked); + }; +} diff --git a/src/components/select/select.e2e.ts b/src/components/select-single/select-single.e2e.ts similarity index 100% rename from src/components/select/select.e2e.ts rename to src/components/select-single/select-single.e2e.ts diff --git a/src/components/select/select.scss b/src/components/select-single/select-single.scss similarity index 100% rename from src/components/select/select.scss rename to src/components/select-single/select-single.scss diff --git a/src/components/select-single/select-single.tsx b/src/components/select-single/select-single.tsx new file mode 100644 index 0000000000..f0e03ded80 --- /dev/null +++ b/src/components/select-single/select-single.tsx @@ -0,0 +1,135 @@ +import { MDCSelect } from '@lime-material-16px/select'; +import { + Component, + Element, + Event, + EventEmitter, + Prop, + State, + Watch, +} from '@stencil/core'; +import { Option } from '../../interface'; + +@Component({ + tag: 'limel-select-single', + shadow: true, + styleUrl: 'select-single.scss', +}) +export class SelectSingle { + /** + * Set to `true` to disable the input. + */ + @Prop({ reflectToAttr: true }) + public disabled = false; + + /** + * The input label. + */ + @Prop({ reflectToAttr: true }) + public label: string; + + /** + * The currently selected item. + */ + @Prop() + public value: Option; + + @Prop() + public options: Option[] = []; + + @Event() + private change: EventEmitter; + + @Element() + private limelSelect: HTMLElement; + + @State() + private mdcSelect; + + public componentDidLoad() { + const element = this.limelSelect.shadowRoot.querySelector( + '.mdc-select' + ); + this.mdcSelect = new MDCSelect(element); + this.onChange(); + } + + public componentDidUnload() { + this.mdcSelect.destroy(); + } + + public render() { + return ( + + + + {this.options.map(option => { + return ( + + {option.text} + + ); + })} + + + {this.label} + + + + ); + } + + @Watch('options') + protected optionsWatcher(newOptions) { + if (newOptions && newOptions.length) { + setTimeout(() => { + this.mdcSelect.selectedIndex = 0; + this.onChange(); + }, 0); + } else { + this.mdcSelect.value = null; + this.mdcSelect.selectedIndex = -1; + this.onChange(); + } + } + + private onChange = (event?) => { + if (event) { + event.stopPropagation(); + } + + const mdcValue = this.mdcSelect.value; + let value: Option; + if (mdcValue === '') { + value = null; + } else { + value = this.options.find(option => { + return mdcValue === option.value; + }); + } + this.change.emit(value); + }; +} diff --git a/src/components/select/test/select.test-wrapper.tsx b/src/components/select-single/test/select.test-wrapper.tsx similarity index 100% rename from src/components/select/test/select.test-wrapper.tsx rename to src/components/select-single/test/select.test-wrapper.tsx diff --git a/src/components/select/select.mdx b/src/components/select/select.mdx index 477d304e09..1782e02c03 100644 --- a/src/components/select/select.mdx +++ b/src/components/select/select.mdx @@ -14,6 +14,10 @@ When importing Option, see [Import Statements](/#import-statements). +### Select More Than One Option + + + ### Initially Empty diff --git a/src/components/select/select.tsx b/src/components/select/select.tsx index 4c99b9879c..021a09c22e 100644 --- a/src/components/select/select.tsx +++ b/src/components/select/select.tsx @@ -1,29 +1,35 @@ -import { MDCSelect } from '@lime-material-16px/select'; -import { - Component, - Element, - Event, - EventEmitter, - Prop, - State, - Watch, -} from '@stencil/core'; +import { Component, Element, Event, EventEmitter, Prop } from '@stencil/core'; import { Option } from '../../interface'; @Component({ tag: 'limel-select', shadow: true, - styleUrl: 'select.scss', }) export class Select { + /** + * Set to `true` to disable the input. + */ @Prop({ reflectToAttr: true }) public disabled = false; + /** + * The input label. + */ @Prop({ reflectToAttr: true }) public label: string; + /** + * Set to `true` to enable selection of more than one option. + */ + @Prop({ reflectToAttr: true }) + public multiple = false; + + /** + * When `multiple` is disabled: the currently selected item. + * When `multiple` is enabled: an array with the currently selected items. + */ @Prop() - public value: Option; + public value: Option | Option[]; @Prop() public options: Option[] = []; @@ -34,93 +40,41 @@ export class Select { @Element() private limelSelect: HTMLElement; - @State() - private mdcSelect; - - public componentDidLoad() { - const element = this.limelSelect.shadowRoot.querySelector( - '.mdc-select' - ); - this.mdcSelect = new MDCSelect(element); - this.onChange(); - } - - public componentDidUnload() { - this.mdcSelect.destroy(); + constructor() { + this.onChangeSingle = this.onChangeSingle.bind(this); + this.onChangeMultiple = this.onChangeMultiple.bind(this); } public render() { - return ( - - - - {this.options.map(option => { - return ( - - {option.text} - - ); - })} - - - {this.label} - - - + onChange={this.onChangeMultiple} + /> + ); + } + return ( + ); } - @Watch('options') - protected optionsWatcher(newOptions) { - if (newOptions && newOptions.length) { - setTimeout(() => { - this.mdcSelect.selectedIndex = 0; - this.onChange(); - }, 0); - } else { - this.mdcSelect.value = null; - this.mdcSelect.selectedIndex = -1; - this.onChange(); - } + private onChangeSingle(event) { + event.stopPropagation(); + this.change.emit(event.detail); } - private onChange = (event?) => { - if (event) { - event.stopPropagation(); - } - - const mdcValue = this.mdcSelect.value; - let value: Option; - if (mdcValue === '') { - value = null; - } else { - value = this.options.find(option => { - return mdcValue === option.value; - }); - } - this.change.emit(value); - }; + private onChangeMultiple(event) { + event.stopPropagation(); + this.change.emit(event.detail); + } } diff --git a/src/examples/multi-select/multi-select.scss b/src/examples/multi-select/multi-select.scss deleted file mode 100644 index bf46b7198b..0000000000 --- a/src/examples/multi-select/multi-select.scss +++ /dev/null @@ -1,3 +0,0 @@ -p { - font-size: small; -} diff --git a/src/examples/multi-select/multi-select.tsx b/src/examples/multi-select/multi-select.tsx deleted file mode 100644 index 811303c1e1..0000000000 --- a/src/examples/multi-select/multi-select.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Component, State } from '@stencil/core'; -import { Option } from '../../components/select/option.types'; - -@Component({ - shadow: true, - tag: 'limel-example-multi-select', - styleUrl: 'multi-select.scss', -}) -export class MultiSelectExample { - @State() - private value: Option[] = [{ text: 'Han Solo', value: 'han' }]; - - @State() - private disabled = false; - - private options: Option[] = [ - { text: 'Luke Skywalker', value: 'luke' }, - { text: 'Han Solo', value: 'han' }, - { text: 'Leia Organo', value: 'leia' }, - ]; - - constructor() { - this.onChange = this.onChange.bind(this); - this.toggleEnabled = this.toggleEnabled.bind(this); - } - - public render() { - return [ - , - - - - - , - Value: {JSON.stringify(this.value)}, - ]; - } - - private onChange(event) { - this.value = event.detail; - } - - private toggleEnabled() { - this.disabled = !this.disabled; - } -} diff --git a/src/examples/select/select-multiple.tsx b/src/examples/select/select-multiple.tsx new file mode 100644 index 0000000000..e0ea27eed5 --- /dev/null +++ b/src/examples/select/select-multiple.tsx @@ -0,0 +1,65 @@ +import { Component, State } from '@stencil/core'; +import { Option } from '../../interface'; + +@Component({ + shadow: true, + tag: 'limel-example-select-multiple', + styleUrl: 'select.scss', +}) +export class SelectMultipleExample { + @State() + public value: Option; + + @State() + public disabled = false; + + private options: Option[] = [ + { text: 'Luke Skywalker', value: 'luke' }, + { text: 'Han Solo', value: 'han' }, + { text: 'Leia Organo', value: 'leia' }, + { text: 'Homer Simpson', value: 'homer' }, + { text: 'Moe Szyslak', value: 'moe' }, + { text: 'Ned Flanders', value: 'ned' }, + { text: 'David Tennant', value: '10' }, + { text: 'Matt Smith', value: '11' }, + { text: 'Peter Capaldi', value: '12' }, + { text: 'Jodie Witthaker', value: '13' }, + ]; + + constructor() { + this.onChange = this.onChange.bind(this); + this.toggleEnabled = this.toggleEnabled.bind(this); + } + + public render() { + return ( + + + + + + + + Value: {JSON.stringify(this.value)} + + ); + } + + private onChange(event) { + this.value = event.detail; + } + + private toggleEnabled() { + this.disabled = !this.disabled; + } +}
- - - -
Value: {JSON.stringify(this.value)}
+ + + +