Input Slider Feature (#292)

* feat: input slider feature

* Polish slider label

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Favour Olayinka 2024-01-29 12:57:40 +01:00 committed by GitHub
parent 2ac5f56a93
commit 0c88c9842a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 164 additions and 1 deletions

View File

@ -0,0 +1,100 @@
<template>
<input-wrapper v-bind="inputWrapperProps">
<template #label>
<slot name="label" />
</template>
<div class="flex space-x-2">
<div class="flex-1 relative">
<div class="font-medium text-sm absolute -top-[6px]"
:style="labelStyle"
>
<div class="">
{{ compVal }}
</div>
</div>
<input
type="range"
class="w-full mt-3"
:min="minSlider"
:max="maxSlider"
:step="stepSlider"
v-model="compVal"
/>
<div class="grid grid-cols-3 gap-2 -mt-1">
<div
v-for="i in sliderLabelsList"
:key="i"
:class="[
theme.SliderInput.stepLabel,
i.style,
]"
>
{{ i.label }}
</div>
</div>
</div>
</div>
<template #help>
<slot name="help" />
</template>
<template #error>
<slot name="error" />
</template>
</input-wrapper>
</template>
<script>
import { inputProps, useFormInput } from "./useFormInput.js";
import InputWrapper from "./components/InputWrapper.vue";
export default {
name: "SliderInput",
components: { InputWrapper },
props: {
...inputProps,
minSlider: { type: Number, default: 0 },
maxSlider: { type: Number, default: 50 },
stepSlider: { type: Number, default: 5 },
},
setup(props, context) {
return {
...useFormInput(props, context),
};
},
computed: {
labelStyle() {
const ratio = ((this.compVal-this.minSlider) / (this.maxSlider-this.minSlider)) * 100
return {
left: `${ratio}%`,
marginLeft: `-${ratio/100*15}px`
}
},
sliderLabelsList() {
const midPoint = (this.maxSlider - this.minSlider) / 2 + this.minSlider;
const labels = [
{
label: `${this.minSlider}`,
style: "flex items-center justify-start",
},
{
label: Math.floor(midPoint),
style: "flex items-center justify-center",
},
{
label: `${this.maxSlider}`,
style: "flex items-center justify-end",
},
];
return labels;
},
},
mounted() {
this.compVal = parseInt(this.compVal ?? this.minSlider);
},
};
</script>

View File

