Started to refactor input components

This commit is contained in:
Julien Nahum 2023-10-28 11:17:50 +01:00
parent 7eb546703f
commit 8e8bad3754
17 changed files with 271 additions and 209 deletions

2
resources/js/app.js vendored
View File

@ -22,7 +22,7 @@ registerComponents(app)
configureCompat({
// default everything to Vue 2 behavior
MODE: 2,
GLOBAL_MOUNT: false,
// GLOBAL_MOUNT: false,
COMPONENT_V_MODEL: false,
INSTANCE_SET: false,
INSTANCE_DELETE: false

View File

@ -1,34 +1,56 @@
<template>
<div :class="wrapperClass">
<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>
<v-checkbox :id="id?id:name" v-model="compVal" :disabled="disabled" :name="name" @input="$emit('input',$event)">
<input-wrapper v-bind="$props">
<template #label>
<span />
</template>
<v-checkbox :id="id?id:name" v-model="compVal" :disabled="disabled" :name="name">
<slot name="label">
{{ label }} <span v-if="required" class="text-red-500 required-dot">*</span>
</slot>
</v-checkbox>
<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 #help>
<slot name="help" />
</template>
<template #error>
<slot name="error" />
</template>
</input-wrapper>
</template>
<script>
import inputMixin from '~/mixins/forms/input.js'
import { inputProps, useFormInput } from './useFormInput.js'
import VCheckbox from './components/VCheckbox.vue'
import InputWrapper from './components/InputWrapper.vue'
export default {
name: 'CheckboxInput',
components: { VCheckbox },
mixins: [inputMixin],
props: {},
components: { InputWrapper, VCheckbox },
props: {
...inputProps
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
}
},
mounted () {
this.compVal = !!this.compVal
this.$emit('input', !!this.compVal)
}
}
</script>

View File

@ -1,13 +1,18 @@
<template>
<div :class="wrapperClass">
<label v-if="label" :for="id?id:name"
:class="[theme.CodeInput.label,{'uppercase text-xs':uppercaseLabels, 'text-sm':!uppercaseLabels}]"
<input-wrapper
v-bind="$props"
>
{{ label }}
<span v-if="required" class="text-red-500 required-dot">*</span>
</label>
<template #label>
<slot name="label" />
</template>
<div :class="[theme.CodeInput.input,{ '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }]">
<template #help>
<slot name="help" />
</template>
<div
:class="[theme.CodeInput.input,{ '!ring-red-500 !ring-2': hasError, '!cursor-not-allowed !bg-gray-200':disabled }]"
>
<codemirror :id="id?id:name" v-model="compVal" :disabled="disabled"
:options="cmOptions"
:style="inputStyle" :name="name"
@ -15,11 +20,10 @@
/>
</div>
<small v-if="help" :class="theme.CodeInput.help">
<slot name="help"><span class="field-help" v-html="help" /></slot>
</small>
<has-error v-if="hasValidation" :form="form" :field="name" />
</div>
<template #error>
<slot name="error" />
</template>
</input-wrapper>
</template>
<script>
@ -28,13 +32,32 @@ import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/htmlmixed/htmlmixed.js'
import inputMixin from '~/mixins/forms/input.js'
import { inputProps, useFormInput } from './useFormInput.js'
import InputWrapper from './components/InputWrapper.vue'
export default {
name: 'CodeInput',
components: { codemirror },
mixins: [inputMixin],
components: { InputWrapper, codemirror },
props: {
...inputProps
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
}
},
data () {
return {
@ -47,8 +70,6 @@ export default {
line: true
}
}
},
methods: {}
}
}
</script>

View File

@ -1,25 +1,50 @@
<template>
<div :class="wrapperClass">
<input-wrapper v-bind="$props">
<template #label>
<span />
</template>
<div class="flex items-center">
<input :id="id?id:name" v-model="compVal" :disabled="disabled"
type="color"
type="color" class="mr-2"
:name="name"
>
<label v-if="label" :for="id?id:name" class="text-gray-700 dark:text-gray-300">
{{ label }}
<span v-if="required" class="text-red-500 required-dot">*</span>
</label>
<small v-if="help" :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" />
<slot name="label">
<span>{{ label }} <span v-if="required" class="text-red-500 required-dot">*</span></span>
</slot>
</div>
<template #help>
<slot name="help" />
</template>
<template #error>
<slot name="error" />
</template>
</input-wrapper>
</template>
<script>
import inputMixin from '~/mixins/forms/input.js'
import InputWrapper from './components/InputWrapper.vue'
import { inputProps, useFormInput } from './useFormInput.js'
export default {
name: 'ColorInput',
mixins: [inputMixin]
components: { InputWrapper },
props: {
...inputProps
},
setup (props, context) {
const { compVal, inputStyle, hasValidation, hasError } = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
}
}
}
</script>

