From a404ddc2f40b104f99ed58da3a764ba041e82209 Mon Sep 17 00:00:00 2001 From: Sergiu Cazac Date: Thu, 6 Apr 2023 18:21:35 +0300 Subject: [PATCH 1/4] UiSelect improvement + clearable UiSelect and UiDatepicker --- docs-src/DocsSidebar.vue | 5 +- docs-src/pages/UiDatepicker.vue | 1 + docs-src/pages/UiSelect.vue | 35 ++++--------- src/UiDatepicker.vue | 32 +++++++++++- src/UiSelect.vue | 88 ++++++++++++++++++++++++++------- src/styles/variables.scss | 2 +- 6 files changed, 113 insertions(+), 50 deletions(-) diff --git a/docs-src/DocsSidebar.vue b/docs-src/DocsSidebar.vue index 8a88f193..88441b3e 100644 --- a/docs-src/DocsSidebar.vue +++ b/docs-src/DocsSidebar.vue @@ -98,10 +98,7 @@ export default { { label: "1.3.x", value: "1.3.2" }, { label: "1.4.x", value: "1.4.0" }, ], - selectedVersion: { - label: "1.4.x", - value: "1.4.0", - }, + selectedVersion: "1.4.0", menu, }; }, diff --git a/docs-src/pages/UiDatepicker.vue b/docs-src/pages/UiDatepicker.vue index e6cac6b6..4ba843f1 100644 --- a/docs-src/pages/UiDatepicker.vue +++ b/docs-src/pages/UiDatepicker.vue @@ -18,6 +18,7 @@ Your Birthday

Floating label

diff --git a/docs-src/pages/UiSelect.vue b/docs-src/pages/UiSelect.vue index c24d605b..bc26cfc7 100644 --- a/docs-src/pages/UiSelect.vue +++ b/docs-src/pages/UiSelect.vue @@ -16,6 +16,7 @@ @@ -226,7 +227,7 @@ modelValue, v-model * - String, Number, Object or Array + String, Number, Boolean, or Array

The model the selected value or values sync to. Can be set initially for default value/values.

@@ -236,14 +237,16 @@ options - Array + Array, Object [] -

An array of options to show to the user.

+

An array or object of options to show to the user.

Can be a plain array, e.g. ['Red', 'Blue', 'Green'] as well as an array of objects.

For a plain array, the option is shown to the user and it is used for filtering.

-

For an array of objects, the label is shown to the user and is used for filtering, and the modelValue is submitted to the server. If provided, class, will be applied to the option element's class attribute. You can redefine these keys to fit your data using the keys prop.

+

For an array of objects, the label is shown to the user and is used for filtering, and the value is submitted to the server. If provided, class, will be applied to the option element's class attribute. You can redefine these keys to fit your data using the keys prop.

+ +

For an object, the value is shown to the user and is used for filtering, and the key is submitted to the server. The keys prop is ignored when using an Object.

The entire option is written to the model when the user makes a selection.

