Enhance Button Customization in Form Builder

- Updated ComponentPreview.vue to support custom button styles, allowing users to define background color, text color, border color, border width, border radius, and hover effects for buttons.
- Enhanced FormBuilderFieldSettingsModal.vue with new fields for customizing button appearance, including color pickers and number inputs for border properties.
- Implemented a color preview feature in the settings modal to visualize button styles based on user selections, improving usability and customization options.
- Added utility functions for generating custom button styles dynamically, ensuring consistent styling across the application.
This commit is contained in:
Afiq 2025-08-06 15:33:55 +08:00
parent 3abaf7afe5
commit 8a6f87ebf1
2 changed files with 291 additions and 17 deletions

View File

@ -336,26 +336,66 @@
{{ component.props.label }}
</label>
<!-- Link Button -->
<a v-if="component.props.linkType && component.props.linkType !== 'none' && getButtonLink()"
:href="getButtonLink()"
:target="component.props.linkTarget || '_self'"
class="inline-block">
<RsButton :type="component.props.buttonType || 'button'" :variant="component.props.variant || 'primary'"
:size="component.props.size || 'md'" :disabled="component.props.disabled || false"
<!-- Custom Button with Custom Colors -->
<div v-if="component.props.variant === 'custom'" class="inline-block">
<!-- Link Button with Custom Colors -->
<a v-if="component.props.linkType && component.props.linkType !== 'none' && getButtonLink()"
:href="getButtonLink()"
:target="component.props.linkTarget || '_self'"
class="inline-block">
<button
:type="component.props.buttonType || 'button'"
:disabled="component.props.disabled || false"
:class="getButtonSizeClass(component.props.size)"
:style="getCustomButtonStyles(component.props)"
:data-hover-effect="component.props.customHoverEffect || 'none'"
class="button-component custom-button"
@click="handleButtonClick"
>
<span v-if="component.props.showButtonText !== false">{{ component.props.buttonText || component.props.label || 'Button' }}</span>
<Icon v-if="component.props.icon" :name="component.props.icon" class="w-4 h-4" :class="{'ml-2': component.props.showButtonText !== false}" />
</button>
</a>
<!-- Regular Button with Custom Colors (no link) -->
<button
v-else
:type="component.props.buttonType || 'button'"
:disabled="component.props.disabled || false"
:class="getButtonSizeClass(component.props.size)"
:style="getCustomButtonStyles(component.props)"
:data-hover-effect="component.props.customHoverEffect || 'none'"
class="button-component custom-button"
@click="handleButtonClick"
>
<span v-if="component.props.showButtonText !== false">{{ component.props.buttonText || component.props.label || 'Button' }}</span>
<Icon v-if="component.props.icon" :name="component.props.icon" class="w-4 h-4" :class="{'ml-2': component.props.showButtonText !== false}" />
</button>
</div>
<!-- Standard Button (non-custom colors) -->
<div v-else class="inline-block">
<!-- Link Button -->
<a v-if="component.props.linkType && component.props.linkType !== 'none' && getButtonLink()"
:href="getButtonLink()"
:target="component.props.linkTarget || '_self'"
class="inline-block">
<RsButton :type="component.props.buttonType || 'button'" :variant="component.props.variant || 'primary'"
:size="component.props.size || 'md'" :disabled="component.props.disabled || false"
class="button-component">
<span v-if="component.props.showButtonText !== false">{{ component.props.buttonText || component.props.label || 'Button' }}</span>
<Icon v-if="component.props.icon" :name="component.props.icon" class="w-4 h-4" :class="{'ml-2': component.props.showButtonText !== false}" />
</RsButton>
</a>
<!-- Regular Button (no link) -->
<RsButton v-else :type="component.props.buttonType || 'button'" :variant="component.props.variant || 'primary'"
:size="component.props.size || 'md'" :disabled="component.props.disabled || false" @click="handleButtonClick"
class="button-component">
<span v-if="component.props.showButtonText !== false">{{ component.props.buttonText || component.props.label || 'Button' }}</span>
<Icon v-if="component.props.icon" :name="component.props.icon" class="w-4 h-4" :class="{'ml-2': component.props.showButtonText !== false}" />
</RsButton>
</a>
<!-- Regular Button (no link) -->
<RsButton v-else :type="component.props.buttonType || 'button'" :variant="component.props.variant || 'primary'"
:size="component.props.size || 'md'" :disabled="component.props.disabled || false" @click="handleButtonClick"
class="button-component">
<span v-if="component.props.showButtonText !== false">{{ component.props.buttonText || component.props.label || 'Button' }}</span>
<Icon v-if="component.props.icon" :name="component.props.icon" class="w-4 h-4" :class="{'ml-2': component.props.showButtonText !== false}" />
</RsButton>
</div>
<div v-if="component.props.help" class="mt-1 text-xs text-gray-500">
{{ component.props.help }}
@ -1382,6 +1422,61 @@ const getButtonLink = () => {
return null;
};
// Custom button styling functions
const getCustomButtonStyles = (props) => {
if (!props || props.variant !== 'custom') return {};
const styles = {
backgroundColor: props.customBackgroundColor || '#3b82f6',
color: props.customTextColor || '#ffffff',
border: 'none',
cursor: 'pointer',
transition: 'all 0.2s ease-in-out'
};
// Add border if specified
if (props.customBorderColor && props.customBorderWidth) {
styles.border = `${props.customBorderWidth}px solid ${props.customBorderColor}`;
}
// Add border radius
if (props.customBorderRadius) {
styles.borderRadius = `${props.customBorderRadius}px`;
}
// Add hover effects
const hoverEffect = props.customHoverEffect;
if (hoverEffect && hoverEffect !== 'none') {
switch (hoverEffect) {
case 'darken':
styles[':hover'] = { filter: 'brightness(0.9)' };
break;
case 'lighten':
styles[':hover'] = { filter: 'brightness(1.1)' };
break;
case 'scale':
styles[':hover'] = { transform: 'scale(1.05)' };
break;
case 'glow':
styles[':hover'] = {
boxShadow: `0 0 10px ${props.customBackgroundColor || '#3b82f6'}`
};
break;
}
}
return styles;
};
const getButtonSizeClass = (size) => {
const sizeClasses = {
'sm': 'px-3 py-1.5 text-sm',
'md': 'px-4 py-2 text-sm',
'lg': 'px-6 py-3 text-base'
};
return sizeClasses[size] || sizeClasses['md'];
};
</script>
<style scoped>
@ -1659,4 +1754,42 @@ const getButtonLink = () => {
background-color: #dbeafe !important;
border-color: #3b82f6 !important;
}
/* Custom button styles */
.custom-button {
font-weight: 500;
border-radius: 6px;
transition: all 0.2s ease-in-out;
}
.custom-button:hover {
transform: translateY(-1px);
}
.custom-button:active {
transform: translateY(0);
}
.custom-button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* Custom button hover effects */
.custom-button[data-hover-effect="darken"]:hover {
filter: brightness(0.9);
}
.custom-button[data-hover-effect="lighten"]:hover {
filter: brightness(1.1);
}
.custom-button[data-hover-effect="scale"]:hover {
transform: scale(1.05) translateY(-1px);
}
.custom-button[data-hover-effect="glow"]:hover {
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
}
</style>