View File

@ -11,7 +11,8 @@
</small>
<div class="flex" v-if="!dateRange">
<input :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="fromDate" :class="inputClasses" :disabled="disabled"
<input :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="fromDate" :class="inputClasses"
:disabled="disabled"
:style="inputStyle" :name="name" data-date-format="YYYY-MM-DD"
:min="setMinDate" :max="setMaxDate"
/>
@ -20,11 +21,13 @@
<div class="flex -mx-2">
<p class="text-gray-900 px-4">From</p>
<input :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="fromDate" :disabled="disabled"
:style="inputStyle" :name="name" data-date-format="YYYY-MM-DD" class="flex-grow border-transparent focus:outline-none "
:style="inputStyle" :name="name" data-date-format="YYYY-MM-DD"
class="flex-grow border-transparent focus:outline-none "
:min="setMinDate" :max="setMaxDate"
/>
<p class="text-gray-900 px-4">To</p>
<input v-if="dateRange" :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="toDate" :disabled="disabled"
<input v-if="dateRange" :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="toDate"
:disabled="disabled"
:style="inputStyle" :name="name" class="flex-grow border-transparent focus:outline-none"
:min="setMinDate" :max="setMaxDate"
/>
@ -53,7 +56,7 @@ export default {
},
data: () => ({
fixedClasses: fixedClasses,
fixedClasses: null,
fromDate: null,
toDate: null
}),
@ -127,7 +130,7 @@ export default {
}
}
fixedClasses.input = this.theme.default.input
this.fixedClasses.input = this.theme.default.input
this.setInputColor()
},

View File

@ -5,9 +5,6 @@
<template #label>
<slot name="label" />
</template>
<template v-if="helpPosition==='above_input'" #help>
<slot name="help" />
</template>
<input :id="id?id:name" v-model="compVal" :disabled="disabled"
:type="nativeType"
@ -19,17 +16,11 @@
@change="onChange" @keydown.enter.prevent="onEnterPress"
>
<!-- TODO: fix this in the case of below input there's something off -->
<!-- <input-help v-if="helpPosition==='below_input' || showCharLimit" :help="help" :theme="theme">-->
<!-- <template #help>-->
<!-- <slot name="help" />-->
<!-- </template>-->
<!-- <template v-if="showCharLimit" #after-help>-->
<!-- <small v-if="showCharLimit && maxCharLimit" :class="theme.default.help">-->
<!-- {{ charCount }}/{{ maxCharLimit }}-->
<!-- </small>-->
<!-- </template>-->
<!-- </input-help>-->
<template v-if="maxCharLimit && showCharLimit" #bottom_after_help>
<small :class="theme.default.help">
{{ charCount }}/{{ maxCharLimit }}
</small>
</template>
<template #error>
<slot name="error" />

View File

@ -1,33 +1,34 @@
<template>
<div :class="wrapperClass">
<input-help v-if="help && helpPosition=='above_input'" :help="help" :theme="theme">
<template #help>
<slot name="help" />
<input-wrapper v-bind="$props">
<template #label>
<span />
</template>
</input-help>
<div class="flex">
<v-switch :id="id?id:name" v-model="compVal" class="inline-block mr-2" :disabled="disabled" />
<slot name="label">
<span>{{ label }} <span v-if="required" class="text-red-500 required-dot">*</span></span>
</slot>
</div>
<input-help v-if="help && helpPosition=='below_input'" :help="help" :theme="theme">
<template #help>
<slot name="help" />
</template>
</input-help>
<has-error v-if="hasValidation" :form="form" :field="name" />
</div>
<template #error>
<slot name="error" />
</template>
</input-wrapper>
</template>
<script>
import { inputProps, useFormInput } from './useFormInput.js'
import InputHelp from './components/InputHelp.vue'
import VSwitch from './components/VSwitch.vue'
import InputWrapper from './components/InputWrapper.vue'
export default {
name: 'ToggleSwitchInput',
components: { InputHelp, VSwitch },
components: { InputWrapper, VSwitch },
props: {
...inputProps
},

View File

@ -1,10 +1,10 @@
<template>
<div :class="wrapperClass" :style="inputStyle">
<slot name="label">
<input-label v-if="label"
<input-label v-if="label && !hideFieldName"
:label="label"
:theme="theme"
:required="true"
:required="required"
:native-for="id?id:name"
:uppercase-labels="uppercaseLabels"
/>
@ -13,8 +13,13 @@
<input-help :help="help" :theme="theme" />
</slot>
<slot />
<slot v-if="help && helpPosition==='below_input'" name="help">
<input-help :help="help" :theme="theme" />
<slot v-if="(help && helpPosition==='below_input') || $slots.bottom_after_help" name="help">
<input-help :help="help" :theme="theme">
<template #after-help>
<slot name="bottom_after_help" />
</template>
</input-help>
</slot>
<slot name="error">
<has-error v-if="hasValidation" :form="form" :field="name" />
@ -34,14 +39,16 @@ export default {
id: { type: String, required: false },
name: { type: String, required: false },
theme: { type: Object, required: true },
form: { type: Object, required: false },
wrapperClass: { type: String, required: false },
inputStyle: { type: Object, required: false },
help: { type: String, required: false },
label: { type: String, required: false },
helpPosition: { type: String, default: 'below_input' },
uppercaseLabels: { type: Boolean, default: true },
hasValidation: { type: Boolean, default: true },
form: { type: Object, required: false }
hideFieldName: { type: Boolean, default: true },
required: { type: Boolean, default: false },
hasValidation: { type: Boolean, default: true }
}
}
</script>