@ -141,6 +141,9 @@ export default {
if (field.type === 'number' && field.is_scale && field.scale_max_value) { if (field.type === 'number' && field.is_scale && field.scale_max_value) {
return 'ScaleInput' return 'ScaleInput'
} }
if (field.type === 'number' && field.is_slider && field.slider_max_value) {
return 'SliderInput'
}
if (['select', 'multi_select'].includes(field.type) && field.without_dropdown) { if (['select', 'multi_select'].includes(field.type) && field.without_dropdown) {
return 'FlatSelectInput' return 'FlatSelectInput'
} }
@ -308,6 +311,10 @@ export default {
inputProperties.minScale = parseInt(field.scale_min_value) ?? 1 inputProperties.minScale = parseInt(field.scale_min_value) ?? 1
inputProperties.maxScale = parseInt(field.scale_max_value) ?? 5 inputProperties.maxScale = parseInt(field.scale_max_value) ?? 5
inputProperties.stepScale = parseInt(field.scale_step_value) ?? 1 inputProperties.stepScale = parseInt(field.scale_step_value) ?? 1
} else if (field.type === 'number' && field.is_slider) {
inputProperties.minSlider = parseInt(field.slider_min_value) ?? 0
inputProperties.maxSlider = parseInt(field.slider_max_value) ?? 50
inputProperties.stepSlider = parseInt(field.slider_step_value) ?? 5
} else if (field.type === 'number' || (field.type === 'phone_number' && field.use_simple_text_input)) { } else if (field.type === 'number' || (field.type === 'phone_number' && field.use_simple_text_input)) {
inputProperties.pattern = '/\d*' inputProperties.pattern = '/\d*'
} else if (field.type === 'phone_number' && !field.use_simple_text_input) { } else if (field.type === 'phone_number' && !field.use_simple_text_input) {

View File

@ -103,6 +103,29 @@
label="Scale steps value" label="Scale steps value"
/> />
</template> </template>
<v-checkbox v-model="field.is_slider" class="mt-4"
:name="field.id+'_is_slider'" @update:model-value="initSlider"
>
Slider
</v-checkbox>
<p class="text-gray-400 text-xs mb-5">
Transform this field into a slider input.
</p>
<template v-if="field.is_slider">
<text-input name="slider_min_value" native-type="number" class="mt-4"
:form="field" required
label="Min slider value"
/>
<text-input name="slider_max_value" native-type="number" :min="1" class="mt-4"
:form="field" required
label="Max slider value"
/>
<text-input name="slider_step_value" native-type="number" :min="1" class="mt-4"
:form="field" required
label="Slider steps value"
/>
</template>
</div> </div>
<!-- Text Options --> <!-- Text Options -->
@ -535,6 +558,7 @@ export default {
initRating () { initRating () {
if (this.field.is_rating) { if (this.field.is_rating) {
this.field.is_scale = false this.field.is_scale = false
this.field.is_slider = false
if (!this.field.rating_max_value) { if (!this.field.rating_max_value) {
this.field.rating_max_value = 5 this.field.rating_max_value = 5
} }
@ -543,6 +567,7 @@ export default {
initScale () { initScale () {
if (this.field.is_scale) { if (this.field.is_scale) {
this.field.is_rating = false this.field.is_rating = false
this.field.is_slider = false
if (!this.field.scale_min_value) { if (!this.field.scale_min_value) {
this.field.scale_min_value = 1 this.field.scale_min_value = 1
} }
@ -554,6 +579,22 @@ export default {
} }
} }
}, },
initSlider () {
if (this.field.is_slider) {
this.field.is_rating = false
this.field.is_scale = false
if (!this.field.slider_min_value) {
this.field.slider_min_value = 0
}
if (!this.field.slider_max_value) {
this.field.slider_max_value = 50
}
if (!this.field.slider_step_value) {
this.field.slider_step_value = 1
}
}
},
onFieldOptionsChange (val) { onFieldOptionsChange (val) {
const vals = (val) ? val.trim().split('\n') : [] const vals = (val) ? val.trim().split('\n') : []
const tmpOpts = vals.map(name => { const tmpOpts = vals.map(name => {

View File

@ -32,6 +32,11 @@ export const themes = {
unselectedButton: 'bg-white hover:bg-gray-50 border', unselectedButton: 'bg-white hover:bg-gray-50 border',
help: 'text-gray-400 dark:text-gray-500' help: 'text-gray-400 dark:text-gray-500'
}, },
SliderInput: {
label: 'text-gray-700 dark:text-gray-300 font-semibold',
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs',
help: 'text-gray-400 dark:text-gray-500'
},
fileInput: { fileInput: {
input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded-lg', input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded-lg',
inputHover: { inputHover: {
@ -71,6 +76,11 @@ export const themes = {
unselectedButton: 'bg-white hover:bg-gray-50 border -mx-4', unselectedButton: 'bg-white hover:bg-gray-50 border -mx-4',
help: 'text-gray-400 dark:text-gray-500' help: 'text-gray-400 dark:text-gray-500'
}, },
SliderInput: {
label: 'text-gray-700 dark:text-gray-300 font-semibold',
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs',
help: 'text-gray-400 dark:text-gray-500'
},
fileInput: { fileInput: {
input: 'min-h-40 border border-dashed border-gray-300 p-4', input: 'min-h-40 border border-dashed border-gray-300 p-4',
inputHover: { inputHover: {
@ -110,6 +120,11 @@ export const themes = {
unselectedButton: 'bg-notion-input-background dark:bg-notion-dark-light hover:bg-gray-50 border', unselectedButton: 'bg-notion-input-background dark:bg-notion-dark-light hover:bg-gray-50 border',
help: 'text-notion-input-help dark:text-gray-500' help: 'text-notion-input-help dark:text-gray-500'
}, },
SliderInput: {
label: 'text-gray-700 dark:text-gray-300 font-semibold',
stepLabel: 'text-gray-700 dark:text-gray-300 text-center text-xs',
help: 'text-gray-400 dark:text-gray-500'
},
fileInput: { fileInput: {
input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded bg-notion-input-background', input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded bg-notion-input-background',
inputHover: { inputHover: {