Enhance Info Display Component Functionality
- Updated ComponentPreview.vue to include optional component labels and help text for improved user guidance. - Enhanced layout options for the info display component, adding support for side-by-side, grid, and horizontal layouts, with button functionality for fields. - Modified FormBuilderComponents.vue to update the description and default properties for the info display component, including new fields for button configuration. - Improved FormBuilderConfiguration.vue to add essential settings for the info display, including component title, name, label, help text, and layout style. - Enhanced FormBuilderFieldSettingsModal.vue to support configuration of information fields, including field type selection (text or button) and button-specific settings. - Added robust error handling for button click actions in the info display, ensuring a seamless user experience when interacting with links. - Refined styles across components for better visual consistency and usability.
This commit is contained in:
parent
d0cef85b72
commit
bc7daed988
@ -66,6 +66,16 @@
|
||||
|
||||
<!-- Information Display -->
|
||||
<div v-else-if="component.type === 'info-display'" class="py-2">
|
||||
<!-- Component Label -->
|
||||
<div v-if="component.props.label && component.props.label !== 'Info Display'" class="mb-2">
|
||||
<label class="text-sm font-medium text-gray-700">{{ component.props.label }}</label>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div v-if="component.props.help" class="mb-2 text-xs text-gray-600">
|
||||
{{ component.props.help }}
|
||||
</div>
|
||||
|
||||
<div class="info-display-container rounded" :style="{
|
||||
backgroundColor: component.props.backgroundColor || '#f9fafb',
|
||||
border: component.props.showBorder ? '1px solid #e5e7eb' : 'none'
|
||||
@ -77,25 +87,84 @@
|
||||
|
||||
<!-- Fields Display -->
|
||||
<div class="p-4">
|
||||
<div v-if="component.props.layout === 'grid'" class="grid grid-cols-2 gap-4">
|
||||
<!-- Side by Side Layout -->
|
||||
<div v-if="component.props.layout === 'side-by-side'" class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div v-for="(field, index) in component.props.fields" :key="index" class="field-item">
|
||||
<dt class="text-sm font-medium text-gray-600">{{ field.label }}</dt>
|
||||
<dd class="text-sm text-gray-900 mt-1">{{ field.value }}</dd>
|
||||
<dt class="text-sm font-medium text-gray-600 mb-2">{{ field.label }}</dt>
|
||||
<dd class="text-sm text-gray-900">
|
||||
<span v-if="field.type === 'button' && field.url" class="block">
|
||||
<button
|
||||
@click="handleInfoButtonClick(field.url, field.openInNewTab)"
|
||||
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
:target="field.openInNewTab ? '_blank' : '_self'"
|
||||
>
|
||||
<Icon v-if="field.icon" :name="field.icon" class="w-4 h-4 mr-1" />
|
||||
{{ field.value }}
|
||||
</button>
|
||||
</span>
|
||||
<span v-else>{{ field.value }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grid Layout -->
|
||||
<div v-else-if="component.props.layout === 'grid'" class="grid grid-cols-2 gap-4">
|
||||
<div v-for="(field, index) in component.props.fields" :key="index" class="field-item">
|
||||
<dt class="text-sm font-medium text-gray-600">{{ field.label }}</dt>
|
||||
<dd class="text-sm text-gray-900 mt-1">
|
||||
<span v-if="field.type === 'button' && field.url" class="block">
|
||||
<button
|
||||
@click="handleInfoButtonClick(field.url, field.openInNewTab)"
|
||||
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
:target="field.openInNewTab ? '_blank' : '_self'"
|
||||
>
|
||||
<Icon v-if="field.icon" :name="field.icon" class="w-4 h-4 mr-1" />
|
||||
{{ field.value }}
|
||||
</button>
|
||||
</span>
|
||||
<span v-else>{{ field.value }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Horizontal Layout -->
|
||||
<div v-else-if="component.props.layout === 'horizontal'" class="space-y-2">
|
||||
<div v-for="(field, index) in component.props.fields" :key="index"
|
||||
class="flex justify-between items-center">
|
||||
<dt class="text-sm font-medium text-gray-600">{{ field.label }}:</dt>
|
||||
<dd class="text-sm text-gray-900">{{ field.value }}</dd>
|
||||
<dd class="text-sm text-gray-900">
|
||||
<span v-if="field.type === 'button' && field.url" class="block">
|
||||
<button
|
||||
@click="handleInfoButtonClick(field.url, field.openInNewTab)"
|
||||
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
:target="field.openInNewTab ? '_blank' : '_self'"
|
||||
>
|
||||
<Icon v-if="field.icon" :name="field.icon" class="w-4 h-4 mr-1" />
|
||||
{{ field.value }}
|
||||
</button>
|
||||
</span>
|
||||
<span v-else>{{ field.value }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vertical Layout (Default) -->
|
||||
<div v-else class="space-y-3">
|
||||
<div v-for="(field, index) in component.props.fields" :key="index" class="field-item">
|
||||
<dt class="text-sm font-medium text-gray-600">{{ field.label }}</dt>
|
||||
<dd class="text-sm text-gray-900 mt-1">{{ field.value }}</dd>
|
||||
<dd class="text-sm text-gray-900 mt-1">
|
||||
<span v-if="field.type === 'button' && field.url" class="block">
|
||||
<button
|
||||
@click="handleInfoButtonClick(field.url, field.openInNewTab)"
|
||||
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
:target="field.openInNewTab ? '_blank' : '_self'"
|
||||
>
|
||||
<Icon v-if="field.icon" :name="field.icon" class="w-4 h-4 mr-1" />
|
||||
{{ field.value }}
|
||||
</button>
|
||||
</span>
|
||||
<span v-else>{{ field.value }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1470,6 +1539,21 @@ const updateRepeatingGroupField = (groupName, groupIndex, fieldName, value) => {
|
||||
|
||||
// Duplicate RepeatingGroupContainer definition removed
|
||||
|
||||
// Handle info display button clicks
|
||||
const handleInfoButtonClick = (url, openInNewTab = false) => {
|
||||
if (!url) return;
|
||||
|
||||
try {
|
||||
if (openInNewTab) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error opening URL:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Update table data for repeating-table component
|
||||
const updateTableData = (newData) => {
|
||||
const tableName = props.component.props.name;
|
||||
|
@ -707,17 +707,22 @@ const availableComponents = [
|
||||
name: 'Info Display',
|
||||
category: 'Layout',
|
||||
icon: 'material-symbols:info-outline',
|
||||
description: 'Display read-only information in key-value format',
|
||||
description: 'Display read-only information in key-value format with optional buttons',
|
||||
defaultProps: {
|
||||
name: 'info_display',
|
||||
title: 'Information',
|
||||
help: 'Display important information in an organized format',
|
||||
fields: [
|
||||
{ label: 'Customer Name', value: 'John Doe', key: 'customer_name' },
|
||||
{ label: 'Email', value: 'john@example.com', key: 'customer_email' },
|
||||
{ label: 'Phone', value: '+1-234-567-8900', key: 'customer_phone' }
|
||||
{ label: 'Customer Name', value: 'John Doe', key: 'customer_name', type: 'text' },
|
||||
{ label: 'Email', value: 'john@example.com', key: 'customer_email', type: 'text' },
|
||||
{ label: 'Phone', value: '+1-234-567-8900', key: 'customer_phone', type: 'text' },
|
||||
{ label: 'View Profile', value: 'View Details', key: 'profile_link', type: 'button', url: 'https://example.com/profile', openInNewTab: true, icon: 'material-symbols:link' }
|
||||
],
|
||||
layout: 'vertical', // vertical, horizontal, grid
|
||||
layout: 'side-by-side', // vertical, horizontal, grid, side-by-side
|
||||
showBorder: true,
|
||||
backgroundColor: '#f9fafb'
|
||||
backgroundColor: '#f9fafb',
|
||||
width: '100%',
|
||||
gridColumn: 'span 12'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -589,45 +589,79 @@
|
||||
|
||||
<!-- Information Display Configuration -->
|
||||
<template v-if="component.type === 'info-display'">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Title"
|
||||
name="title"
|
||||
v-model="configModel.title"
|
||||
help="Title displayed at the top"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Layout"
|
||||
name="layout"
|
||||
v-model="configModel.layout"
|
||||
:options="[
|
||||
{ label: 'Vertical (Label above value)', value: 'vertical' },
|
||||
{ label: 'Horizontal (Label: Value)', value: 'horizontal' },
|
||||
{ label: 'Grid (2 columns)', value: 'grid' }
|
||||
]"
|
||||
help="How to display the information fields"
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Essential Settings for Info Display -->
|
||||
<div class="space-y-4">
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Show Border"
|
||||
name="showBorder"
|
||||
v-model="configModel.showBorder"
|
||||
help="Show border around the information display"
|
||||
type="text"
|
||||
label="Component Title"
|
||||
name="title"
|
||||
v-model="configModel.title"
|
||||
help="Title displayed at the top of the information display"
|
||||
placeholder="e.g., User Information, Contact Details"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="color"
|
||||
label="Background Color"
|
||||
name="backgroundColor"
|
||||
v-model="configModel.backgroundColor"
|
||||
help="Background color"
|
||||
type="text"
|
||||
label="Component Name (Internal)"
|
||||
name="name"
|
||||
v-model="configModel.name"
|
||||
help="Used internally to identify this component"
|
||||
validation="required|alpha_numeric"
|
||||
placeholder="e.g., user_info, contact_details"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="textarea"
|
||||
label="Help Text"
|
||||
name="help"
|
||||
v-model="configModel.help"
|
||||
help="Additional information or instructions for users"
|
||||
placeholder="e.g., This section displays your profile information"
|
||||
rows="2"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Layout Style"
|
||||
name="layout"
|
||||
v-model="configModel.layout"
|
||||
:options="[
|
||||
{ label: 'Vertical (Label above value)', value: 'vertical' },
|
||||
{ label: 'Horizontal (Label: Value)', value: 'horizontal' },
|
||||
{ label: 'Grid (2 columns)', value: 'grid' },
|
||||
{ label: 'Side by Side (2 columns)', value: 'side-by-side' }
|
||||
]"
|
||||
help="How to display the information fields"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Show Border"
|
||||
name="showBorder"
|
||||
v-model="configModel.showBorder"
|
||||
help="Show border around the information display"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="color"
|
||||
label="Background Color"
|
||||
name="backgroundColor"
|
||||
v-model="configModel.backgroundColor"
|
||||
help="Background color for the information display"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Component Label"
|
||||
name="label"
|
||||
v-model="configModel.label"
|
||||
help="Label displayed above the component (optional)"
|
||||
placeholder="e.g., User Information Section"
|
||||
/>
|
||||
|
||||
<!-- Information Fields Management -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
@ -661,7 +695,23 @@
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
|
||||
<!-- Field Type Selection -->
|
||||
<div class="mb-2">
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Field Type"
|
||||
v-model="field.type"
|
||||
:options="[
|
||||
{ label: 'Text', value: 'text' },
|
||||
{ label: 'Button', value: 'button' }
|
||||
]"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Text Field Value -->
|
||||
<div v-if="field.type === 'text'" class="flex items-center">
|
||||
<FormKit
|
||||
type="text"
|
||||
placeholder="Value (e.g., John Doe)"
|
||||
@ -676,6 +726,50 @@
|
||||
<Icon name="material-symbols:delete-outline" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Button Field Configuration -->
|
||||
<div v-if="field.type === 'button'" class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<FormKit
|
||||
type="text"
|
||||
placeholder="Button Text (e.g., View Details)"
|
||||
v-model="field.value"
|
||||
:classes="{ outer: 'mb-0 flex-1' }"
|
||||
/>
|
||||
<button
|
||||
class="ml-2 p-1 text-gray-400 hover:text-red-500 rounded"
|
||||
@click="removeInfoField(index)"
|
||||
title="Remove field"
|
||||
>
|
||||
<Icon name="material-symbols:delete-outline" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<FormKit
|
||||
type="text"
|
||||
placeholder="URL (e.g., https://example.com)"
|
||||
v-model="field.url"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Open in new tab"
|
||||
v-model="field.openInNewTab"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
placeholder="Icon (optional, e.g., material-symbols:link)"
|
||||
v-model="field.icon"
|
||||
:classes="{ outer: 'mb-0 flex-1' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!configModel.fields || configModel.fields.length === 0" class="p-3 text-center text-gray-500 text-xs">
|
||||
@ -1103,7 +1197,11 @@ const addInfoField = () => {
|
||||
configModel.value.fields.push({
|
||||
label: `Field ${configModel.value.fields.length + 1}`,
|
||||
value: 'Value',
|
||||
key: `field_${configModel.value.fields.length + 1}`
|
||||
key: `field_${configModel.value.fields.length + 1}`,
|
||||
type: 'text', // text, button
|
||||
url: '', // for button type
|
||||
openInNewTab: false, // for button type
|
||||
icon: '' // for button type
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -156,6 +156,68 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Info Display-specific Essential Settings -->
|
||||
<template v-else-if="component.type === 'info-display'">
|
||||
<div class="space-y-4">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Component Title"
|
||||
name="title"
|
||||
v-model="configModel.title"
|
||||
help="Title displayed at the top of the information display"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
placeholder="e.g., User Information, Contact Details"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Component Name (Internal)"
|
||||
name="name"
|
||||
v-model="configModel.name"
|
||||
help="Used internally to identify this component"
|
||||
validation="required|alpha_numeric"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
placeholder="e.g., user_info, contact_details"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Component Label"
|
||||
name="label"
|
||||
v-model="configModel.label"
|
||||
help="Label displayed above the component (optional)"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
placeholder="e.g., User Information Section"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="textarea"
|
||||
label="Help Text"
|
||||
name="help"
|
||||
v-model="configModel.help"
|
||||
help="Additional information or instructions for users"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
placeholder="e.g., This section displays your profile information"
|
||||
rows="2"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Layout Style"
|
||||
name="layout"
|
||||
v-model="configModel.layout"
|
||||
:options="[
|
||||
{ label: 'Vertical (Label above value)', value: 'vertical' },
|
||||
{ label: 'Horizontal (Label: Value)', value: 'horizontal' },
|
||||
{ label: 'Grid (2 columns)', value: 'grid' },
|
||||
{ label: 'Side by Side (2 columns)', value: 'side-by-side' }
|
||||
]"
|
||||
help="How to display the information fields"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Standard form field Essential Settings -->
|
||||
<template v-else>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@ -2143,6 +2205,188 @@ if (this.element.querySelector('.file-upload')) {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Info Display Specific Settings -->
|
||||
<template v-if="component.type === 'info-display'">
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Show Border"
|
||||
name="showBorder"
|
||||
v-model="configModel.showBorder"
|
||||
help="Show border around the information display"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="color"
|
||||
label="Background Color"
|
||||
name="backgroundColor"
|
||||
v-model="configModel.backgroundColor"
|
||||
help="Background color for the information display"
|
||||
:classes="{ outer: 'field-wrapper' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Information Fields Management -->
|
||||
<div class="info-fields-section">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<h5 class="text-sm font-semibold text-gray-800">Information Fields</h5>
|
||||
<p class="text-xs text-gray-500 mt-1">Configure the information that will be displayed</p>
|
||||
</div>
|
||||
<button
|
||||
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
@click="addInfoField"
|
||||
>
|
||||
<Icon name="material-symbols:add" class="w-4 h-4 mr-1" />
|
||||
Add Field
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Fields List -->
|
||||
<div v-if="configModel.fields && configModel.fields.length > 0" class="space-y-4">
|
||||
<div
|
||||
v-for="(field, index) in configModel.fields"
|
||||
:key="index"
|
||||
class="field-card bg-white border border-gray-200 rounded-lg p-4 hover:border-gray-300 transition-colors"
|
||||
>
|
||||
<!-- Field Header -->
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center">
|
||||
<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3">
|
||||
<Icon
|
||||
:name="field.type === 'button' ? 'material-symbols:smart-button' : 'material-symbols:text-fields'"
|
||||
class="w-4 h-4 text-blue-600"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-sm font-medium text-gray-800">
|
||||
{{ field.label || `Field ${index + 1}` }}
|
||||
</h6>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ field.type === 'button' ? 'Button Field' : 'Text Field' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded transition-colors"
|
||||
@click="removeInfoField(index)"
|
||||
title="Remove field"
|
||||
>
|
||||
<Icon name="material-symbols:delete-outline" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Field Configuration -->
|
||||
<div class="space-y-3">
|
||||
<!-- Basic Info -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Display Label"
|
||||
v-model="field.label"
|
||||
placeholder="e.g., Customer Name"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Field Key"
|
||||
v-model="field.key"
|
||||
placeholder="e.g., customer_name"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Field Type -->
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Field Type"
|
||||
v-model="field.type"
|
||||
:options="[
|
||||
{ label: '📝 Text Information', value: 'text' },
|
||||
{ label: '🔗 Action Button', value: 'button' }
|
||||
]"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
|
||||
<!-- Text Field Configuration -->
|
||||
<div v-if="field.type === 'text'">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Display Value"
|
||||
v-model="field.value"
|
||||
placeholder="e.g., John Doe, 555-123-4567"
|
||||
help="The text that will be shown to users"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Button Field Configuration -->
|
||||
<div v-if="field.type === 'button'" class="space-y-3 p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||||
<div class="flex items-center mb-2">
|
||||
<Icon name="material-symbols:settings" class="w-4 h-4 text-blue-600 mr-2" />
|
||||
<span class="text-sm font-medium text-blue-800">Button Settings</span>
|
||||
</div>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Button Text"
|
||||
v-model="field.value"
|
||||
placeholder="e.g., View Profile, Download PDF"
|
||||
help="Text displayed on the button"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Button URL"
|
||||
v-model="field.url"
|
||||
placeholder="e.g., https://example.com/profile"
|
||||
help="Where the button should redirect to"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
label="Open in new tab"
|
||||
v-model="field.openInNewTab"
|
||||
help="Opens link in new window"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Icon (optional)"
|
||||
v-model="field.icon"
|
||||
placeholder="material-symbols:link"
|
||||
help="Icon name to display"
|
||||
:classes="{ outer: 'mb-0' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="empty-fields-state text-center py-8 border-2 border-dashed border-gray-300 rounded-lg">
|
||||
<Icon name="material-symbols:info-outline" class="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
||||
<h6 class="text-sm font-medium text-gray-600 mb-1">No information fields yet</h6>
|
||||
<p class="text-xs text-gray-500 mb-4">Add fields to display information to your users</p>
|
||||
<button
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
||||
@click="addInfoField"
|
||||
>
|
||||
<Icon name="material-symbols:add" class="w-4 h-4 mr-1" />
|
||||
Add Your First Field
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -2337,6 +2581,8 @@ if (this.element.querySelector('.file-upload')) {
|
||||
placeholder="e.g., required|email|length:3,50"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<!-- Validation Rules Guide -->
|
||||
<ValidationRulesHelp />
|
||||
</div>
|
||||
@ -2702,10 +2948,10 @@ const showField = (fieldName) => {
|
||||
if (!props.component) return false
|
||||
|
||||
const fieldConfig = {
|
||||
label: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'],
|
||||
name: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'hidden', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'],
|
||||
label: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml', 'info-display'],
|
||||
name: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'hidden', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml', 'info-display'],
|
||||
placeholder: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'select', 'searchSelect', 'dynamic-list'],
|
||||
help: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'hidden', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'],
|
||||
help: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'hidden', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml', 'info-display'],
|
||||
value: ['heading', 'paragraph', 'hidden'],
|
||||
width: ['text', 'textarea', 'number', 'email', 'password', 'url', 'tel', 'mask', 'select', 'searchSelect', 'checkbox', 'radio', 'switch', 'date', 'time', 'datetime-local', 'range', 'color', 'file', 'otp', 'dropzone', 'button', 'heading', 'paragraph', 'form-section', 'info-display', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml'],
|
||||
rows: ['textarea'],
|
||||
@ -2720,7 +2966,7 @@ const showField = (fieldName) => {
|
||||
const hasOptions = computed(() => showField('options'))
|
||||
const hasSpecificSettings = computed(() => {
|
||||
if (!props.component) return false
|
||||
const specificTypes = ['mask', 'otp', 'dropzone', 'range', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml']
|
||||
const specificTypes = ['mask', 'otp', 'dropzone', 'range', 'button', 'form-section', 'dynamic-list', 'repeating-table', 'repeating-group', 'customHtml', 'info-display']
|
||||
return specificTypes.includes(props.component.type)
|
||||
})
|
||||
|
||||
@ -3210,6 +3456,29 @@ const removeCondition = (index) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Info Display field management
|
||||
const addInfoField = () => {
|
||||
if (!configModel.value.fields) {
|
||||
configModel.value.fields = []
|
||||
}
|
||||
|
||||
configModel.value.fields.push({
|
||||
label: `Field ${configModel.value.fields.length + 1}`,
|
||||
value: 'Value',
|
||||
key: `field_${configModel.value.fields.length + 1}`,
|
||||
type: 'text', // text, button
|
||||
url: '', // for button type
|
||||
openInNewTab: false, // for button type
|
||||
icon: '' // for button type
|
||||
})
|
||||
}
|
||||
|
||||
const removeInfoField = (index) => {
|
||||
if (configModel.value.fields && configModel.value.fields.length > index) {
|
||||
configModel.value.fields.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const generateConditionalLogicCode = () => {
|
||||
if (!configModel.value.conditionalLogic || !configModel.value.conditionalLogic.conditions.length) {
|
||||
return '// No conditions defined'
|
||||
|
Loading…
x
Reference in New Issue
Block a user