feat: migrate form builder to shadcn/ui + Huge Icons

- Replace @wordpress/components with shadcn Input, Button, Switch, Select, Textarea, Label
- Replace @wordpress/icons with @hugeicons/core-free-icons and @hugeicons/react
- Update fieldTypes config with Huge Icons component references
- Migrate FormFieldOptions, FieldSettingsPanel, FormField, FormCanvas, FieldPalette
This commit is contained in:
dwindown
2026-04-19 13:44:10 +07:00
parent 99912a9335
commit c103e368be
8 changed files with 160 additions and 106 deletions

7
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "2.0.0",
"license": "GPL-2.0-or-later",
"dependencies": {
"@hugeicons/core-free-icons": "^4.1.1",
"@hugeicons/react": "^1.1.6",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
@@ -2413,6 +2414,12 @@
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@hugeicons/core-free-icons": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.1.1.tgz",
"integrity": "sha512-teqIBvPHl90ygIwKyJwTxOH8aNp1X1PjDTcMvLkEwdPxPD+8mssrZ5kXKIAJJFYPsz69a8LYQY0UPid4PAdavg==",
"license": "MIT"
},
"node_modules/@hugeicons/react": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@hugeicons/react/-/react-1.1.6.tgz",

View File

@@ -19,6 +19,7 @@
"tw-animate-css": "^1.4.0"
},
"dependencies": {
"@hugeicons/core-free-icons": "^4.1.1",
"@hugeicons/react": "^1.1.6",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",

View File

@@ -3,7 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { Icon } from '@wordpress/icons';
import { HugeiconsIcon } from '@hugeicons/react';
import { FIELD_CATEGORIES, getFieldTypesByCategory } from '../../config/fieldTypes';
import './FieldPalette.css';
@@ -37,7 +37,7 @@ export default function FieldPalette({ onDragStart }) {
onDragStart={(e) => handleDragStart(e, field.type)}
title={field.label}
>
<Icon icon={field.icon} />
<HugeiconsIcon icon={field.icon} size={20} />
<span>{ field.label }</span>
</div>
))}

View File