View File

@ -16,64 +16,57 @@
</div>
</template>
<script>
export default {
name: 'VCheckbox',
<script setup>
import { ref, watch, onMounted, defineProps, defineEmits, defineOptions } from 'vue'
props: {
defineOptions({
name: 'VCheckbox'
})
const props = defineProps({
id: { type: String, default: null },
name: { type: String, default: 'checkbox' },
value: { type: [Boolean, String], default: false },
modelValue: { type: [Boolean, String], default: false },
checked: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
sizeClasses: { type: String, default: 'w-4 h-4' }
},
})
data: () => ({
internalValue: false
}),
const emit = defineEmits(['update:modelValue', 'click'])
watch: {
value (val) {
this.internalValue = val
},
const internalValue = ref(props.modelValue)
checked (val) {
this.internalValue = val
},
watch(() => props.modelValue, val => {
internalValue.value = val
})
internalValue (val, oldVal) {
// Support form data string checkbox (string 1 or 0)
watch(() => props.checked, val => {
internalValue.value = val
})
watch(() => internalValue.value, (val, oldVal) => {
if (val === 0 || val === '0') val = false
if (val === 1 || val === '1') val = true
if (val !== oldVal) {
this.$emit('input', val)
emit('update:modelValue', val)
}
})
if ('checked' in props) {
internalValue.value = props.checked
}
},
created () {
this.internalValue = this.value
onMounted(() => {
emit('update:modelValue', internalValue.value)
})
if ('checked' in this.$options.propsData) {
this.internalValue = this.checked
}
},
mounted () {
this.$emit('input', this.internalValue)
},
methods: {
handleClick (e) {
this.$emit('click', e)
const handleClick = (e) => {
emit('click', e)
if (!e.isPropagationStopped) {
this.internalValue = e.target.checked
this.$emit('input', this.internalValue)
}
}
internalValue.value = e.target.checked
emit('update:modelValue', internalValue.value)
}
}
</script>

View File

@ -17,7 +17,6 @@ const emit = defineEmits(['update:modelValue'])
const onClick = () => {
if (disabled) return
console.log('ok emiting', !modelValue)
emit('update:modelValue', !modelValue)
}
</script>

View File

@ -34,7 +34,7 @@ export function registerComponents (app) {
FlatSelectInput,
ToggleSwitchInput
].forEach(Component => {
app.component(Component.name, Component)
Component.name ? app.component(Component.name, Component) : app.component(Component.name, Component)
})
// Register async components

View File

@ -11,6 +11,7 @@ export const inputProps = {
disabled: { type: Boolean, default: false },
placeholder: { type: String, default: null },
uppercaseLabels: { type: Boolean, default: false },
hideFieldName: { type: Boolean, default: false },
help: { type: String, default: null },
helpPosition: { type: String, default: 'below_input' },
theme: { type: Object, default: () => themes.default },
@ -32,7 +33,7 @@ export function useFormInput (props, context, formPrefixKey = null) {
})
const hasError = computed(() => {
return hasValidation && props.form?.errors.has(name)
return hasValidation && props.form?.errors?.has(name)
})
const compVal = computed({

View File

@ -36,7 +36,7 @@
</p>
<v-checkbox v-model="field.hidden" class="mb-3"
:name="field.id+'_hidden'"
@input="onFieldHiddenChange"
@update:model-value="onFieldHiddenChange"
>
Hidden
</v-checkbox>

View File

@ -47,19 +47,19 @@
</p>
<v-checkbox v-model="field.hidden" class="mb-3"
:name="field.id+'_hidden'"
@input="onFieldHiddenChange"
@update:model-value="onFieldHiddenChange"
>
Hidden
</v-checkbox>
<v-checkbox v-model="field.required" class="mb-3"
:name="field.id+'_required'"
@input="onFieldRequiredChange"
@update:model-value="onFieldRequiredChange"
>
Required
</v-checkbox>
<v-checkbox v-model="field.disabled" class="mb-3"
:name="field.id+'_disabled'"
@input="onFieldDisabledChange"
@update:model-value="onFieldDisabledChange"
>
Disabled
</v-checkbox>
@ -105,7 +105,7 @@
Number Options
</h3>
<v-checkbox v-model="field.is_rating" class="mt-4"
:name="field.id+'_is_rating'" @input="initRating"
:name="field.id+'_is_rating'" @update:model-value="initRating"
>
Rating
</v-checkbox>
@ -142,7 +142,7 @@
</h3>
<v-checkbox v-model="field.date_range" class="mt-4"
:name="field.id+'_date_range'"
@input="onFieldDateRangeChange"
@update:model-value="onFieldDateRangeChange"
>
Date Range
</v-checkbox>
@ -151,7 +151,7 @@
</p>
<v-checkbox v-model="field.with_time"
:name="field.id+'_with_time'"
@input="onFieldWithTimeChange"
@update:model-value="onFieldWithTimeChange"
>
Date with time
</v-checkbox>
@ -166,7 +166,7 @@
/>
<v-checkbox v-model="field.prefill_today"
name="prefill_today"
@input="onFieldPrefillTodayChange"
@update:model-value="onFieldPrefillTodayChange"
>
Prefill with 'today'
</v-checkbox>
@ -176,14 +176,14 @@
<v-checkbox v-model="field.disable_past_dates"
name="disable_past_dates" class="mb-3"
@input="onFieldDisablePastDatesChange"
@update:model-value="onFieldDisablePastDatesChange"
>
Disable past dates
</v-checkbox>
<v-checkbox v-model="field.disable_future_dates"
name="disable_future_dates" class="mb-3"
@input="onFieldDisableFutureDatesChange"
@update:model-value="onFieldDisableFutureDatesChange"
>
Disable future dates
</v-checkbox>
@ -203,12 +203,12 @@
@input="onFieldOptionsChange"
/>
<v-checkbox v-model="field.allow_creation"
name="allow_creation" help="" @input="onFieldAllowCreationChange"
name="allow_creation" help="" @update:model-value="onFieldAllowCreationChange"
>
Allow respondent to create new options
</v-checkbox>
<v-checkbox v-model="field.without_dropdown" class="mt-4"
name="without_dropdown" help="" @input="onFieldWithoutDropdownChange"
name="without_dropdown" help="" @update:model-value="onFieldWithoutDropdownChange"
>
Always show all select options
</v-checkbox>
@ -241,7 +241,7 @@
<!-- Pre-fill depends on type -->
<v-checkbox v-if="field.type=='checkbox'" v-model="field.prefill" class="mt-4"
:name="field.id+'_prefill'"
@input="field.prefill = $event"
@update:model-value="field.prefill = $event"
>
Pre-filled value
</v-checkbox>
@ -327,7 +327,7 @@
<v-checkbox v-model="field.generates_uuid"
:name="field.id+'_generates_uuid'"
@input="onFieldGenUIdChange"
@update:model-value="onFieldGenUIdChange"
>
Generates a unique id on submission
</v-checkbox>
@ -337,7 +337,7 @@
<v-checkbox v-model="field.generates_auto_increment_id"
:name="field.id+'_generates_auto_increment_id'"
@input="onFieldGenAutoIdChange"
@update:model-value="onFieldGenAutoIdChange"
>
Generates an auto-incremented id on submission
</v-checkbox>

View File

@ -10,7 +10,7 @@
</p>
<v-checkbox v-model="field.hidden" class="mb-3"
:name="field.id+'_hidden'"
@input="onFieldHiddenChange"
@update:model-value="onFieldHiddenChange"
>
Hidden
</v-checkbox>

View File

@ -10,19 +10,19 @@
</p>
<v-checkbox v-model="field.hidden" class="mb-3"
:name="field.id+'_hidden'"
@input="onFieldHiddenChange"
@update:model-value="onFieldHiddenChange"
>
Hidden
</v-checkbox>
<v-checkbox v-model="field.required" class="mb-3"
:name="field.id+'_required'"
@input="onFieldRequiredChange"
@update:model-value="onFieldRequiredChange"
>
Required
</v-checkbox>
<v-checkbox v-model="field.disabled" class="mb-3"
:name="field.id+'_disabled'"
@input="onFieldDisabledChange"
@update:model-value="onFieldDisabledChange"
>
Disabled
</v-checkbox>
@ -68,7 +68,7 @@
Number Options
</h3>
<v-checkbox v-model="field.is_rating" class="mt-3"
:name="field.id+'_is_rating'" @input="initRating"
:name="field.id+'_is_rating'" @update:model-value="initRating"
>
Rating
</v-checkbox>
@ -92,7 +92,7 @@
</p>
<v-checkbox v-model="field.multi_lines" class="mb-2"
:name="field.id+'_multi_lines'"
@input="field.multi_lines = $event"
@update:model-value="field.multi_lines = $event"
>
Multi-lines input
</v-checkbox>
@ -105,7 +105,7 @@
</h3>
<v-checkbox v-model="field.date_range" class="mt-3"
:name="field.id+'_date_range'"
@input="onFieldDateRangeChange"
@update:model-value="onFieldDateRangeChange"
>
Date Range
</v-checkbox>
@ -114,7 +114,7 @@
</p>
<v-checkbox v-model="field.with_time"
:name="field.id+'_with_time'"
@input="onFieldWithTimeChange"
@update:model-value="onFieldWithTimeChange"
>
Date with time
</v-checkbox>
@ -129,7 +129,7 @@
/>
<v-checkbox v-model="field.prefill_today"
name="prefill_today"
@input="onFieldPrefillTodayChange"
@update:model-value="onFieldPrefillTodayChange"
>
Prefill with 'today'
</v-checkbox>
@ -139,14 +139,14 @@
<v-checkbox v-model="field.disable_past_dates"
name="disable_past_dates" class="mb-3"
@input="onFieldDisablePastDatesChange"
@update:model-value="onFieldDisablePastDatesChange"
>
Disable past dates
</v-checkbox>
<v-checkbox v-model="field.disable_future_dates"
name="disable_future_dates" class="mb-3"
@input="onFieldDisableFutureDatesChange"
@update:model-value="onFieldDisableFutureDatesChange"
>
Disable future dates
</v-checkbox>
@ -166,12 +166,12 @@
@input="onFieldOptionsChange"
/>
<v-checkbox v-model="field.allow_creation"
name="allow_creation" help="" @input="onFieldAllowCreationChange"
name="allow_creation" help="" @update:model-value="onFieldAllowCreationChange"
>
Allow respondent to create new options
</v-checkbox>
<v-checkbox v-model="field.without_dropdown" class="mt-3"
name="without_dropdown" help="" @input="onFieldWithoutDropdownChange"
name="without_dropdown" help="" @update:model-value="onFieldWithoutDropdownChange"
>
Always show all select options
</v-checkbox>
@ -243,7 +243,7 @@
<!-- Pre-fill depends on type -->
<v-checkbox v-if="field.type=='checkbox'" v-model="field.prefill" class="mt-3"
:name="field.id+'_prefill'"
@input="field.prefill =$event"
@update:model-value="field.prefill =$event"
>
Pre-filled value
</v-checkbox>
@ -270,7 +270,6 @@
<text-input v-else-if="field.type!=='files'" name="prefill" class="mt-3"
:form="field"
label="Pre-filled value"
:disabled="field.type==='date' && field.prefill_today===true"
/>
<div v-if="['select','multi_select'].includes(field.type)" class="-mt-3 mb-3 text-gray-400 dark:text-gray-500">
<small>
@ -314,7 +313,7 @@
/>
<template v-if="['text','number','url','email'].includes(field.type)">
<text-input v-model="field.max_char_limit" name="max_char_limit" native-type="number" :min="1" :max="2000"
<text-input name="max_char_limit" native-type="number" :min="1" :max="2000"
:form="field"
label="Max character limit"
help="Maximum character limit of 2000"
@ -334,7 +333,7 @@
<v-checkbox v-model="field.generates_uuid"
:name="field.id+'_generates_uuid'"
@input="onFieldGenUIdChange"
@update:model-value="onFieldGenUIdChange"
>
Generates a unique id
</v-checkbox>
@ -345,7 +344,7 @@
<v-checkbox v-model="field.generates_auto_increment_id"
:name="field.id+'_generates_auto_increment_id'"
@input="onFieldGenAutoIdChange"
@update:model-value="onFieldGenAutoIdChange"
>
Generates an auto-incremented id
</v-checkbox>