Form editor v2 (#579)
* Form editor v2 * fix template test * setFormDefaults when save * fix form cleaning dark mode * improvements on open sidebar * UI polish * Fix change type button --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
@@ -1,53 +1,62 @@
|
||||
<template>
|
||||
<div v-if="field">
|
||||
<!-- General -->
|
||||
<div class="border-b py-2 px-4">
|
||||
<h3 class="font-semibold block text-lg">
|
||||
General
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Exclude this field or make it required.
|
||||
</p>
|
||||
<toggle-switch-input
|
||||
<div
|
||||
v-if="field"
|
||||
class="py-2"
|
||||
>
|
||||
<div class="px-4">
|
||||
<text-input
|
||||
name="name"
|
||||
:form="field"
|
||||
name="hidden"
|
||||
label="Hidden"
|
||||
@update:model-value="onFieldHiddenChange"
|
||||
wrapper-class="mb-2"
|
||||
:required="true"
|
||||
label="Block Name"
|
||||
/>
|
||||
<select-input
|
||||
name="width"
|
||||
class="mt-3"
|
||||
:options="[
|
||||
{ name: 'Full', value: 'full' },
|
||||
{ name: '1/2 (half width)', value: '1/2' },
|
||||
{ name: '1/3 (a third of the width)', value: '1/3' },
|
||||
{ name: '2/3 (two thirds of the width)', value: '2/3' },
|
||||
{ name: '1/4 (a quarter of the width)', value: '1/4' },
|
||||
{ name: '3/4 (three quarters of the width)', value: '3/4' },
|
||||
]"
|
||||
|
||||
<HiddenRequiredDisabled
|
||||
:form="field"
|
||||
label="Field Width"
|
||||
/>
|
||||
<select-input
|
||||
v-if="['nf-text', 'nf-image'].includes(field.type)"
|
||||
name="align"
|
||||
class="mt-3"
|
||||
:options="[
|
||||
{ name: 'Left', value: 'left' },
|
||||
{ name: 'Center', value: 'center' },
|
||||
{ name: 'Right', value: 'right' },
|
||||
{ name: 'Justify', value: 'justify' },
|
||||
]"
|
||||
:form="field"
|
||||
label="Field Alignment"
|
||||
:field="field"
|
||||
:can-be-disabled="false"
|
||||
:can-be-hidden="true"
|
||||
:can-be-required="false"
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 mt-2">
|
||||
<select-input
|
||||
name="width"
|
||||
class="flex-grow"
|
||||
:options="[
|
||||
{ name: 'Full', value: 'full' },
|
||||
{ name: '1/2', value: '1/2' },
|
||||
{ name: '1/3', value: '1/3' },
|
||||
{ name: '2/3', value: '2/3' },
|
||||
{ name: '1/4', value: '1/4' },
|
||||
{ name: '3/4', value: '3/4' },
|
||||
]"
|
||||
:form="field"
|
||||
label="Width"
|
||||
/>
|
||||
<select-input
|
||||
v-if="['nf-text', 'nf-image'].includes(field.type)"
|
||||
name="align"
|
||||
class="flex-grow"
|
||||
:options="[
|
||||
{ name: 'Left', value: 'left' },
|
||||
{ name: 'Center', value: 'center' },
|
||||
{ name: 'Right', value: 'right' },
|
||||
{ name: 'Justify', value: 'justify' },
|
||||
]"
|
||||
:form="field"
|
||||
label="Alignment"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="field.type == 'nf-text'"
|
||||
class="border-b py-2 px-4"
|
||||
class="border-t py-2"
|
||||
>
|
||||
<rich-text-area-input
|
||||
class="mx-4"
|
||||
name="content"
|
||||
:form="field"
|
||||
label="Content"
|
||||
@@ -62,43 +71,25 @@
|
||||
<text-input
|
||||
name="next_btn_text"
|
||||
:form="field"
|
||||
label="Text of next button"
|
||||
label="Next button label"
|
||||
:required="true"
|
||||
/>
|
||||
<text-input
|
||||
name="previous_btn_text"
|
||||
:form="field"
|
||||
label="Text of previous button"
|
||||
help="Shown on the next page"
|
||||
label="Previous button label"
|
||||
help="Displayed on the next page"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="field.type == 'nf-divider'"
|
||||
class="border-b py-2 px-4"
|
||||
>
|
||||
<text-input
|
||||
name="name"
|
||||
:form="field"
|
||||
:required="true"
|
||||
label="Field Name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="field.type == 'nf-image'"
|
||||
class="border-b py-2 px-4"
|
||||
class="border-t py-2"
|
||||
>
|
||||
<text-input
|
||||
name="name"
|
||||
:form="field"
|
||||
:required="true"
|
||||
label="Field Name"
|
||||
/>
|
||||
<image-input
|
||||
name="image_block"
|
||||
class="mt-3"
|
||||
class="mx-4"
|
||||
:form="field"
|
||||
label="Upload Image"
|
||||
:required="false"
|
||||
@@ -107,95 +98,48 @@
|
||||
|
||||
<div
|
||||
v-else-if="field.type == 'nf-code'"
|
||||
class="border-b py-2 px-4"
|
||||
class="border-t"
|
||||
>
|
||||
<code-input
|
||||
name="content"
|
||||
class="mt-4 mx-4"
|
||||
:form="field"
|
||||
label="Content"
|
||||
help="You can add any html code, including iframes"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="border-b py-2 px-4"
|
||||
>
|
||||
<p>No settings found.</p>
|
||||
</div>
|
||||
|
||||
<!-- Logic Block -->
|
||||
<form-block-logic-editor
|
||||
class="py-2 px-4 border-b"
|
||||
:form="form"
|
||||
:field="field"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormBlockLogicEditor from "../../components/form-logic-components/FormBlockLogicEditor.vue"
|
||||
<script setup>
|
||||
import HiddenRequiredDisabled from './HiddenRequiredDisabled.vue'
|
||||
|
||||
export default {
|
||||
name: "BlockOptions",
|
||||
components: { FormBlockLogicEditor },
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editorToolbarCustom: [["bold", "italic", "underline", "link"]],
|
||||
}
|
||||
const props = defineProps({
|
||||
field: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
})
|
||||
|
||||
computed: {},
|
||||
watch(() => props.field?.width, (val) => {
|
||||
if (val === undefined || val === null) {
|
||||
props.field.width = 'full'
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch: {
|
||||
"field.width": {
|
||||
handler(val) {
|
||||
if (val === undefined || val === null) {
|
||||
this.field.width = "full"
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
"field.align": {
|
||||
handler(val) {
|
||||
if (val === undefined || val === null) {
|
||||
this.field.align = "left"
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
watch(() => props.field?.align, (val) => {
|
||||
if (val === undefined || val === null) {
|
||||
props.field.align = 'left'
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
created() {
|
||||
if (this.field?.width === undefined || this.field?.width === null) {
|
||||
this.field.width = "full"
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {},
|
||||
|
||||
methods: {
|
||||
onFieldHiddenChange(val) {
|
||||
this.field.hidden = val
|
||||
if (this.field.hidden) {
|
||||
this.field.required = false
|
||||
}
|
||||
},
|
||||
onFieldHelpPositionChange(val) {
|
||||
if (!val) {
|
||||
this.field.help_position = "below_input"
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
onMounted(() => {
|
||||
if (props.field?.width === undefined || props.field?.width === null) {
|
||||
props.field.width = 'full'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,66 +1,46 @@
|
||||
<template>
|
||||
<dropdown
|
||||
<UPopover
|
||||
v-if="changeTypeOptions.length > 0"
|
||||
ref="newTypeDropdown"
|
||||
dusk="nav-dropdown"
|
||||
v-model:open="open"
|
||||
class="-mb-1"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<v-button
|
||||
class="relative"
|
||||
:class="btnClasses"
|
||||
size="small"
|
||||
color="light-gray"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="h-4 w-4 text-blue-600 inline mr-1 -mt-1"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
|
||||
/>
|
||||
</svg>
|
||||
<span class="whitespace-nowrap">Change Type</span>
|
||||
</v-button>
|
||||
</template>
|
||||
<div class="flex items-center gap-1.5 group">
|
||||
<Icon
|
||||
name="heroicons:arrows-right-left-20-solid"
|
||||
class="flex-shrink-0 w-5 h-5 text-gray-400 group-hover:text-gray-500"
|
||||
/>
|
||||
<span class="truncate">Change Type</span>
|
||||
</div>
|
||||
|
||||
<a
|
||||
v-for="(op, index) in changeTypeOptions"
|
||||
:key="index"
|
||||
href="#"
|
||||
class="block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
@click.prevent="changeType(op.value)"
|
||||
>
|
||||
{{ op.name }}
|
||||
</a>
|
||||
</dropdown>
|
||||
<template #panel>
|
||||
<a
|
||||
v-for="(op, index) in changeTypeOptions"
|
||||
:key="index"
|
||||
href="#"
|
||||
class="block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
|
||||
@click.prevent="changeType(op.value)"
|
||||
>
|
||||
{{ op.name }}
|
||||
</a>
|
||||
</template>
|
||||
</UPopover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Dropdown from "~/components/global/Dropdown.vue"
|
||||
|
||||
export default {
|
||||
name: "ChangeFieldType",
|
||||
components: { Dropdown },
|
||||
components: {},
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
btnClasses: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['changeType'],
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
open: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
@@ -114,7 +94,7 @@ export default {
|
||||
changeType(newType) {
|
||||
if (newType) {
|
||||
this.$emit("changeType", newType)
|
||||
this.$refs.newTypeDropdown.close()
|
||||
this.open = false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,47 +1,33 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="field"
|
||||
class="py-2"
|
||||
class="pb-2"
|
||||
>
|
||||
<!-- General -->
|
||||
<div class="border-b px-4">
|
||||
<h3 class="font-semibold block text-lg">
|
||||
General
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-2 text-xs">
|
||||
Exclude this field or make it required.
|
||||
</p>
|
||||
<toggle-switch-input
|
||||
<div class="px-4">
|
||||
<text-input
|
||||
name="name"
|
||||
class="mt-2"
|
||||
:form="field"
|
||||
name="required"
|
||||
label="Required"
|
||||
@update:model-value="onFieldRequiredChange"
|
||||
:required="true"
|
||||
wrapper-class="mb-2"
|
||||
label="Field Name"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="hidden"
|
||||
label="Hidden"
|
||||
@update:model-value="onFieldHiddenChange"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="disabled"
|
||||
label="Disabled"
|
||||
@update:model-value="onFieldDisabledChange"
|
||||
<HiddenRequiredDisabled
|
||||
class="mt-4"
|
||||
:field="field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Checkbox -->
|
||||
<div
|
||||
v-if="field.type === 'checkbox'"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Checkbox
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Advanced options for checkbox.
|
||||
</p>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-check-circle"
|
||||
title="Checkbox"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="use_toggle_switch"
|
||||
@@ -53,11 +39,12 @@
|
||||
<!-- File Uploads -->
|
||||
<div
|
||||
v-if="field.type === 'files'"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg mb-3">
|
||||
File uploads
|
||||
</h3>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-paper-clip"
|
||||
title="File uploads"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="multiple"
|
||||
@@ -92,14 +79,12 @@
|
||||
|
||||
<div
|
||||
v-if="field.type === 'rating'"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Rating
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Advanced options for rating.
|
||||
</p>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-star"
|
||||
title="Rating"
|
||||
/>
|
||||
<text-input
|
||||
name="rating_max_value"
|
||||
native-type="number"
|
||||
@@ -113,14 +98,12 @@
|
||||
|
||||
<div
|
||||
v-if="field.type === 'scale'"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Scale
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Advanced options for scale.
|
||||
</p>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-scale-20-solid"
|
||||
title="Scale"
|
||||
/>
|
||||
<text-input
|
||||
name="scale_min_value"
|
||||
native-type="number"
|
||||
@@ -151,14 +134,12 @@
|
||||
|
||||
<div
|
||||
v-if="field.type === 'slider'"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Slider
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Advanced options for slider.
|
||||
</p>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-adjustments-horizontal"
|
||||
title="Slider"
|
||||
/>
|
||||
<text-input
|
||||
name="slider_min_value"
|
||||
native-type="number"
|
||||
@@ -195,14 +176,12 @@
|
||||
<!-- Text Options -->
|
||||
<div
|
||||
v-if="field.type === 'text' && displayBasedOnAdvanced"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Text Options
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Keep it simple or make it a multi-lines input.
|
||||
</p>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-bars-3-bottom-left"
|
||||
title="Text Options"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="multi_lines"
|
||||
@@ -221,11 +200,12 @@
|
||||
<!-- Date Options -->
|
||||
<div
|
||||
v-if="field.type === 'date'"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Date Options
|
||||
</h3>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-calendar-20-solid"
|
||||
title="Date Options"
|
||||
/>
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
class="mt-3"
|
||||
@@ -286,14 +266,12 @@
|
||||
<!-- select/multiselect Options -->
|
||||
<div
|
||||
v-if="['select', 'multi_select'].includes(field.type)"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Select Options
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Advanced options for your select/multiselect fields.
|
||||
</p>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-chevron-up-down-20-solid"
|
||||
title="Select Options"
|
||||
/>
|
||||
<text-area-input
|
||||
v-model="optionsText"
|
||||
:name="field.id + '_options_text'"
|
||||
@@ -320,22 +298,11 @@
|
||||
<!-- Customization - Placeholder, Prefill, Relabel, Field Help -->
|
||||
<div
|
||||
v-if="displayBasedOnAdvanced"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Customization
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Change your form field name, pre-fill a value, add hints, etc.
|
||||
</p>
|
||||
|
||||
<text-input
|
||||
name="name"
|
||||
class="mt-3"
|
||||
:form="field"
|
||||
:required="true"
|
||||
label="Field Name"
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-adjustments-horizontal"
|
||||
title="Customization"
|
||||
/>
|
||||
|
||||
<toggle-switch-input
|
||||
@@ -568,7 +535,7 @@
|
||||
help="Maximum character limit of 2000"
|
||||
:required="false"
|
||||
/>
|
||||
<checkbox-input
|
||||
<toggle-switch-input
|
||||
name="show_char_limit"
|
||||
:form="field"
|
||||
class="mt-3"
|
||||
@@ -580,11 +547,13 @@
|
||||
<!-- Advanced Options -->
|
||||
<div
|
||||
v-if="field.type === 'text'"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg mb-3">
|
||||
Advanced Options
|
||||
</h3>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-bars-3-bottom-left"
|
||||
title="Advanced Options"
|
||||
/>
|
||||
|
||||
<toggle-switch-input
|
||||
:form="field"
|
||||
name="generates_uuid"
|
||||
@@ -600,19 +569,6 @@
|
||||
@update:model-value="onFieldGenAutoIdChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Logic Block -->
|
||||
<form-block-logic-editor
|
||||
class="py-2 px-4 border-b"
|
||||
:form="form"
|
||||
:field="field"
|
||||
/>
|
||||
|
||||
<custom-field-validation
|
||||
class="py-2 px-4 border-b pb-16"
|
||||
:form="form"
|
||||
:field="field"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -620,15 +576,15 @@
|
||||
import timezones from '~/data/timezones.json'
|
||||
import countryCodes from '~/data/country_codes.json'
|
||||
import CountryFlag from 'vue-country-flag-next'
|
||||
import FormBlockLogicEditor from '../../components/form-logic-components/FormBlockLogicEditor.vue'
|
||||
import CustomFieldValidation from '../../components/CustomFieldValidation.vue'
|
||||
import MatrixFieldOptions from './MatrixFieldOptions.vue'
|
||||
import HiddenRequiredDisabled from './HiddenRequiredDisabled.vue'
|
||||
import EditorSectionHeader from '~/components/open/forms/components/form-components/EditorSectionHeader.vue'
|
||||
import { format } from 'date-fns'
|
||||
import { default as _has } from 'lodash/has'
|
||||
|
||||
export default {
|
||||
name: 'FieldOptions',
|
||||
components: { CountryFlag, FormBlockLogicEditor, CustomFieldValidation, MatrixFieldOptions },
|
||||
components: { CountryFlag, MatrixFieldOptions, HiddenRequiredDisabled, EditorSectionHeader },
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
@@ -738,28 +694,6 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
onFieldDisabledChange(val) {
|
||||
this.field.disabled = val
|
||||
if (this.field.disabled) {
|
||||
this.field.hidden = false
|
||||
}
|
||||
},
|
||||
onFieldRequiredChange(val) {
|
||||
this.field.required = val
|
||||
if (this.field.required) {
|
||||
this.field.hidden = false
|
||||
}
|
||||
},
|
||||
onFieldHiddenChange(val) {
|
||||
this.field.hidden = val
|
||||
if (this.field.hidden) {
|
||||
this.field.required = false
|
||||
this.field.disabled = false
|
||||
} else {
|
||||
this.field.generates_uuid = false
|
||||
this.field.generates_auto_increment_id = false
|
||||
}
|
||||
},
|
||||
onFieldDateRangeChange(val) {
|
||||
this.field.date_range = val
|
||||
if (this.field.date_range) {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<button
|
||||
v-for="option in availableOptions"
|
||||
:key="option.name"
|
||||
class="flex flex-col items-center justify-center p-1.5 border rounded-lg transition-colors text-gray-500"
|
||||
:class="[
|
||||
option.class ? (typeof option.class === 'function' ? option.class(isSelected(option.name)) : option.class) : {},
|
||||
{
|
||||
'border-blue-500': isSelected(option.name),
|
||||
'hover:bg-gray-100 border-gray-300': !isSelected(option.name)
|
||||
}
|
||||
]"
|
||||
@click="toggleOption(option.name)"
|
||||
>
|
||||
<Icon
|
||||
:name="isSelected(option.name) && option.selectedIcon ? option.selectedIcon : option.icon"
|
||||
:class="[
|
||||
'w-4 h-4 mb-1',
|
||||
{
|
||||
'text-blue-500': isSelected(option.name),
|
||||
'text-inherit': !isSelected(option.name),
|
||||
},
|
||||
option.iconClass ? (typeof option.iconClass === 'function' ? option.iconClass(isSelected(option.name)) : option.iconClass) : {}
|
||||
]"
|
||||
/>
|
||||
<span
|
||||
class="text-xs"
|
||||
:class="{
|
||||
'text-blue-500': isSelected(option.name),
|
||||
'text-inherit': !isSelected(option.name),
|
||||
}"
|
||||
>{{ isSelected(option.name) ? option.selectedLabel ?? option.label : option.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const props = defineProps({
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
canBeDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
canBeRequired: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
canBeHidden: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:field'])
|
||||
|
||||
const options = ref([
|
||||
{
|
||||
name: 'required',
|
||||
label: 'Required',
|
||||
icon: 'ph:asterisk-bold',
|
||||
selectedIcon: 'ph:asterisk-bold',
|
||||
iconClass: (isActive) => isActive ? 'text-red-500' : '',
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
label: 'Hidden',
|
||||
icon: 'heroicons:eye',
|
||||
selectedIcon: 'heroicons:eye-slash-solid',
|
||||
},
|
||||
{
|
||||
name: 'disabled',
|
||||
label: 'Disabled',
|
||||
icon: 'heroicons:lock-open',
|
||||
selectedIcon: 'heroicons:lock-closed-solid',
|
||||
}
|
||||
])
|
||||
|
||||
const availableOptions = computed(() => {
|
||||
return options.value.filter(option => {
|
||||
if (option.name === 'disabled') return props.canBeDisabled
|
||||
if (option.name === 'required') return props.canBeRequired
|
||||
if (option.name === 'hidden') return props.canBeHidden
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const isSelected = (optionName) => {
|
||||
return props.field[optionName]
|
||||
}
|
||||
|
||||
const toggleOption = (optionName) => {
|
||||
const newValue = !props.field[optionName]
|
||||
|
||||
if (optionName === 'required' && newValue) {
|
||||
props.field.hidden = false
|
||||
} else if (optionName === 'hidden' && newValue) {
|
||||
props.field.required = false
|
||||
props.field.disabled = false
|
||||
props.field.generates_uuid = false
|
||||
props.field.generates_auto_increment_id = false
|
||||
} else if (optionName === 'disabled' && newValue) {
|
||||
props.field.hidden = false
|
||||
}
|
||||
|
||||
if ((optionName === 'disabled' && props.canBeDisabled) ||
|
||||
(optionName === 'required' && props.canBeRequired) ||
|
||||
(optionName === 'hidden' && props.canBeHidden)) {
|
||||
props.field[optionName] = newValue
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,14 +1,12 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="localField.type === 'matrix'"
|
||||
class="border-b py-2 px-4"
|
||||
class="px-4"
|
||||
>
|
||||
<h3 class="font-semibold block text-lg">
|
||||
Matrix
|
||||
</h3>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
Advanced options for matrix.
|
||||
</p>
|
||||
<EditorSectionHeader
|
||||
icon="i-heroicons-table-cells-20-solid"
|
||||
title="Matrix"
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="">
|
||||
@@ -72,6 +70,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import EditorSectionHeader from '~/components/open/forms/components/form-components/EditorSectionHeader.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
||||
Reference in New Issue
Block a user