@@ -3,7 +3,11 @@
*/
import { __ } from '@wordpress/i18n';
import { TextControl, CheckboxControl, SelectControl, TextareaControl } from '@wordpress/components';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { FIELD_TYPES } from '../../config/fieldTypes';
import FormFieldOptions from './FormFieldOptions';
import './FieldSettingsPanel.css';
@@ -53,44 +57,54 @@ export default function FieldSettingsPanel({
</div>
<div className="formipay-settings-content">
<TextControl
label={ __('Label', 'formipay') }
<div className="formipay-settings-field">
<Label>{ __('Label', 'formipay') }</Label>
<Input
value={field.label || ''}
onChange={(value) => handleChange('label', value)}
help={ __('The display label for this field', 'formipay') }
onChange={(e) => handleChange('label', e.target.value)}
/>
<p className="formipay-settings-help">{ __('The display label for this field', 'formipay') }</p>
</div>
<TextControl
label={ __('Field ID', 'formipay') }
<div className="formipay-settings-field">
<Label>{ __('Field ID', 'formipay') }</Label>
<Input
value={field.field_id || ''}
onChange={(value) => handleChange('field_id', value)}
help={ __('Unique identifier for this field (used in form data)', 'formipay') }
onChange={(e) => handleChange('field_id', e.target.value)}
/>
<p className="formipay-settings-help">{ __('Unique identifier for this field (used in form data)', 'formipay') }</p>
</div>
{ hasPlaceholder && (
<TextControl
label={ __('Placeholder', 'formipay') }
<div className="formipay-settings-field">
<Label>{ __('Placeholder', 'formipay') }</Label>
<Input
value={field.placeholder || ''}
onChange={(value) => handleChange('placeholder', value)}
onChange={(e) => handleChange('placeholder', e.target.value)}
/>
</div>
) }
{ hasDefaultValue && (
<TextControl
label={ __('Default Value', 'formipay') }
<div className="formipay-settings-field">
<Label>{ __('Default Value', 'formipay') }</Label>
<Input
value={field.default_value || ''}
onChange={(value) => handleChange('default_value', value)}
onChange={(e) => handleChange('default_value', e.target.value)}
/>
</div>
) }
{ hasDescription && (
<TextareaControl
label={ __('Description', 'formipay') }
<div className="formipay-settings-field">
<Label>{ __('Description', 'formipay') }</Label>
<Textarea
value={field.description || ''}
onChange={(value) => handleChange('description', value)}
help={ __('Optional help text displayed below the field', 'formipay') }
onChange={(e) => handleChange('description', e.target.value)}
rows={3}
/>
<p className="formipay-settings-help">{ __('Optional help text displayed below the field', 'formipay') }</p>
</div>
) }
{ hasOptions && (
@@ -102,26 +116,36 @@ export default function FieldSettingsPanel({
) }
{ hasGridColumns && (
<SelectControl
label={ __('Option Grid Columns', 'formipay') }
value={field.option_grid_columns || 1}
options={[
{ label: __('1 Column', 'formipay'), value: 1 },
{ label: __('2 Columns', 'formipay'), value: 2 },
{ label: __('3 Columns', 'formipay'), value: 3 },
{ label: __('4 Columns', 'formipay'), value: 4 },
]}
onChange={(value) => handleChange('option_grid_columns', parseInt(value))}
/>
<div className="formipay-settings-field">
<Label>{ __('Option Grid Columns', 'formipay') }</Label>
<Select
value={String(field.option_grid_columns || 1)}
onValueChange={(value) => handleChange('option_grid_columns', parseInt(value))}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">{__('1 Column', 'formipay')}</SelectItem>
<SelectItem value="2">{__('2 Columns', 'formipay')}</SelectItem>
<SelectItem value="3">{__('3 Columns', 'formipay')}</SelectItem>
<SelectItem value="4">{__('4 Columns', 'formipay')}</SelectItem>
</SelectContent>
</Select>
</div>
) }
{ isRequired && (
<CheckboxControl
label={ __('Required Field', 'formipay') }
<div className="formipay-settings-field formipay-settings-switch">
<div className="formipay-switch-row">
<Label>{ __('Required Field', 'formipay') }</Label>
<Switch
checked={field.is_required || false}
onChange={(value) => handleChange('is_required', value)}
help={ __('User must fill this field before submitting', 'formipay') }
onCheckedChange={(value) => handleChange('is_required', value)}
/>
</div>
<p className="formipay-settings-help">{ __('User must fill this field before submitting', 'formipay') }</p>
</div>
) }
</div>
</div>

View File

@@ -3,8 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { Icon } from '@wordpress/icons';
import plus from '@wordpress/icons/build/plus';
import { Add01Icon } from '@hugeicons/react';
import { DEFAULT_FIELD_CONFIG, generateFieldId } from '../../config/fieldTypes';
import FormField from './FormField';
import './FormCanvas.css';
@@ -61,7 +60,7 @@ export default function FormCanvas({
>
{fields.length === 0 ? (
<div className="formipay-empty-state">
<Icon icon={plus()} size={48} />
<Add01Icon size={48} />
<p>
{ __('Drag fields from the palette to build your form', 'formipay') }
</p>

View File

@@ -3,10 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { Icon } from '@wordpress/icons';
import chevronUp from '@wordpress/icons/build/chevron-up';
import chevronDown from '@wordpress/icons/build/chevron-down';
import trash from '@wordpress/icons/build/trash';
import { ArrowUp01Icon, ArrowDown01Icon, Delete02Icon } from '@hugeicons/react';
import { FIELD_TYPES } from '../../config/fieldTypes';
import './FormField.css';
@@ -52,7 +49,7 @@ export default function FormField({
disabled={!onMoveUp}
title={ __('Move Up', 'formipay') }
>
<Icon icon={chevronUp()} size={16} />
<ArrowUp01Icon size={16} />
</button>
<button
type="button"
@@ -61,7 +58,7 @@ export default function FormField({
disabled={!onMoveDown}
title={ __('Move Down', 'formipay') }
>
<Icon icon={chevronDown()} size={16} />
<ArrowDown01Icon size={16} />
</button>
<button
type="button"
@@ -69,7 +66,7 @@ export default function FormField({
onClick={(e) => { e.stopPropagation(); onDelete?.(); }}
title={ __('Delete', 'formipay') }
>
<Icon icon={trash()} size={16} />
<Delete02Icon size={16} />
</button>
</div>
</div>

View File

@@ -3,10 +3,10 @@
*/
import { __ } from '@wordpress/i18n';
import { TextControl, Button } from '@wordpress/components';
import { Icon as WPIcon } from '@wordpress/icons';
import plus from '@wordpress/icons/build/plus';
import trash from '@wordpress/icons/build/trash';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Add01Icon, Delete02Icon } from '@hugeicons/react';
export default function FormFieldOptions({ options = [], onChange, fieldType }) {
const handleAddOption = () => {
@@ -46,10 +46,10 @@ export default function FormFieldOptions({ options = [], onChange, fieldType })
</label>
<Button
variant="secondary"
size="compact"
size="sm"
onClick={handleAddOption}
icon={<WPIcon icon={plus()} size={16} />}
>
<Add01Icon size={16} />
{ __('Add Option', 'formipay') }
</Button>
</div>
@@ -58,38 +58,45 @@ export default function FormFieldOptions({ options = [], onChange, fieldType })
{options.map((option, index) => (
<div key={index} className="formipay-option-item">
<div className="formipay-option-fields">
<TextControl
label={__('Label', 'formipay')}
<div className="formipay-option-field">
<Label>{__('Label', 'formipay')}</Label>
<Input
value={option.label || ''}
onChange={(value) => handleUpdateOption(index, { label: value })}
onChange={(e) => handleUpdateOption(index, { label: e.target.value })}
placeholder={__('Option label', 'formipay')}
/>
</div>
<TextControl
label={__('Value', 'formipay')}
<div className="formipay-option-field">
<Label>{__('Value', 'formipay')}</Label>
<Input
value={option.value || ''}
onChange={(value) => handleUpdateOption(index, { value: value })}
onChange={(e) => handleUpdateOption(index, { value: e.target.value })}
placeholder={__('Optional custom value', 'formipay')}
/>
</div>
<TextControl
label={__('Amount', 'formipay')}
<div className="formipay-option-field">
<Label>{__('Amount', 'formipay')}</Label>
<Input
type="number"
value={option.amount || ''}
onChange={(value) => handleUpdateOption(index, { amount: value })}
onChange={(e) => handleUpdateOption(index, { amount: e.target.value })}
placeholder={__('0', 'formipay')}
step="0.01"
/>
</div>
</div>
<div className="formipay-option-actions">
<Button
variant="tertiary"
size="compact"
variant="ghost"
size="icon"
onClick={() => handleDeleteOption(index)}
icon={<WPIcon icon={trash()} size={16} />}
label={__('Delete Option', 'formipay')}
/>
title={__('Delete Option', 'formipay')}
>
<Delete02Icon size={16} />
</Button>
</div>
</div>
))}

View File

@@ -2,23 +2,42 @@
* Form Field Type Definitions
*/
import {
TextFontIcon,
Link01Icon,
Mail01Icon,
TelephoneIcon,
HashtagIcon,
Calendar01Icon,
Calendar03Icon,
PaintBoardIcon,
DropdownFieldTypeIcon,
CheckmarkSquare01Icon,
RadioButtonIcon,
EyeIcon,
ParagraphIcon,
MinusSignIcon,
NextIcon,
Globe02Icon,
} from '@hugeicons/core-free-icons';
export const FIELD_TYPES = {
text: { label: 'Text', icon: 'text', category: 'input' },
url: { label: 'URL', icon: 'link', category: 'input' },
email: { label: 'Email', icon: 'email', category: 'input' },
tel: { label: 'Telephone', icon: 'phone', category: 'input' },
number: { label: 'Number', icon: 'number', category: 'input' },
date: { label: 'Date', icon: 'calendar', category: 'input' },
datetime: { label: 'Date & Time', icon: 'calendar-alt', category: 'input' },
color: { label: 'Color', icon: 'art', category: 'input' },
select: { label: 'Select Dropdown', icon: 'list-view', category: 'choice' },
checkbox: { label: 'Checkbox', icon: 'checkbox', category: 'choice' },
radio: { label: 'Radio', icon: 'radio', category: 'choice' },
hidden: { label: 'Hidden', icon: 'hidden', category: 'advanced' },
textarea: { label: 'Textarea', icon: 'document', category: 'input' },
divider: { label: 'Divider', icon: 'minus', category: 'layout' },
page_break: { label: 'Page Break', icon: 'page-break', category: 'layout' },
country_list: { label: 'Preset: Country List', icon: 'globe', category: 'preset' },
text: { label: 'Text', icon: TextFontIcon, category: 'input' },
url: { label: 'URL', icon: Link01Icon, category: 'input' },
email: { label: 'Email', icon: Mail01Icon, category: 'input' },
tel: { label: 'Telephone', icon: TelephoneIcon, category: 'input' },
number: { label: 'Number', icon: HashtagIcon, category: 'input' },
date: { label: 'Date', icon: Calendar01Icon, category: 'input' },
datetime: { label: 'Date & Time', icon: Calendar03Icon, category: 'input' },
color: { label: 'Color', icon: PaintBoardIcon, category: 'input' },
select: { label: 'Select Dropdown', icon: DropdownFieldTypeIcon, category: 'choice' },
checkbox: { label: 'Checkbox', icon: CheckmarkSquare01Icon, category: 'choice' },
radio: { label: 'Radio', icon: RadioButtonIcon, category: 'choice' },
hidden: { label: 'Hidden', icon: EyeIcon, category: 'advanced' },
textarea: { label: 'Textarea', icon: ParagraphIcon, category: 'input' },
divider: { label: 'Divider', icon: MinusSignIcon, category: 'layout' },
page_break: { label: 'Page Break', icon: NextIcon, category: 'layout' },
country_list: { label: 'Preset: Country List', icon: Globe02Icon, category: 'preset' },
};
export const FIELD_CATEGORIES = {