Number input as Scale (#227)
* Number input as Scale * scale input --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
parent
8a2e071c56
commit
7c03d20cc4
|
@ -0,0 +1,102 @@
|
||||||
|
<template>
|
||||||
|
<div :class="wrapperClass" :style="inputStyle">
|
||||||
|
<label v-if="label" :for="id?id:name"
|
||||||
|
:class="[theme.default.label,{'uppercase text-xs':uppercaseLabels, 'text-sm':!uppercaseLabels}]"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
<span v-if="required" class="text-red-500 required-dot">*</span>
|
||||||
|
</label>
|
||||||
|
<small v-if="help && helpPosition=='above_input'" :class="theme.default.help" class="flex mb-1">
|
||||||
|
<slot name="help"><span class="field-help" v-html="help" /></slot>
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<div class="rectangle-outer grid grid-cols-5 gap-2">
|
||||||
|
<div v-for="i in scaleList" :key="i"
|
||||||
|
:class="[{'font-semibold':compVal===i},theme.ScaleInput.button, compVal!==i ? unselectedButtonClass: '']"
|
||||||
|
:style="btnStyle(i===compVal)"
|
||||||
|
role="button" @click="setScale(i)"
|
||||||
|
>
|
||||||
|
{{ i }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small v-if="help && helpPosition=='below_input'" :class="theme.default.help">
|
||||||
|
<slot name="help"><span class="field-help" v-html="help" /></slot>
|
||||||
|
</small>
|
||||||
|
<has-error v-if="hasValidation" :form="form" :field="name" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import inputMixin from '~/mixins/forms/input.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ScaleInput',
|
||||||
|
|
||||||
|
mixins: [inputMixin],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
minScale: { type: Number, default: 1 },
|
||||||
|
maxScale: { type: Number, default: 5 },
|
||||||
|
stepScale: { type: Number, default: 1 }
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
scaleList () {
|
||||||
|
let list = []
|
||||||
|
for (let i = this.minScale; i <= this.maxScale; i += this.stepScale) {
|
||||||
|
list.push(i)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
},
|
||||||
|
unselectedButtonClass () {
|
||||||
|
return this.theme.ScaleInput.unselectedButton
|
||||||
|
},
|
||||||
|
textColor () {
|
||||||
|
const color = (this.color.charAt(0) === '#') ? this.color.substring(1, 7) : this.color
|
||||||
|
const r = parseInt(color.substring(0, 2), 16) // hexToR
|
||||||
|
const g = parseInt(color.substring(2, 4), 16) // hexToG
|
||||||
|
const b = parseInt(color.substring(4, 6), 16) // hexToB
|
||||||
|
const uicolors = [r / 255, g / 255, b / 255]
|
||||||
|
const c = uicolors.map((col) => {
|
||||||
|
if (col <= 0.03928) {
|
||||||
|
return col / 12.92
|
||||||
|
}
|
||||||
|
return Math.pow((col + 0.055) / 1.055, 2.4)
|
||||||
|
})
|
||||||
|
const L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2])
|
||||||
|
return (L > 0.55) ? '#000000' : '#FFFFFF'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
if (this.compVal && typeof this.compVal === 'string'){
|
||||||
|
this.compVal = parseInt(this.compVal)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
btnStyle (isSelected) {
|
||||||
|
if (!isSelected) return {}
|
||||||
|
return {
|
||||||
|
color: this.textColor,
|
||||||
|
backgroundColor: this.color
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setScale (val) {
|
||||||
|
if (this.disabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.compVal === val) {
|
||||||
|
this.compVal = null
|
||||||
|
} else {
|
||||||
|
this.compVal = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -15,6 +15,7 @@ import ImageInput from './ImageInput.vue'
|
||||||
import RatingInput from './RatingInput.vue'
|
import RatingInput from './RatingInput.vue'
|
||||||
import FlatSelectInput from './FlatSelectInput.vue'
|
import FlatSelectInput from './FlatSelectInput.vue'
|
||||||
import ToggleSwitchInput from './ToggleSwitchInput.vue'
|
import ToggleSwitchInput from './ToggleSwitchInput.vue'
|
||||||
|
import ScaleInput from './ScaleInput.vue'
|
||||||
|
|
||||||
// Components that are registered globaly.
|
// Components that are registered globaly.
|
||||||
[
|
[
|
||||||
|
@ -32,7 +33,8 @@ import ToggleSwitchInput from './ToggleSwitchInput.vue'
|
||||||
ImageInput,
|
ImageInput,
|
||||||
RatingInput,
|
RatingInput,
|
||||||
FlatSelectInput,
|
FlatSelectInput,
|
||||||
ToggleSwitchInput
|
ToggleSwitchInput,
|
||||||
|
ScaleInput
|
||||||
].forEach(Component => {
|
].forEach(Component => {
|
||||||
Vue.component(Component.name, Component)
|
Vue.component(Component.name, Component)
|
||||||
})
|
})
|
||||||
|
|
|
@ -150,6 +150,9 @@ export default {
|
||||||
if (field.type === 'number' && field.is_rating && field.rating_max_value) {
|
if (field.type === 'number' && field.is_rating && field.rating_max_value) {
|
||||||
return 'RatingInput'
|
return 'RatingInput'
|
||||||
}
|
}
|
||||||
|
if (field.type === 'number' && field.is_scale && field.scale_max_value) {
|
||||||
|
return 'ScaleInput'
|
||||||
|
}
|
||||||
if (['select', 'multi_select'].includes(field.type) && field.without_dropdown) {
|
if (['select', 'multi_select'].includes(field.type) && field.without_dropdown) {
|
||||||
return 'FlatSelectInput'
|
return 'FlatSelectInput'
|
||||||
}
|
}
|
||||||
|
@ -302,6 +305,10 @@ export default {
|
||||||
inputProperties.accept = (this.form.is_pro && field.allowed_file_types) ? field.allowed_file_types : ""
|
inputProperties.accept = (this.form.is_pro && field.allowed_file_types) ? field.allowed_file_types : ""
|
||||||
} else if (field.type === 'number' && field.is_rating) {
|
} else if (field.type === 'number' && field.is_rating) {
|
||||||
inputProperties.numberOfStars = parseInt(field.rating_max_value)
|
inputProperties.numberOfStars = parseInt(field.rating_max_value)
|
||||||
|
} else if (field.type === 'number' && field.is_scale) {
|
||||||
|
inputProperties.minScale = parseInt(field.scale_min_value) ?? 1
|
||||||
|
inputProperties.maxScale = parseInt(field.scale_max_value) ?? 5
|
||||||
|
inputProperties.stepScale = parseInt(field.scale_step_value) ?? 1
|
||||||
} 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) {
|
||||||
|
|
|
@ -73,13 +73,36 @@
|
||||||
Rating
|
Rating
|
||||||
</v-checkbox>
|
</v-checkbox>
|
||||||
<p class="text-gray-400 mb-3 text-xs">
|
<p class="text-gray-400 mb-3 text-xs">
|
||||||
If enabled then this field will be star rating input.
|
Transform this field into a star rating input.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<text-input v-if="field.is_rating" name="rating_max_value" native-type="number" :min="1" class="mt-3"
|
<text-input v-if="field.is_rating" name="rating_max_value" native-type="number" :min="1" class="mt-3"
|
||||||
:form="field" required
|
:form="field" required
|
||||||
label="Max rating value"
|
label="Max rating value"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<v-checkbox v-model="field.is_scale" class="mt-4"
|
||||||
|
:name="field.id+'_is_scale'" @input="initScale"
|
||||||
|
>
|
||||||
|
Scale
|
||||||
|
</v-checkbox>
|
||||||
|
<p class="text-gray-400 text-xs mb-5">
|
||||||
|
Transform this field into a scale/score input.
|
||||||
|
</p>
|
||||||
|
<template v-if="field.is_scale">
|
||||||
|
<text-input name="scale_min_value" native-type="number" class="mt-4"
|
||||||
|
:form="field" required
|
||||||
|
label="Min scale value"
|
||||||
|
/>
|
||||||
|
<text-input name="scale_max_value" native-type="number" :min="1" class="mt-4"
|
||||||
|
:form="field" required
|
||||||
|
label="Max scale value"
|
||||||
|
/>
|
||||||
|
<text-input name="scale_step_value" native-type="number" :min="1" class="mt-4"
|
||||||
|
:form="field" required
|
||||||
|
label="Scale step svalue"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Text Options -->
|
<!-- Text Options -->
|
||||||
|
@ -510,9 +533,26 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initRating() {
|
initRating() {
|
||||||
if (this.field.is_rating && !this.field.rating_max_value) {
|
if (this.field.is_rating) {
|
||||||
|
this.$set(this.field, 'is_scale', false)
|
||||||
|
if (!this.field.rating_max_value) {
|
||||||
this.$set(this.field, 'rating_max_value', 5)
|
this.$set(this.field, 'rating_max_value', 5)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initScale () {
|
||||||
|
if (this.field.is_scale) {
|
||||||
|
this.$set(this.field, 'is_rating', false)
|
||||||
|
if (!this.field.scale_min_value) {
|
||||||
|
this.$set(this.field, 'scale_min_value', 1)
|
||||||
|
}
|
||||||
|
if (!this.field.scale_max_value) {
|
||||||
|
this.$set(this.field, 'scale_max_value', 5)
|
||||||
|
}
|
||||||
|
if (!this.field.scale_step_value) {
|
||||||
|
this.$set(this.field, 'scale_step_value', 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onFieldOptionsChange(val) {
|
onFieldOptionsChange(val) {
|
||||||
const vals = (val) ? val.trim().split("\n") : []
|
const vals = (val) ? val.trim().split("\n") : []
|
||||||
|
|
|
@ -25,6 +25,12 @@ export const themes = {
|
||||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||||
input: 'relative w-full rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-4 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
input: 'relative w-full rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-4 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent',
|
||||||
help: 'text-gray-400 dark:text-gray-500'
|
help: 'text-gray-400 dark:text-gray-500'
|
||||||
|
},
|
||||||
|
ScaleInput: {
|
||||||
|
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||||
|
button: 'cursor-pointer text-gray-700 inline-block rounded-lg border-gray-300 px-4 py-2 flex-grow dark:bg-notion-dark-light dark:text-gray-300 text-center',
|
||||||
|
unselectedButton: 'bg-white hover:bg-gray-50 border',
|
||||||
|
help: 'text-gray-400 dark:text-gray-500'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
simple: {
|
simple: {
|
||||||
|
@ -50,6 +56,12 @@ export const themes = {
|
||||||
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||||
input: 'border-transparent flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2',
|
input: 'border-transparent flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2',
|
||||||
help: 'text-gray-400 dark:text-gray-500'
|
help: 'text-gray-400 dark:text-gray-500'
|
||||||
|
},
|
||||||
|
ScaleInput: {
|
||||||
|
label: 'text-gray-700 dark:text-gray-300 font-semibold',
|
||||||
|
button: 'flex-1 appearance-none border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-gray-50 text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 text-center',
|
||||||
|
unselectedButton: 'bg-white hover:bg-gray-50 border -mx-4',
|
||||||
|
help: 'text-gray-400 dark:text-gray-500'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
notion: {
|
notion: {
|
||||||
|
@ -75,6 +87,12 @@ export const themes = {
|
||||||
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||||
input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion border border-gray-300 dark:border-gray-600 w-full text-gray-900 bg-notion-input-background dark:bg-notion-dark-light shadow-inner dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:ring-opacity-100 focus:border-transparent focus:ring-0 focus:shadow-focus-notion',
|
input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion border border-gray-300 dark:border-gray-600 w-full text-gray-900 bg-notion-input-background dark:bg-notion-dark-light shadow-inner dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:ring-opacity-100 focus:border-transparent focus:ring-0 focus:shadow-focus-notion',
|
||||||
help: 'text-notion-input-help dark:text-gray-500'
|
help: 'text-notion-input-help dark:text-gray-500'
|
||||||
|
},
|
||||||
|
ScaleInput: {
|
||||||
|
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
|
||||||
|
button: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 text-center',
|
||||||
|
unselectedButton: 'bg-notion-input-background dark:bg-notion-dark-light hover:bg-gray-50 border',
|
||||||
|
help: 'text-notion-input-help dark:text-gray-500'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue