Refactor inpus

This commit is contained in:
Julien Nahum
2023-12-01 21:24:38 +01:00
parent 47653ebe64
commit 0eea59c5ba
33 changed files with 285 additions and 383 deletions

View File

@@ -2,7 +2,7 @@
<a v-if="href" :class="btnClasses" :href="href" :target="target">
<slot />
</a>
<button v-else-if="!to" :type="nativeType" :disabled="loading" :class="btnClasses"
<button v-else-if="!to" :type="nativeType" :disabled="loading?true:null" :class="btnClasses"
@click="onClick($event)"
>
<template v-if="!loading">

View File

@@ -1,10 +1,10 @@
<template>
<input-wrapper v-bind="$props">
<input-wrapper v-bind="inputWrapperProps">
<template #label>
<span />
</template>
<v-checkbox :id="id?id:name" v-model="compVal" :disabled="disabled" :name="name">
<v-checkbox :id="id?id:name" v-model="compVal" :disabled="disabled?true:null" :name="name">
<slot name="label">
{{ label }} <span v-if="required" class="text-red-500 required-dot">*</span>
</slot>
@@ -34,18 +34,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@@ -13,7 +13,7 @@
<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"
<codemirror :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
:options="cmOptions"
:style="inputStyle" :name="name"
:placeholder="placeholder"
@@ -44,18 +44,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@@ -1,11 +1,11 @@
<template>
<input-wrapper v-bind="$props">
<input-wrapper v-bind="inputWrapperProps">
<template #label>
<span />
</template>
<div class="flex items-center">
<input :id="id?id:name" v-model="compVal" :disabled="disabled"
<input :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
type="color" class="mr-2"
:name="name"
>
@@ -37,13 +37,8 @@ export default {
},
setup (props, context) {
const { compVal, inputStyle, hasValidation, hasError } = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
}
}

View File

@@ -1,32 +1,36 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<div class="flex" v-if="!dateRange">
<input :type="useTime ? 'datetime-local' : 'date'" :id="id?id:name" v-model="fromDate" :class="inputClasses"
:disabled="disabled"
<div v-if="!dateRange" class="flex">
<input :id="id?id:name" v-model="fromDate" :type="useTime ? 'datetime-local' : 'date'" :class="inputClasses"
:disabled="disabled?true:null"
:style="inputStyle" :name="name" data-date-format="YYYY-MM-DD"
:min="setMinDate" :max="setMaxDate"
/>
>
</div>
<div :class="inputClasses" v-else>
<div v-else :class="inputClasses">
<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"
<p class="text-gray-900 px-4">
From
</p>
<input :id="id?id:name" v-model="fromDate" :type="useTime ? 'datetime-local' : 'date'" :disabled="disabled?true:null"
: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"
>
<p class="text-gray-900 px-4">
To
</p>
<input v-if="dateRange" :id="id?id:name" v-model="toDate" :type="useTime ? 'datetime-local' : 'date'"
:disabled="disabled?true:null"
:style="inputStyle" :name="name" class="flex-grow border-transparent focus:outline-none"
:min="setMinDate" :max="setMaxDate"
/>
>
</div>
</div>
@@ -42,7 +46,7 @@
<script>
import { inputProps, useFormInput } from './useFormInput.js'
import InputWrapper from './components/InputWrapper.vue'
import {fixedClasses} from '../../plugins/config/vue-tailwind/datePicker.js'
import { fixedClasses } from '../../plugins/config/vue-tailwind/datePicker.js'
export default {
name: 'DateInput',
@@ -51,25 +55,15 @@ export default {
props: {
...inputProps,
withTime: {type: Boolean, default: false},
dateRange: {type: Boolean, default: false},
disablePastDates: {type: Boolean, default: false},
disableFutureDates: {type: Boolean, default: false}
withTime: { type: Boolean, default: false },
dateRange: { type: Boolean, default: false },
disablePastDates: { type: Boolean, default: false },
disableFutureDates: { type: Boolean, default: false }
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
@@ -80,22 +74,22 @@ export default {
}),
computed: {
inputClasses() {
inputClasses () {
let str = 'border border-gray-300 dark:bg-notion-dark-light dark:border-gray-600 dark:placeholder-gray-500 dark:text-gray-300 flex-1 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-opacity-100 placeholder-gray-400 px-4 py-2 rounded-lg shadow-sm text-base text-black text-gray-700'
str += this.dateRange ? ' w-50' : ' w-full'
str += this.disabled ? ' !cursor-not-allowed !bg-gray-200' : ''
return str
},
useTime() {
useTime () {
return this.withTime && !this.dateRange
},
setMinDate() {
setMinDate () {
if (this.disablePastDates) {
return new Date().toISOString().split('T')[0]
}
return false
},
setMaxDate() {
setMaxDate () {
if (this.disableFutureDates) {
return new Date().toISOString().split('T')[0]
}
@@ -105,16 +99,16 @@ export default {
watch: {
color: {
handler() {
handler () {
this.setInputColor()
},
immediate: true
},
fromDate: {
handler(val) {
handler (val) {
if (this.dateRange) {
if (!Array.isArray(this.compVal)) {
this.compVal = [];
this.compVal = []
}
this.compVal[0] = this.dateToUTC(val)
} else {
@@ -124,10 +118,10 @@ export default {
immediate: false
},
toDate: {
handler(val) {
handler (val) {
if (this.dateRange) {
if (!Array.isArray(this.compVal)) {
this.compVal = [null];
this.compVal = [null]
}
this.compVal[1] = this.dateToUTC(val)
} else {
@@ -138,7 +132,7 @@ export default {
}
},
mounted() {
mounted () {
if (this.compVal) {
if (Array.isArray(this.compVal)) {
this.fromDate = this.compVal[0] ?? null
@@ -158,17 +152,17 @@ export default {
* @param event
* @returns {boolean}
*/
onEnterPress(event) {
onEnterPress (event) {
event.preventDefault()
return false
},
setInputColor() {
setInputColor () {
if (this.$refs.datepicker) {
const dateInput = this.$refs.datepicker.$el.getElementsByTagName('input')[0]
dateInput.style.setProperty('--tw-ring-color', this.color)
}
},
dateToUTC(val) {
dateToUTC (val) {
if (!val) {
return null
}
@@ -177,7 +171,7 @@ export default {
}
return new Date(val).toISOString()
},
dateToLocal(val) {
dateToLocal (val) {
if (!val) {
return null
}
@@ -187,7 +181,7 @@ export default {
String(dateObj.getDate()).padStart(2, '0')
if (this.useTime) {
dateStr += 'T' + String(dateObj.getHours()).padStart(2, '0') + ':' +
String(dateObj.getMinutes()).padStart(2, '0');
String(dateObj.getMinutes()).padStart(2, '0')
}
return dateStr
}

View File

@@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@@ -94,18 +94,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@@ -51,18 +51,8 @@ export default {
multiple: { type: Boolean, default: false }
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
data () {
@@ -71,10 +61,10 @@ export default {
computed: {},
methods: {
onSelect (value) {
if(this.disabled){
if (this.disabled) {
return
}
if (this.multiple) {
const emitValue = Array.isArray(this.compVal) ? [...this.compVal] : []

View File

@@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@@ -121,18 +121,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
@@ -155,8 +145,8 @@ export default {
showUploadModal: {
handler (val) {
document.removeEventListener('paste', this.onUploadPasteEvent)
if(this.showUploadModal){
document.addEventListener("paste", this.onUploadPasteEvent)
if (this.showUploadModal) {
document.addEventListener('paste', this.onUploadPasteEvent)
}
}
}
@@ -176,7 +166,7 @@ export default {
this.droppedFiles(e.dataTransfer.files)
},
onUploadPasteEvent (e) {
if(!this.showUploadModal) return
if (!this.showUploadModal) return
this.uploadDragoverEvent = false
this.uploadDragoverTracking = false
this.droppedFiles(e.clipboardData.files)

View File

@@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@@ -9,7 +9,7 @@
<div :id="id ? id : name" :name="name" :style="inputStyle" class="flex items-center">
<v-select v-model="selectedCountryCode" class="w-[130px]" dropdown-class="w-[300px]" input-class="rounded-r-none"
:data="countries"
:disabled="disabled || countries.length===1" :searchable="true" :search-keys="['name']" :option-key="'code'" :color="color"
:disabled="(disabled || countries.length===1)?true:null" :searchable="true" :search-keys="['name']" :option-key="'code'" :color="color"
:has-error="hasValidation && form.errors.has(name)"
:placeholder="'Select a country'" :uppercase-labels="true" :theme="theme" @update:model-value="onChangeCountryCode"
>
@@ -27,9 +27,9 @@
</div>
</template>
</v-select>
<input v-model="inputVal" type="text" class="inline-flex-grow !border-l-0 !rounded-l-none" :disabled="disabled"
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200': disabled }]"
:placeholder="placeholder" :style="inputStyle" @update:model-value="onInput"
<input v-model="inputVal" type="text" class="inline-flex-grow !border-l-0 !rounded-l-none" :disabled="disabled?true:null"
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200': disabled }]"
:placeholder="placeholder" :style="inputStyle" @update:model-value="onInput"
>
</div>
@@ -40,7 +40,6 @@
<template #error>
<slot name="error" />
</template>
</input-wrapper>
</template>
@@ -61,18 +60,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@@ -1,12 +1,11 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<div class="stars-outer">
<div v-for="i in numberOfStars" :key="i"
class="cursor-pointer inline-block text-gray-200 dark:text-gray-800"
@@ -46,22 +45,11 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
data () {
return {
hoverRating: -1

View File

@@ -1,12 +1,12 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<vue-editor :id="id?id:name" ref="editor" v-model="compVal" :disabled="disabled"
<vue-editor :id="id?id:name" ref="editor" v-model="compVal" :disabled="disabled?true:null"
:placeholder="placeholder" :class="[{ '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }, theme.RichTextAreaInput.input]"
:editor-toolbar="editorToolbar" class="rich-editor resize-y"
:style="inputStyle"
@@ -41,25 +41,15 @@ export default {
[{ header: 1 }, { header: 2 }],
['bold', 'italic', 'underline', 'link'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{color: []}]
[{ color: [] }]
]
}
}
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
}

View File

@@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@@ -41,18 +41,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
@@ -62,7 +52,7 @@ export default {
computed: {
scaleList () {
let list = []
const list = []
for (let i = this.minScale; i <= this.maxScale; i += this.stepScale) {
list.push(i)
}
@@ -89,7 +79,7 @@ export default {
},
mounted () {
if (this.compVal && typeof this.compVal === 'string'){
if (this.compVal && typeof this.compVal === 'string') {
this.compVal = parseInt(this.compVal)
}
},

View File

@@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@@ -21,7 +21,7 @@
:theme="theme"
:has-error="hasValidation && form.errors.has(name)"
:allow-creation="allowCreation"
:disabled="disabled"
:disabled="disabled?true:null"
:help="help"
:help-position="helpPosition"
@@ -97,20 +97,11 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
data () {
return {
additionalOptions: []

View File

@@ -1,6 +1,6 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
@@ -18,7 +18,7 @@
<a :class="theme.default.help" href="#" @click.prevent="clear">Clear</a>
</small>
</template>
<template #error>
<slot name="error" />
</template>
@@ -39,18 +39,8 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@@ -1,12 +1,12 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<textarea :id="id?id:name" v-model="compVal" :disabled="disabled"
<textarea :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
:class="[theme.default.input,{ '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200':disabled }]"
class="resize-y"
:name="name" :style="inputStyle"
@@ -16,7 +16,7 @@
<template v-if="maxCharLimit && showCharLimit" #bottom_after_help>
<small :class="theme.default.help">
{{ charCount }}/{{ maxCharLimit }}
{{ charCount }}/{{ maxCharLimit }}
</small>
</template>
@@ -38,25 +38,17 @@ export default {
props: {
...inputProps,
maxCharLimit: { type: Number, required: false, default: null },
showCharLimit: { type: Boolean, required: false, default: false },
showCharLimit: { type: Boolean, required: false, default: false }
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context)
setup (props, context) {
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},
computed: {
charCount() {
charCount () {
return (this.compVal) ? this.compVal.length : 0
}
}

View File

@@ -1,12 +1,12 @@
<template>
<input-wrapper
v-bind="$props"
v-bind="inputWrapperProps"
>
<template #label>
<slot name="label" />
</template>
<input :id="id?id:name" v-model="compVal" :disabled="disabled"
<input :id="id?id:name" v-model="compVal" :disabled="disabled?true:null"
:type="nativeType"
:pattern="pattern"
:style="inputStyle"
@@ -18,7 +18,7 @@
<template v-if="maxCharLimit && showCharLimit" #bottom_after_help>
<small :class="theme.default.help">
{{ charCount }}/{{ maxCharLimit }}
{{ charCount }}/{{ maxCharLimit }}
</small>
</template>
@@ -48,13 +48,6 @@ export default {
},
setup (props, context) {
const {
compVal,
inputStyle,
hasValidation,
hasError
} = useFormInput(props, context, props.nativeType === 'file' ? 'file-' : null)
const onChange = (event) => {
if (props.nativeType !== 'file') return
@@ -69,10 +62,9 @@ export default {
}
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context, props.nativeType === 'file' ? 'file-' : null),
onEnterPress,
onChange
}
},
computed: {

View File

@@ -1,11 +1,11 @@
<template>
<input-wrapper v-bind="$props">
<input-wrapper v-bind="inputWrapperProps">
<template #label>
<span />
</template>
<div class="flex">
<v-switch :id="id?id:name" v-model="compVal" class="inline-block mr-2" :disabled="disabled" />
<v-switch :id="id?id:name" v-model="compVal" class="inline-block mr-2" :disabled="disabled?true:null" />
<slot name="label">
<span>{{ label }} <span v-if="required" class="text-red-500 required-dot">*</span></span>
</slot>
@@ -34,13 +34,8 @@ export default {
},
setup (props, context) {
const { compVal, inputStyle, hasValidation, hasError } = useFormInput(props, context)
return {
compVal,
inputStyle,
hasValidation,
hasError
...useFormInput(props, context)
}
},

View File

@@ -38,12 +38,12 @@ export default {
props: {
id: { type: String, required: false },
name: { type: String, required: false },
theme: { type: Object, required: true },
label: { type: String, required: false },
form: { type: Object, required: false },
theme: { type: Object, required: true },
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 },
hideFieldName: { type: Boolean, default: true },

View File

@@ -7,7 +7,7 @@
type="checkbox"
:class="sizeClasses"
class="rounded border-gray-500 cursor-pointer"
:disabled="disabled"
:disabled="disabled?true:null"
@click="handleClick"
>
<label :for="id || name" class="text-gray-700 dark:text-gray-300 ml-2" :class="{'!cursor-not-allowed':disabled}">

View File

@@ -6,6 +6,7 @@ export const inputProps = {
name: { type: String, required: true },
label: { type: String, required: false },
form: { type: Object, required: false },
theme: { type: Object, default: () => themes.default },
modelValue: { required: false },
required: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
@@ -14,7 +15,6 @@ export const inputProps = {
hideFieldName: { type: Boolean, default: false },
help: { type: String, default: null },
helpPosition: { type: String, default: 'below_input' },
theme: { type: Object, default: () => themes.default },
color: { type: String, default: '#3B82F6' },
wrapperClass: { type: String, default: 'relative mb-3' }
}
@@ -58,11 +58,21 @@ export function useFormInput (props, context, formPrefixKey = null) {
}
})
const inputWrapperProps = computed(() => {
const wrapperProps = {}
Object.keys(inputProps).forEach((key) => {
if (!['modelValue', 'disabled', 'placeholder', 'color'].includes(key)) {
wrapperProps[key] = props[key]
}
})
return wrapperProps
})
// Watch for changes in props.modelValue and update the local content
watch(
() => props.modelValue,
(newValue) => {
if (content.value !== newValue){
if (content.value !== newValue) {
content.value = newValue
}
}
@@ -72,6 +82,7 @@ export function useFormInput (props, context, formPrefixKey = null) {
compVal,
inputStyle,
hasValidation,
hasError
hasError,
inputWrapperProps
}
}

View File

@@ -1,5 +1,5 @@
<template>
<button :type="nativeType" :disabled="loading" :class="`py-${sizes['p-y']} px-${sizes['p-x']} text-${sizes['font']} ${theme.Button.body}`" :style="buttonStyle"
<button :type="nativeType" :disabled="loading?true:null" :class="`py-${sizes['p-y']} px-${sizes['p-x']} text-${sizes['font']} ${theme.Button.body}`" :style="buttonStyle"
class="btn" @click="$emit('click',$event)"
>
<template v-if="!loading">

View File

@@ -12,8 +12,9 @@
@click.prevent="openAddFieldSidebar"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
stroke="currentColor" class="w-5 h-5"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
</div>
<div class="p-2 text-gray-300 hover:text-blue-500 cursor-pointer" role="button"
@@ -50,7 +51,7 @@
</div>
<component :is="getFieldComponents" v-if="getFieldComponents"
v-bind="inputProperties(field)" :required="isFieldRequired"
:disabled="isFieldDisabled"
:disabled="isFieldDisabled?true:null"
/>
<template v-else>
<div v-if="field.type === 'nf-text' && field.content" :id="field.id" :key="field.id"
@@ -79,7 +80,7 @@
<script>
import { computed } from 'vue'
import { useWorkingFormStore } from '../../../stores/working_form';
import { useWorkingFormStore } from '../../../stores/working_form'
import FormLogicPropertyResolver from '../../../forms/FormLogicPropertyResolver.js'
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
@@ -112,24 +113,24 @@ export default {
type: Object,
required: true
},
adminPreview: {type: Boolean, default: false} // If used in FormEditorPreview
adminPreview: { type: Boolean, default: false } // If used in FormEditorPreview
},
setup () {
const workingFormStore = useWorkingFormStore()
return {
workingFormStore,
selectedFieldIndex : computed(() => workingFormStore.selectedFieldIndex),
showEditFieldSidebar : computed(() => workingFormStore.showEditFieldSidebar)
selectedFieldIndex: computed(() => workingFormStore.selectedFieldIndex),
showEditFieldSidebar: computed(() => workingFormStore.showEditFieldSidebar)
}
},
data() {
data () {
return {}
},
computed: {
fieldComponents() {
fieldComponents () {
return {
text: 'TextInput',
number: 'TextInput',
@@ -146,7 +147,7 @@ export default {
/**
* Get the right input component for the field/options combination
*/
getFieldComponents() {
getFieldComponents () {
const field = this.field
if (field.type === 'text' && field.multi_lines) {
return 'TextAreaInput'
@@ -174,27 +175,27 @@ export default {
}
return this.fieldComponents[field.type]
},
isPublicFormPage() {
isPublicFormPage () {
return this.$route.name === 'forms.show_public'
},
isFieldHidden() {
isFieldHidden () {
return !this.showHidden && this.shouldBeHidden
},
shouldBeHidden() {
shouldBeHidden () {
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isHidden()
},
isFieldRequired() {
isFieldRequired () {
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isRequired()
},
isFieldDisabled() {
isFieldDisabled () {
return (new FormLogicPropertyResolver(this.field, this.dataFormValue)).isDisabled()
},
beingEdited() {
beingEdited () {
return this.adminPreview && this.showEditFieldSidebar && this.form.properties.findIndex((item) => {
return item.id === this.field.id
}) === this.selectedFieldIndex
},
selectionFieldsOptions() {
selectionFieldsOptions () {
// For auto update hidden options
let fieldsOptions = []
@@ -209,27 +210,27 @@ export default {
return fieldsOptions
},
fieldSideBarOpened() {
fieldSideBarOpened () {
return this.adminPreview && (this.form && this.selectedFieldIndex !== null) ? (this.form.properties[this.selectedFieldIndex] && this.showEditFieldSidebar) : false
}
},
watch: {},
mounted() {
mounted () {
},
methods: {
editFieldOptions() {
editFieldOptions () {
this.workingFormStore.openSettingsForField(this.field)
},
openAddFieldSidebar() {
openAddFieldSidebar () {
this.workingFormStore.openAddFieldSidebar(this.field)
},
/**
* Get the right input component for the field/options combination
*/
getFieldClasses() {
getFieldClasses () {
let classes = ''
if (this.adminPreview) {
classes += '-mx-4 px-4 -my-1 py-1 group/nffield relative transition-colors'
@@ -240,7 +241,7 @@ export default {
}
return classes
},
getFieldWidthClasses(field) {
getFieldWidthClasses (field) {
if (!field.width || field.width === 'full') return 'w-full px-2'
else if (field.width === '1/2') {
return 'w-full sm:w-1/2 px-2'
@@ -254,7 +255,7 @@ export default {
return 'w-full sm:w-3/4 px-2'
}
},
getFieldAlignClasses(field) {
getFieldAlignClasses (field) {
if (!field.align || field.align === 'left') return 'text-left'
else if (field.align === 'right') {
return 'text-right'
@@ -267,7 +268,7 @@ export default {
/**
* Get the right input component options for the field/options
*/
inputProperties(field) {
inputProperties (field) {
const inputProperties = {
key: field.id,
name: field.id,
@@ -277,7 +278,7 @@ export default {
placeholder: field.placeholder,
help: field.help,
helpPosition: (field.help_position) ? field.help_position : 'below_input',
uppercaseLabels: this.form.uppercase_labels == 1 || this.form.uppercase_labels == true,
uppercaseLabels: this.form.uppercase_labels == 1 || this.form.uppercase_labels == true,
theme: this.theme,
maxCharLimit: (field.max_char_limit) ? parseInt(field.max_char_limit) : 2000,
showCharLimit: field.show_char_limit || false
@@ -309,7 +310,7 @@ export default {
} else if (field.type === 'files' || (field.type === 'url' && field.file_upload)) {
inputProperties.multiple = (field.multiple !== undefined && field.multiple)
inputProperties.mbLimit = 5
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) {
inputProperties.numberOfStars = parseInt(field.rating_max_value)
} else if (field.type === 'number' && field.is_scale) {
@@ -323,7 +324,7 @@ export default {
}
return inputProperties
},
}
}
}
</script>

View File

@@ -11,7 +11,7 @@
</template>
<toggle-switch-input :value="value.hide_title" name="hide_title" class="mt-4"
label="Hide Form Title"
:disabled="form.hide_title===true"
:disabled="(form.hide_title===true)?true:null"
:help="hideTitleHelp"
@update:model-value="onChangeHideTitle"
/>

View File

@@ -27,7 +27,7 @@
Submission confirmation
<pro-tag />
</h2>
<toggle-switch-input :disabled="emailSubmissionConfirmationField===null" name="send_submission_confirmation"
<toggle-switch-input :disabled="(emailSubmissionConfirmationField===null)?true:null" name="send_submission_confirmation"
:form="form" class="mt-4"
label="Send submission confirmation" :help="emailSubmissionConfirmationHelp"
/>

View File

@@ -10,8 +10,8 @@
/>
<template v-if="hasInput">
<component :is="inputComponentData.component" v-model="content.value" class="w-full"
:name="'value_'+property.id" v-bind="inputComponentData" placeholder="Filter Value"
<component v-bind="inputComponentData" :is="inputComponentData.component" v-model="content.value" class="w-full"
:name="'value_'+property.id" placeholder="Filter Value"
@input="$emit('input',castContent(content))"
/>
</template>
@@ -62,7 +62,7 @@ export default {
}
if (['select', 'multi_select'].includes(this.property.type)) {
componentData.multiple = false;
componentData.multiple = false
componentData.options = this.property[this.property.type].options.map(option => {
return {
name: option.name,
@@ -97,7 +97,7 @@ export default {
this.content.property_meta = {
id: this.property.id,
type: this.property.type,
type: this.property.type
}
this.isMounted = true
},

View File

@@ -7,7 +7,7 @@
option-key="identifier"
name="group-control-slot-rule"
/>
<v-button class="ml-1 mt-1" color="blue" size="small" :disabled="selectedRule === ''" @click="addRule">
<v-button class="ml-1 mt-1" color="blue" size="small" :disabled="(selectedRule === '')?true:null" @click="addRule">
Add Condition
</v-button>
<v-button class="ml-1 mt-1" color="outline-blue" size="small" @click="groupCtrl.newGroup">

View File

@@ -1,18 +1,20 @@
<template>
<modal :show="show" @close="$emit('close')" :closeable="!aiForm.busy">
<modal :show="show" :closeable="!aiForm.busy" @close="$emit('close')">
<template #icon>
<template v-if="state=='default'">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-10 h-10 text-blue">
<path fill-rule="evenodd"
d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z"
clip-rule="evenodd"/>
clip-rule="evenodd"
/>
</svg>
</template>
<template v-else-if="state=='ai'">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
<path fill-rule="evenodd"
d="M14.615 1.595a.75.75 0 01.359.852L12.982 9.75h7.268a.75.75 0 01.548 1.262l-10.5 11.25a.75.75 0 01-1.272-.71l1.992-7.302H3.75a.75.75 0 01-.548-1.262l10.5-11.25a.75.75 0 01.913-.143z"
clip-rule="evenodd"/>
clip-rule="evenodd"
/>
</svg>
</template>
</template>
@@ -24,38 +26,48 @@
AI-powered form generator
</template>
</template>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-8" v-if="state=='default'">
<div class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50"
@click="$emit('close')" v-track.select_form_base="{base:'contact-form'}">
<div v-if="state=='default'" class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-8">
<div v-track.select_form_base="{base:'contact-form'}"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" @click="$emit('close')"
>
<div class="p-4">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
<path d="M1.5 8.67v8.58a3 3 0 003 3h15a3 3 0 003-3V8.67l-8.928 5.493a3 3 0 01-3.144 0L1.5 8.67z"/>
<path d="M22.5 6.908V6.75a3 3 0 00-3-3h-15a3 3 0 00-3 3v.158l9.714 5.978a1.5 1.5 0 001.572 0L22.5 6.908z"/>
<path d="M1.5 8.67v8.58a3 3 0 003 3h15a3 3 0 003-3V8.67l-8.928 5.493a3 3 0 01-3.144 0L1.5 8.67z" />
<path d="M22.5 6.908V6.75a3 3 0 00-3-3h-15a3 3 0 00-3 3v.158l9.714 5.978a1.5 1.5 0 001.572 0L22.5 6.908z" />
</svg>
</div>
<p class="font-medium">Start from a simple contact form</p>
<p class="font-medium">
Start from a simple contact form
</p>
</div>
<div v-if="aiFeaturesEnabled" v-track.select_form_base="{base:'ai'}"
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" @click="state='ai'">
class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50" @click="state='ai'"
>
<div class="p-4 relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
<path fill-rule="evenodd"
d="M14.615 1.595a.75.75 0 01.359.852L12.982 9.75h7.268a.75.75 0 01.548 1.262l-10.5 11.25a.75.75 0 01-1.272-.71l1.992-7.302H3.75a.75.75 0 01-.548-1.262l10.5-11.25a.75.75 0 01.913-.143z"
clip-rule="evenodd"/>
clip-rule="evenodd"
/>
</svg>
</div>
<p class="font-medium text-blue-700">Use our AI to create the form</p>
<p class="font-medium text-blue-700">
Use our AI to create the form
</p>
<span class="text-xs text-gray-500">(1 min)</span>
</div>
<div class="rounded-md border p-4 flex flex-col items-center cursor-pointer hover:bg-gray-50 relative">
<div class="p-4 relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-8 h-8 text-blue-500">
<path
d="M11.25 5.337c0-.355-.186-.676-.401-.959a1.647 1.647 0 01-.349-1.003c0-1.036 1.007-1.875 2.25-1.875S15 2.34 15 3.375c0 .369-.128.713-.349 1.003-.215.283-.401.604-.401.959 0 .332.278.598.61.578 1.91-.114 3.79-.342 5.632-.676a.75.75 0 01.878.645 49.17 49.17 0 01.376 5.452.657.657 0 01-.66.664c-.354 0-.675-.186-.958-.401a1.647 1.647 0 00-1.003-.349c-1.035 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401.31 0 .557.262.534.571a48.774 48.774 0 01-.595 4.845.75.75 0 01-.61.61c-1.82.317-3.673.533-5.555.642a.58.58 0 01-.611-.581c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.035-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959a.641.641 0 01-.658.643 49.118 49.118 0 01-4.708-.36.75.75 0 01-.645-.878c.293-1.614.504-3.257.629-4.924A.53.53 0 005.337 15c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.036 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.369 0 .713.128 1.003.349.283.215.604.401.959.401a.656.656 0 00.659-.663 47.703 47.703 0 00-.31-4.82.75.75 0 01.83-.832c1.343.155 2.703.254 4.077.294a.64.64 0 00.657-.642z"/>
d="M11.25 5.337c0-.355-.186-.676-.401-.959a1.647 1.647 0 01-.349-1.003c0-1.036 1.007-1.875 2.25-1.875S15 2.34 15 3.375c0 .369-.128.713-.349 1.003-.215.283-.401.604-.401.959 0 .332.278.598.61.578 1.91-.114 3.79-.342 5.632-.676a.75.75 0 01.878.645 49.17 49.17 0 01.376 5.452.657.657 0 01-.66.664c-.354 0-.675-.186-.958-.401a1.647 1.647 0 00-1.003-.349c-1.035 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401.31 0 .557.262.534.571a48.774 48.774 0 01-.595 4.845.75.75 0 01-.61.61c-1.82.317-3.673.533-5.555.642a.58.58 0 01-.611-.581c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.035-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959a.641.641 0 01-.658.643 49.118 49.118 0 01-4.708-.36.75.75 0 01-.645-.878c.293-1.614.504-3.257.629-4.924A.53.53 0 005.337 15c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.036 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.369 0 .713.128 1.003.349.283.215.604.401.959.401a.656.656 0 00.659-.663 47.703 47.703 0 00-.31-4.82.75.75 0 01.83-.832c1.343.155 2.703.254 4.077.294a.64.64 0 00.657-.642z"
/>
</svg>
</div>
<p class="font-medium">Start from a template</p>
<router-link :to="{name:'templates'}" v-track.select_form_base="{base:'template'}" class="absolute inset-0"/>
<p class="font-medium">
Start from a template
</p>
<router-link v-track.select_form_base="{base:'template'}" :to="{name:'templates'}" class="absolute inset-0" />
</div>
</div>
<div v-else-if="state=='ai'">
@@ -65,26 +77,29 @@
</svg>
Back
</a>
<text-area-input label="Form Description" :disabled="loading" :form="aiForm" name="form_prompt" help="Give us a description of the form you want to build (the more details the better)"
placeholder="A simple contact form, with a name, email and message field" />
<v-button class="w-full" @click.prevent="generateForm" :loading="loading">
<text-area-input label="Form Description" :disabled="loading?true:null" :form="aiForm" name="form_prompt" help="Give us a description of the form you want to build (the more details the better)"
placeholder="A simple contact form, with a name, email and message field"
/>
<v-button class="w-full" :loading="loading" @click.prevent="generateForm">
Generate a form
</v-button>
<p class="text-gray-500 text-xs text-center mt-1">~60 sec</p>
<p class="text-gray-500 text-xs text-center mt-1">
~60 sec
</p>
</div>
</modal>
</template>
<script>
import Loader from "../../../common/Loader.vue";
import Form from "vform";
import axios from "axios";
import Loader from '../../../common/Loader.vue'
import Form from 'vform'
import axios from 'axios'
export default {
name: 'CreateFormBaseModal',
components: {Loader},
components: { Loader },
props: {
show: {type: Boolean, required: true},
show: { type: Boolean, required: true }
},
data: () => ({
@@ -92,17 +107,17 @@ export default {
aiForm: new Form({
form_prompt: ''
}),
loading: false,
loading: false
}),
computed: {
aiFeaturesEnabled() {
aiFeaturesEnabled () {
return window.config.ai_features_enabled
}
},
methods: {
generateForm() {
generateForm () {
if (this.loading) return
this.loading = true
@@ -115,7 +130,7 @@ export default {
this.state = 'default'
})
},
fetchGeneratedForm(generationId) {
fetchGeneratedForm (generationId) {
// check every 4 seconds if form is generated
setTimeout(() => {
axios.get('/api/forms/ai/' + generationId).then(response => {

View File

@@ -70,7 +70,7 @@
<div class="border-t mt-4 -mx-4" />
<toggle-switch-input v-model="advancedOptions.hide_title" name="hide_title" class="mt-4"
label="Hide Form Title"
:disabled="form.hide_title===true"
:disabled="(form.hide_title===true)?true:null"
:help="hideTitleHelp"
/>
<color-input v-model="advancedOptions.bgcolor" name="bgcolor" class="mt-4"

View File

@@ -2,8 +2,8 @@
<modal :show="show" max-width="lg" @close="close">
<text-input ref="companyName" label="Company Name" name="name" :required="true" :form="form" help="Name that will appear on invoices" />
<text-input label="Email" name="email" native-type="email" :required="true" :form="form" help="Where invoices will be sent" />
<v-button :loading="form.busy || loading" :disabled="form.busy || loading" class="mt-6 block mx-auto"
@click="saveDetails" :arrow="true"
<v-button :loading="form.busy || loading" :disabled="(form.busy || loading)?true:null" class="mt-6 block mx-auto"
:arrow="true" @click="saveDetails"
>
Go to checkout
</v-button>
@@ -38,7 +38,7 @@ export default {
setup () {
const authStore = useAuthStore()
return {
user : computed(() => authStore.user)
user: computed(() => authStore.user)
}
},
@@ -50,6 +50,8 @@ export default {
loading: false
}),
computed: {},
watch: {
user () {
this.updateUser()
@@ -71,7 +73,7 @@ export default {
},
methods: {
updateUser() {
updateUser () {
if (this.user) {
this.form.name = this.user.name
this.form.email = this.user.email
@@ -94,8 +96,6 @@ export default {
close () {
this.$emit('close')
}
},
computed: {}
}
}
</script>