@@ -704,25 +707,10 @@ export default { select2o5: '', select3: 'Orange', select4: '', - select5: { - label: 'Lavender', - image: 'https://via.placeholder.com/64/e6e6fa/e6e6fa', - value: 'lavender' - }, + select5: 'lavender', select6: '', select7: '', - select8: [ - { - label: 'Orange', - image: 'https://via.placeholder.com/64/ffa500/ffa500', - value: 'orange' - }, - { - label: 'Lime', - image: 'https://via.placeholder.com/64/00ff00/00ff00', - value: 'lime' - } - ], + select8: ['orange', 'lime'], select9: [], select10: [], select10Touched: false, @@ -731,10 +719,7 @@ export default { select11Loading: false, select11NoResults: false, select11LoadingTimeout: null, - select12: { - name: 'Australia', - code: 'AU' - }, + select12: 'AU', select12o5: '', select13: '', select14: 'Peach', diff --git a/src/UiDatepicker.vue b/src/UiDatepicker.vue index fee47ff6..f699d537 100644 --- a/src/UiDatepicker.vue +++ b/src/UiDatepicker.vue @@ -23,6 +23,19 @@ {{ label }} + + + + + +
{{ @@ -174,6 +187,10 @@ export default { type: Boolean, default: false, }, + hasClearButton: { + type: Boolean, + default: false, + }, help: String, error: String, disabled: { @@ -431,7 +448,7 @@ export default { padding-top: $ui-input-icon-margin-top--with-label; } - .ui-datepicker__dropdown-button { + .ui-datepicker__clear-button { top: $ui-input-button-margin-top--with-label; } } @@ -488,6 +505,19 @@ export default { width: 100%; } +.ui-datepicker__clear-button { + color: $ui-input-button-color; + cursor: pointer; + font-size: $ui-input-button-size; + position: absolute; + right: $ui-input-button-margin-top * 2; + top: $ui-input-button-margin-top; + + &:hover { + color: $ui-input-button-color--hover; + } +} + .ui-datepicker__icon-wrapper { flex-shrink: 0; margin-right: $ui-input-icon-margin-right; diff --git a/src/UiSelect.vue b/src/UiSelect.vue index 8c063083..124206b5 100644 --- a/src/UiSelect.vue +++ b/src/UiSelect.vue @@ -28,6 +28,19 @@ {{ label }}
+ + + + + +
{{ @@ -140,7 +153,6 @@ import UiProgressCircular from "./UiProgressCircular.vue"; import UiSelectOption from "./UiSelectOption.vue"; import RespondsToExternalClick from "./mixins/RespondsToExternalClick"; -import { looseIndexOf, looseEqual } from "./helpers/util"; import { scrollIntoView, resetScroll } from "./helpers/element-scroll"; import fuzzysearch from "fuzzysearch"; @@ -161,11 +173,10 @@ export default { name: String, tabindex: [String, Number], modelValue: { - type: [String, Number, Object, Array], - required: true, + type: [String, Number, Boolean, Array] }, options: { - type: Array, + type: [Array, Object], default() { return []; }, @@ -185,6 +196,10 @@ export default { type: String, default: "basic", // 'basic' or 'image' }, + hasClearButton: { + type: Boolean, + default: false, + }, multiple: { type: Boolean, default: false, @@ -306,12 +321,28 @@ export default { return Boolean(this.help) || Boolean(this.$slots.help); }, + preparedOptions () { + if (Array.isArray(this.options)) { + return this.options.map(option => typeof option !== 'object' + ? { [this.keys.label]: option, [this.keys.value]: option } + : option + ) + } + + return Object.keys(this.options).map((value) => { + return { + label: this.options[value], + value + } + }) + }, + filteredOptions() { if (this.disableFilter) { - return this.options; + return this.preparedOptions; } - const options = this.options.filter((option) => { + const options = this.preparedOptions.filter((option) => { if (this.filter) { return this.filter(option, this.query, this.defaultFilter); } @@ -327,17 +358,22 @@ export default { }, displayText() { + const getLabel = (value) => { + const selectedOption = value ? this.preparedOptions.find(o => o[this.keys.value] === value) : null; + return selectedOption ? selectedOption[this.keys.label] : ""; + }; + if (this.multiple) { if (this.modelValue.length > 0) { return this.modelValue - .map((value) => value[this.keys.label] || value) + .map(value => getLabel(value)) .join(this.multipleDelimiter); } return ""; } - return this.modelValue ? this.modelValue[this.keys.label] || this.modelValue : ""; + return getLabel(this.modelValue); }, hasDisplayText() { @@ -360,14 +396,14 @@ export default { } if (Array.isArray(this.modelValue)) { - return this.modelValue.map((option) => option[this.keys.value] || option).join(","); + return this.modelValue.join(","); } - return this.modelValue[this.keys.value] || this.modelValue; + return this.modelValue; }, emptyValue() { - return this.multiple ? [] : ""; + return this.multiple ? [] : null; }, }, @@ -392,8 +428,7 @@ export default { created() { const invalidMultipleValue = this.multiple && !Array.isArray(this.modelValue); - const invalidEmptyValue = !this.modelValue && this.modelValue !== ""; - if (invalidMultipleValue || invalidEmptyValue) { + if (invalidMultipleValue) { this.$emit("update:modelValue", this.emptyValue); this.initialValue = JSON.stringify(this.emptyValue); } @@ -439,7 +474,7 @@ export default { if (this.multiple) { this.updateOption(option, { select: shouldSelect }); } else { - this.setValue(option); + this.setValue(option[this.keys.value]); this.selectedIndex = index; } @@ -460,19 +495,19 @@ export default { isOptionSelected(option) { if (this.multiple) { - return looseIndexOf(this.modelValue, option) > -1; + return this.modelValue.indexOf(option[this.keys.value]) > -1; } - return looseEqual(this.modelValue, option); + return this.modelValue === option[this.keys.value]; }, updateOption(option, options = { select: true }) { let value = []; let updated = false; - const i = looseIndexOf(this.modelValue, option); + const i = this.modelValue.indexOf(option[this.keys.value]); if (options.select && i < 0) { - value = this.modelValue.concat(option); + value = this.modelValue.concat(option[this.keys.value]); updated = true; } @@ -660,12 +695,14 @@ export default { } } + $ui-icon-size: 1em !default; + &.has-label { .ui-select__icon-wrapper { padding-top: $ui-input-icon-margin-top--with-label; } - .ui-select__dropdown-button { + .ui-select__clear-button { top: $ui-input-button-margin-top--with-label; } } @@ -732,6 +769,19 @@ export default { width: 100%; } +.ui-select__clear-button { + color: $ui-input-button-color; + cursor: pointer; + font-size: $ui-input-button-size; + position: absolute; + right: $ui-input-button-margin-top * 2; + top: $ui-input-button-margin-top; + + &:hover { + color: $ui-input-button-color--hover; + } +} + .ui-select__icon-wrapper { flex-shrink: 0; margin-right: $ui-input-icon-margin-right; diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 10f96eb2..88ddb73f 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -73,7 +73,7 @@ $ui-input-button-color--hover : $primary-text-color !default; $ui-input-button-opacity--disabled : 0.6 !default; $ui-input-button-size : rem(18px) !default; $ui-input-button-margin-top : rem(7px) !default; -$ui-input-button-margin-top--with-label : rem(27px) !default; +$ui-input-button-margin-top--with-label : rem(25px) !default; // Input feedback, help and error text $ui-input-feedback-font-size : rem(14px) !default; From e72b7dc41a55539e31d2bd6a1aae8abbdf0de6d3 Mon Sep 17 00:00:00 2001 From: Sergiu Cazac Date: Thu, 6 Apr 2023 19:33:11 +0300 Subject: [PATCH 2/4] Datepicker clear --- src/UiDatepicker.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UiDatepicker.vue b/src/UiDatepicker.vue index f699d537..827662c3 100644 --- a/src/UiDatepicker.vue +++ b/src/UiDatepicker.vue @@ -27,7 +27,7 @@ v-show="hasClearButton && !disabled && modelValue" class="ui-datepicker__clear-button" title="Clear" - @click.stop="$emit('update:modelValue', null)" + @click.stop="clear" > Date: Wed, 12 Apr 2023 14:19:23 +0300 Subject: [PATCH 3/4] Datepicker clear documentation and examples --- docs-src/pages/UiDatepicker.vue | 22 ++++++++++++++++++++-- docs-src/pages/UiSelect.vue | 22 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/docs-src/pages/UiDatepicker.vue b/docs-src/pages/UiDatepicker.vue index 4ba843f1..be58d62e 100644 --- a/docs-src/pages/UiDatepicker.vue +++ b/docs-src/pages/UiDatepicker.vue @@ -18,7 +18,6 @@ Your Birthday

Floating label

@@ -209,6 +208,14 @@ disabled placeholder="Select a date" >A Special Day + +

With a clear button

+ + A Special Day

API

@@ -453,6 +460,16 @@

Whether or not the datepicker is disabled. Set to true to disable the datepicker.

+ + + hasClearButton + Boolean + false + +

Whether or not the datepicker should include a value clear button.

+

Set to true to show a clear button. Clicking it will set the value to an empty one depending on the datepicker type.

+ +
@@ -652,7 +669,8 @@ export default { picker13: null, picker1301: null, picker14: null, - picker15: new Date() + picker15: new Date(), + picker16: new Date() }; }, diff --git a/docs-src/pages/UiSelect.vue b/docs-src/pages/UiSelect.vue index bc26cfc7..4b81e98e 100644 --- a/docs-src/pages/UiSelect.vue +++ b/docs-src/pages/UiSelect.vue @@ -16,7 +16,6 @@ @@ -198,6 +197,16 @@ placeholder="Select a colour" :options="colourStrings" > + +

With a clear button

+ +

API

@@ -473,6 +482,16 @@

Whether or not the select is disabled. Set to true to disable the select.

+ + + hasClearButton + Boolean + false + +

Whether or not the select should include a value clear button.

+

Set to true to show a clear button. Clicking it will set the value to null.

+ + @@ -723,6 +742,7 @@ export default { select12o5: '', select13: '', select14: 'Peach', + select15: 'Peach', colours, colourStrings, countries From 21d01615e6eeb24d78963691b7c5176b299a0390 Mon Sep 17 00:00:00 2001 From: Sergiu Cazac Date: Wed, 12 Apr 2023 17:10:31 +0300 Subject: [PATCH 4/4] Format/lint fix --- src/UiSelect.vue | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/UiSelect.vue b/src/UiSelect.vue index 124206b5..a3cc0136 100644 --- a/src/UiSelect.vue +++ b/src/UiSelect.vue @@ -29,7 +29,9 @@ typeof option !== 'object' - ? { [this.keys.label]: option, [this.keys.value]: option } - : option - ) + return this.options.map((option) => + typeof option !== "object" + ? { [this.keys.label]: option, [this.keys.value]: option } + : option + ); } return Object.keys(this.options).map((value) => { return { label: this.options[value], - value - } - }) + value, + }; + }); }, filteredOptions() { @@ -359,15 +362,15 @@ export default { displayText() { const getLabel = (value) => { - const selectedOption = value ? this.preparedOptions.find(o => o[this.keys.value] === value) : null; + const selectedOption = value + ? this.preparedOptions.find((o) => o[this.keys.value] === value) + : null; return selectedOption ? selectedOption[this.keys.label] : ""; }; if (this.multiple) { if (this.modelValue.length > 0) { - return this.modelValue - .map(value => getLabel(value)) - .join(this.multipleDelimiter); + return this.modelValue.map((value) => getLabel(value)).join(this.multipleDelimiter); } return "";