From e99d3faf931c771f93d51dbb28f019a24cdaccf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leandro=20In=C3=A1cio?= Date: Tue, 29 Aug 2023 15:50:13 +0200 Subject: [PATCH] :sparkles: Introduce customizable bullets for HdRange (#1188) --- src/components/form/HdRange.vue | 62 +++- src/stories/HdRange.stories.js | 319 +++++++++------------ tests/unit/components/form/HdRange.spec.js | 27 ++ 3 files changed, 219 insertions(+), 189 deletions(-) diff --git a/src/components/form/HdRange.vue b/src/components/form/HdRange.vue index b1e22c482..a74263b19 100644 --- a/src/components/form/HdRange.vue +++ b/src/components/form/HdRange.vue @@ -28,16 +28,19 @@ class="range__progress" /> -
- +
+
@@ -59,6 +62,7 @@ import formField from './formFieldMixin'; export default { name: 'HdRange', mixins: [formField], + inheritAttrs: false, props: { name: { type: String, @@ -112,6 +116,12 @@ export default { type: String, default: '', // falls back to the internal styles }, + stepBullets: { + type: Array, + required: false, + default: () => [], + validator: (steps) => steps.filter((s) => typeof s === 'number').length === steps.length, + }, }, data() { return { @@ -153,6 +163,7 @@ export default { this.updateUI(); }, value() { + this.updateToClosestValue(); this.adjustValue(); this.updateUI(); }, @@ -198,6 +209,37 @@ export default { blurHandler() { this.isActive = false; }, + isStepBulletVisible(value) { + return this.stepBullets.includes(value); + }, + customStepBulletOffset(value) { + if (!this.stepBullets.length) return {}; // Early return standard bullets + const stepPosition = this.stepBullets.indexOf(value); + const valuePercentage = (value - this.min) / (this.max - this.min); + let valuePercentageInputWidthPixels = this.trackWidth * valuePercentage; + + if (stepPosition > 0) { + const previousValue = this.stepBullets[stepPosition - 1]; + const previousValuePercentage = (previousValue - this.min) / (this.max - this.min); + const previousPercentageInputWidthPixels = this.trackWidth * previousValuePercentage; + valuePercentageInputWidthPixels -= previousPercentageInputWidthPixels; + } + + return { + paddingLeft: `${valuePercentageInputWidthPixels}px`, + }; + }, + getClosestValueInStepBullets(value) { + return this.stepBullets.reduce((currentClosest, currentValue) => { + const currentClosestDiff = Math.abs(currentClosest - value); + const currentValueDiff = Math.abs(currentValue - value); + return currentClosestDiff > currentValueDiff ? currentValue : currentClosest; + }, 0); + }, + updateToClosestValue() { + if (!this.stepBullets.length) return; + this.computedValue = this.getClosestValueInStepBullets(this.computedValue); + }, }, }; diff --git a/src/stories/HdRange.stories.js b/src/stories/HdRange.stories.js index 4d69ca4fb..1d091ad1c 100644 --- a/src/stories/HdRange.stories.js +++ b/src/stories/HdRange.stories.js @@ -1,182 +1,143 @@ -/* eslint-disable import/no-extraneous-dependencies, no-console */ -import { storiesOf } from '@storybook/vue'; import HdRange from 'homeday-blocks/src/components/form/HdRange.vue'; -import FormWrapper from 'homeday-blocks/src/storiesWrappers/FormWrapper'; -import { number, boolean, text } from '@storybook/addon-knobs'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { text } from '@storybook/addon-knobs'; -storiesOf('Components/Form/HdRange', module) - .addDecorator(FormWrapper) - .add('default 🎛', () => ({ - components: { HdRange }, - template: ` -
- -

Value: {{ currentValue }}

-
- `, - props: { - value: { - type: Number, - default: number('value', 50), - }, - min: { - type: Number, - default: number('min', 0), - }, - max: { - type: Number, - default: number('max', 100), - }, - step: { - type: Number, - default: number('step', 10), - }, - disabled: { - type: Boolean, - default: boolean('disabled', false), - }, - displayStepBullets: { - type: Boolean, - default: boolean('displayStepBullets', false), - }, - }, - watch: { - value() { - this.currentValue = this.value; - }, - }, - data() { - return { - currentValue: this.value, - }; - }, - })) - .add('with bullets', () => ({ - components: { HdRange }, - template: ` -
- -

Value: {{ currentValue }}

-
- `, - data() { - return { - currentValue: 50, - }; - }, - })) - .add('with tooltip', () => ({ - components: { HdRange }, - template: ` -
- -

Value: {{ currentValue }}

-
- `, - data() { - return { - currentValue: 50, - }; - }, - })) - .add('with custom tooltip value', () => ({ - components: { HdRange }, - template: ` -
- -

Value: {{ currentValue }}

-
- `, - data() { - return { - currentValue: 0, - }; - }, - computed: { - tooltipValue() { - return ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'][ - this.currentValue - ]; - }, - }, - })) - .add('with labels', () => ({ - components: { HdRange }, - template: ` -
- -

Value: {{ currentValue }}

-
- `, - data() { - return { - currentValue: 0, - labels: ['Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.', 'Sun.'], - }; - }, - })) - .add('custom backgrounds 🎛', () => ({ - components: { HdRange }, - template: ` -
- -

Value: {{ currentValue }}

-
- `, - props: { - trackBackground: { - type: String, - default: text( - 'track-background', - 'radial-gradient(circle at center, #4CBA38, #FFE713, #E00016)' - ), - }, - progressBackground: { - type: String, - default: text('progress-background', 'transparent'), - }, - }, - data() { - return { - currentValue: 2, - }; +export default { + title: 'Components/Form/HdRange', + component: HdRange, + args: { + name: 'storybook', + required: false, + disabled: false, + min: 0, + max: 100, + step: 1, + value: 50, + labels: [], + displayStepBullets: false, + displayTooltip: false, + tooltipValue: '', + trackBackground: '', + progressBackground: '', + stepBullets: [], + }, +}; + +const Template = (_, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { HdRange }, + template: ` +
+ +

Value: {{ currentValue }}

+
+ `, + data() { + return { + currentValue: 50, + }; + }, +}); + +export const Default = Template.bind({}); + +export const WithBullets = Template.bind({}); +WithBullets.args = { + displayStepBullets: true, + step: 20, +}; + +export const WithTooltip = Template.bind({}); +WithTooltip.args = { + displayTooltip: true, +}; + +export const WithLabels = (_, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { HdRange }, + template: ` +
+ +

Value: {{ currentValue }}

+
+ `, + data() { + return { + currentValue: 2, + }; + }, +}); +WithLabels.args = { + min: 0, + max: 6, + displayStepBullets: true, + labels: ['Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.', 'Sun.'], +}; + +export const CustomBackgrounds = Template.bind({}); +CustomBackgrounds.args = { + trackBackground: text( + 'track-background', + 'radial-gradient(circle at center, #4CBA38, #FFE713, #E00016)' + ), + progressBackground: text('progress-background', 'transparent'), +}; + +export const WithCustomTooltipValue = (_, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { HdRange }, + template: ` +
+ +

Value: {{ currentValue }}

+
+ `, + data() { + return { + currentValue: 2, + }; + }, + computed: { + tooltipVal() { + return ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'][ + this.currentValue + ]; }, - })); + }, +}); +WithCustomTooltipValue.args = { + ...WithLabels.args, + displayTooltip: true, +}; + +export const WithCustomStepBullets = (_, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { HdRange }, + template: ` +
+ +

Value: {{ currentValue }}

+
+ `, + data() { + return { + currentValue: 26, + }; + }, +}); diff --git a/tests/unit/components/form/HdRange.spec.js b/tests/unit/components/form/HdRange.spec.js index 44c62104e..75d5ee783 100644 --- a/tests/unit/components/form/HdRange.spec.js +++ b/tests/unit/components/form/HdRange.spec.js @@ -243,4 +243,31 @@ describe('HdRange', () => { expect(mockedUpdateUI).toHaveBeenCalledTimes(2); }); + + describe('Custom step bullets', () => { + const VISIBLE_STEP_BULLETS_SELECTOR = '.range__step'; + const stepBulletsWrapperBuilder = wrapperFactoryBuilder(HdRange, { + props: { + name: 'storybook', + min: 1, + max: 100, + step: 1, + value: 50, + labels: [], + displayStepBullets: false, + stepBullets: [1, 10, 50, 60, 100], + }, + }); + + it('renders custom step bullets', () => { + const wrapper = stepBulletsWrapperBuilder(); + expect(wrapper.findAll(VISIBLE_STEP_BULLETS_SELECTOR).length).toBe(5); + }); + it('updates value to closest step bullet', async () => { + const wrapper = stepBulletsWrapperBuilder(); + wrapper.setProps({ value: 90 }); + await wrapper.vm.$nextTick(); + expect(wrapper.emitted().input[0][0]).toBe(100); + }); + }); });