View File

@ -503,13 +503,111 @@
{ label: 'Secondary (Gray)', value: 'secondary' },
{ label: 'Success (Green)', value: 'success' },
{ label: 'Danger (Red)', value: 'danger' },
{ label: 'Warning (Orange)', value: 'warning' }
{ label: 'Warning (Orange)', value: 'warning' },
{ label: 'Custom Color', value: 'custom' }
]"
help="Visual appearance of the button"
:classes="{ outer: 'field-wrapper' }"
/>
</div>
<!-- Custom Color Picker -->
<div v-if="configModel.variant === 'custom'" class="space-y-3">
<h5 class="text-sm font-medium text-gray-700 border-b pb-2">Custom Color Settings</h5>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormKit
type="color"
label="Background Color"
name="customBackgroundColor"
v-model="configModel.customBackgroundColor"
help="Choose the button background color"
:classes="{ outer: 'field-wrapper' }"
/>
<FormKit
type="color"
label="Text Color"
name="customTextColor"
v-model="configModel.customTextColor"
help="Choose the button text color"
:classes="{ outer: 'field-wrapper' }"
/>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormKit
type="color"
label="Border Color"
name="customBorderColor"
v-model="configModel.customBorderColor"
help="Choose the button border color"
:classes="{ outer: 'field-wrapper' }"
/>
<FormKit
type="number"
label="Border Width (px)"
name="customBorderWidth"
v-model="configModel.customBorderWidth"
help="Set the border width in pixels"
:classes="{ outer: 'field-wrapper' }"
:min="0"
:max="10"
placeholder="2"
/>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormKit
type="number"
label="Border Radius (px)"
name="customBorderRadius"
v-model="configModel.customBorderRadius"
help="Set the corner roundness in pixels"
:classes="{ outer: 'field-wrapper' }"
:min="0"
:max="50"
placeholder="6"
/>
<FormKit
type="select"
label="Hover Effect"
name="customHoverEffect"
v-model="configModel.customHoverEffect"
:options="[
{ label: 'None', value: 'none' },
{ label: 'Darken', value: 'darken' },
{ label: 'Lighten', value: 'lighten' },
{ label: 'Scale', value: 'scale' },
{ label: 'Glow', value: 'glow' }
]"
help="Choose hover animation effect"
:classes="{ outer: 'field-wrapper' }"
/>
</div>
<!-- Color Preview -->
<div class="mt-4 p-3 bg-gray-50 border border-gray-200 rounded-lg">
<h6 class="text-sm font-medium text-gray-700 mb-2">Color Preview</h6>
<div class="flex items-center space-x-3">
<button
class="px-4 py-2 rounded font-medium transition-all duration-200"
:style="getCustomButtonStyles()"
disabled
>
{{ configModel.buttonText || configModel.label || 'Button Preview' }}
</button>
<div class="text-xs text-gray-600">
<div>Background: {{ configModel.customBackgroundColor || '#3b82f6' }}</div>
<div>Text: {{ configModel.customTextColor || '#ffffff' }}</div>
<div>Border: {{ configModel.customBorderColor || 'transparent' }}</div>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<FormKit
type="select"
@ -2348,6 +2446,49 @@ const getIframeUrlPreview = () => {
return queryString ? `${baseUrl}?${queryString}` : baseUrl
}
// Generate custom button styles for preview
const getCustomButtonStyles = () => {
const styles = {
backgroundColor: configModel.value.customBackgroundColor || '#3b82f6',
color: configModel.value.customTextColor || '#ffffff',
border: 'none',
cursor: 'pointer'
}
// Add border if specified
if (configModel.value.customBorderColor && configModel.value.customBorderWidth) {
styles.border = `${configModel.value.customBorderWidth}px solid ${configModel.value.customBorderColor}`
}
// Add border radius
if (configModel.value.customBorderRadius) {
styles.borderRadius = `${configModel.value.customBorderRadius}px`
}
// Add hover effects
const hoverEffect = configModel.value.customHoverEffect
if (hoverEffect && hoverEffect !== 'none') {
switch (hoverEffect) {
case 'darken':
styles[':hover'] = { filter: 'brightness(0.9)' }
break
case 'lighten':
styles[':hover'] = { filter: 'brightness(1.1)' }
break
case 'scale':
styles[':hover'] = { transform: 'scale(1.05)' }
break
case 'glow':
styles[':hover'] = {
boxShadow: `0 0 10px ${configModel.value.customBackgroundColor || '#3b82f6'}`
}
break
}
}
return styles
}
// Type changing functionality
const compatibilityGroups = {
// Text-based inputs (can switch between each other)