Merge pull request #12322 from ToolJet/feat/resizable-slots-form

feat: Makes header and footer resizable
This commit is contained in:
Johnson Cherian 2025-04-25 11:40:56 +05:30 committed by GitHub
commit 23ca209a16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 818 additions and 752 deletions

View file

@ -22,6 +22,8 @@ import {
handleActivateTargets,
handleDeactivateTargets,
handleActivateNonDraggingComponents,
computeScrollDelta,
computeScrollDeltaOnDrag,
} from './gridUtils';
import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd';
import useStore from '@/AppBuilder/_stores/store';
@ -56,6 +58,7 @@ export default function Grid({ gridWidth, currentLayout }) {
const canvasWidth = NO_OF_GRIDS * gridWidth;
const getHoveredComponentForGrid = useStore((state) => state.getHoveredComponentForGrid, shallow);
const getResolvedComponent = useStore((state) => state.getResolvedComponent, shallow);
const updateContainerAutoHeight = useStore((state) => state.updateContainerAutoHeight, shallow);
const [canvasBounds, setCanvasBounds] = useState(CANVAS_BOUNDS);
const draggingComponentId = useStore((state) => state.draggingComponentId, shallow);
const resizingComponentId = useGridStore((state) => state.resizingComponentId, shallow);
@ -345,6 +348,7 @@ export default function Grid({ gridWidth, currentLayout }) {
const handleDragEnd = useCallback(
(boxPositions) => {
let newParent = null;
let oldParent = null;
const updatedLayouts = boxPositions.reduce((layouts, { id, x, y, parent }) => {
const currentWidget = boxList.find((box) => box.id === id);
const containerWidth = parent ? useGridStore.getState().subContainerWidths[parent] : gridWidth;
@ -389,7 +393,7 @@ export default function Grid({ gridWidth, currentLayout }) {
}
}
newParent = parent ? parent : null;
oldParent = currentWidget.component?.parent;
layouts[id] = {
width: _width,
height: _height,
@ -400,6 +404,11 @@ export default function Grid({ gridWidth, currentLayout }) {
return layouts;
}, {});
setComponentLayout(updatedLayouts, newParent, undefined, { updateParent: true });
// const currentWidget = boxList.find((box) => box.id === id);
updateContainerAutoHeight(newParent);
updateContainerAutoHeight(oldParent);
toggleCanvasUpdater();
},
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -870,20 +879,19 @@ export default function Grid({ gridWidth, currentLayout }) {
const targetSlotId = target?.slotId;
const targetGridWidth = useGridStore.getState().subContainerWidths[targetSlotId] || gridWidth;
// const restrictedWidgets = RESTRICTED_WIDGETS_CONFIG?.[source.widgetType] || [];
// const draggedWidgetType = dragged.widgetType;
const isParentChangeAllowed = dragContext.isDroppable;
// Compute new position
let { left, top } = getAdjustedDropPosition(e, target, isParentChangeAllowed, targetGridWidth, dragged);
const isModalToCanvas = source.isModal && target.slotId === 'real-canvas';
let scrollDelta = computeScrollDelta({ source });
if (isParentChangeAllowed && !isModalToCanvas) {
const parent = target.slotId === 'real-canvas' ? null : target.slotId;
// Special case for Modal; If source widget is modal, prevent drops to canvas
handleDragEnd([{ id: e.target.id, x: left, y: top, parent }]);
const parent = target.slotId === 'real-canvas' ? null : target.slotId;
handleDragEnd([{ id: e.target.id, x: left, y: top + scrollDelta, parent }]);
} else {
const sourcegridWidth = useGridStore.getState().subContainerWidths[source.slotId] || gridWidth;
@ -892,9 +900,8 @@ export default function Grid({ gridWidth, currentLayout }) {
!isModalToCanvas ??
toast.error(`${dragged.widgetType} is not compatible as a child component of ${target.widgetType}`);
}
// Apply transform for smooth transition
e.target.style.transform = `translate(${left}px, ${top}px)`;
e.target.style.transform = `translate(${left}px, ${top + scrollDelta}px)`;
// Force reordering of conatiner if the parent has not changed
const newParentId = target.slotId === 'real-canvas' ? 'canvas' : target.slotId;
@ -962,12 +969,6 @@ export default function Grid({ gridWidth, currentLayout }) {
setCanvasBounds({ ...relativePosition });
}
e.target.style.transform = `translate(${left}px, ${top}px)`;
e.target.setAttribute(
'widget-pos2',
`translate: ${e.translate[0]} | Round: ${Math.round(e.translate[0] / gridWidth) * gridWidth} | ${gridWidth}`
);
// This block is to show grid lines on the canvas when the dragged element is over a new canvas
if (document.elementFromPoint(e.clientX, e.clientY)) {
const targetElems = document.elementsFromPoint(e.clientX, e.clientY);
@ -995,6 +996,17 @@ export default function Grid({ gridWidth, currentLayout }) {
handleActivateTargets(newParentId);
}
}
// Build the drag context from the event
const source = { slotId: oldParentId };
let scrollDelta = computeScrollDeltaOnDrag({ source });
e.target.style.transform = `translate(${left}px, ${top - scrollDelta}px)`;
e.target.setAttribute(
'widget-pos2',
`translate: ${e.translate[0]} | Round: ${Math.round(e.translate[0] / gridWidth) * gridWidth} | ${gridWidth}`
);
// Postion ghost element exactly as same at dragged element
if (document.getElementById(`moveable-drag-ghost`)) {
document.getElementById(`moveable-drag-ghost`).style.transform = `translate(${left}px, ${top}px)`;
@ -1084,6 +1096,7 @@ export default function Grid({ gridWidth, currentLayout }) {
}
}}
snapGridAll={true}
scrollable={true}
/>
</>
);

View file

@ -415,6 +415,20 @@ export function hideGridLines() {
document.getElementById('real-canvas')?.classList.add('hide-grid');
}
export function showGridLinesOnSlot(slotId) {
var canvasElm = document.getElementById(`canvas-${slotId}`);
canvasElm.classList.remove('hide-grid');
canvasElm.classList.add('show-grid');
}
export function hideGridLinesOnSlot(slotId) {
var canvasElm = document.getElementById(`canvas-${slotId}`);
canvasElm.classList.remove('show-grid');
canvasElm.classList.add('hide-grid');
}
// Track previously active elements for efficient cleanup
let previousActiveWidgets = null;
let previousActiveCanvas = null;
@ -488,3 +502,18 @@ export const handleDeactivateTargets = () => {
component.classList.remove('non-dragging-component');
});
};
export const computeScrollDelta = ({ source }) => {
// Only need to calculate scroll delta when moving from a sub-container
if (source.slotId !== 'real-canvas') {
const subContainerWrap = document
.querySelector(`#canvas-${source.slotId}`)
?.closest('.sub-container-overflow-wrap');
return subContainerWrap?.scrollTop || 0;
}
// Default case: No scroll adjustment needed
return 0;
};
export const computeScrollDeltaOnDrag = computeScrollDelta;

View file

@ -175,7 +175,6 @@ export class DragContext {
const restrictedWidgets = [...restrictedWidgetsOnTarget, ...restrictedWidgetsOnTargetSlot];
return !restrictedWidgets.includes(dragged.widgetType);
ß;
}
}

View file

@ -33,6 +33,7 @@ const SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY = [
'Divider',
'VerticalDivider',
'Link',
'Form',
];
const RenderWidget = ({

View file

@ -182,25 +182,6 @@ export const baseComponentProperties = (
});
}
items.push({
title: `${i18next.t('widget.common.general', 'General')}`,
isOpen: true,
children: (
<>
{renderElement(
component,
componentMeta,
layoutPropertyChanged,
dataQueries,
'tooltip',
'general',
currentState,
allComponents
)}
</>
),
});
items.push({
title: `${i18next.t('widget.common.devices', 'Devices')}`,
isOpen: true,

View file

@ -3,7 +3,7 @@ export const containerConfig = {
displayName: 'Container',
description: 'Group components',
defaultSize: {
width: 5,
width: 10,
height: 200,
},
component: 'Container',
@ -47,10 +47,16 @@ export const containerConfig = {
defaultValue: true,
},
},
headerHeight: {
type: 'numberInput',
displayName: 'Header height',
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
},
defaultChildren: [
{
componentName: 'Text',
slotName: 'header',
layout: {
top: 20,
left: 1,
@ -98,15 +104,6 @@ export const containerConfig = {
},
accordian: 'container',
},
headerHeight: {
type: 'numberInput',
displayName: 'Height',
validation: {
schema: { type: 'number' },
defaultValue: 80,
},
accordian: 'header',
},
borderRadius: {
type: 'numberInput',
displayName: 'Border',
@ -158,6 +155,7 @@ export const containerConfig = {
loadingState: { value: `{{false}}` },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
headerHeight: { value: `{{80}}` },
},
events: [],
styles: {

View file

@ -4,7 +4,7 @@ export const formConfig = {
description: 'Wrapper for multiple components',
defaultSize: {
width: 13,
height: 480,
height: 450,
},
defaultChildren: [
{
@ -19,8 +19,8 @@ export const formConfig = {
accessorKey: 'text',
styles: ['fontWeight', 'textSize', 'textColor'],
defaultValue: {
text: 'Form title',
textSize: 20,
text: 'Form',
textSize: 16,
textColor: '#000',
},
},
@ -34,203 +34,68 @@ export const formConfig = {
},
properties: ['text'],
defaultValue: {
text: 'Button2',
text: 'Submit',
padding: 'none',
},
},
{
componentName: 'Text',
layout: {
top: 40,
left: 10,
height: 30,
width: 17,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'User Details',
fontWeight: 'bold',
textSize: 18,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'Text',
layout: {
top: 90,
left: 10,
height: 30,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'Name',
fontWeight: 'normal',
textSize: 14,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'Text',
layout: {
top: 160,
left: 10,
height: 30,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'Age',
fontWeight: 'normal',
textSize: 14,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'TextInput',
layout: {
top: 120,
left: 10,
height: 30,
width: 25,
top: 20,
left: 5,
height: 40,
width: 31,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
placeholder: 'Enter your name',
label: '',
label: 'Name',
width: '{{60}}',
direction: 'left',
alignment: 'side',
auto: '{{false}}',
padding: 'default',
},
},
{
componentName: 'NumberInput',
layout: {
top: 190,
left: 10,
height: 30,
width: 25,
top: 80,
left: 5,
height: 40,
width: 31,
},
properties: ['value', 'label'],
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
value: 24,
label: '',
placeholder: 'Age',
label: 'Age',
width: '{{60}}',
direction: 'left',
alignment: 'side',
auto: '{{false}}',
padding: 'default',
},
},
{
componentName: 'Button',
componentName: 'TextInput',
layout: {
top: 240,
left: 10,
height: 30,
width: 10,
top: 140,
left: 5,
height: 40,
width: 31,
},
properties: ['text'],
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
text: 'Submit',
placeholder: 'Tomy',
label: 'Pet name',
width: '{{60}}',
alignment: 'side',
direction: 'left',
auto: '{{false}}',
padding: 'default',
},
},
],
@ -276,6 +141,24 @@ export const formConfig = {
},
showHeader: { type: 'toggle', displayName: 'Header' },
showFooter: { type: 'toggle', displayName: 'Footer' },
headerHeight: {
type: 'numberInput',
displayName: 'Header height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
canvasHeight: {
type: 'numberInput',
displayName: 'Canvas height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
footerHeight: {
type: 'numberInput',
displayName: 'Footer height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
visibility: {
type: 'toggle',
displayName: 'Visibility',
@ -294,6 +177,13 @@ export const formConfig = {
defaultValue: false,
},
},
tooltip: {
type: 'code',
displayName: 'Tooltip',
validation: { schema: { type: 'string' } },
section: 'additionalActions',
placeholder: 'Enter tooltip text',
},
},
events: {
onSubmit: { displayName: 'On submit' },
@ -316,22 +206,6 @@ export const formConfig = {
defaultValue: '#ffffffff',
},
},
headerHeight: {
type: 'code',
displayName: 'Header height',
validation: {
schema: { type: 'string' },
defaultValue: '80px',
},
},
footerHeight: {
type: 'code',
displayName: 'Footer height',
validation: {
schema: { type: 'string' },
defaultValue: '80px',
},
},
backgroundColor: {
type: 'colorSwatches',
displayName: 'Background color',
@ -403,18 +277,18 @@ export const formConfig = {
value:
"{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}",
},
showHeader: { value: '{{false}}' },
showFooter: { value: '{{false}}' },
showHeader: { value: '{{true}}' },
showFooter: { value: '{{true}}' },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
headerHeight: { value: 60 },
footerHeight: { value: 60 },
},
events: [],
styles: {
backgroundColor: { value: '#fff' },
borderRadius: { value: '0' },
borderColor: { value: '#fff' },
headerHeight: { value: '60px' },
footerHeight: { value: '60px' },
},
},
};

View file

@ -33,7 +33,8 @@ export const Container = ({
shallow
);
const { borderRadius, borderColor, boxShadow, headerHeight = 80 } = styles;
const { borderRadius, borderColor, boxShadow } = styles;
const { headerHeight = 80 } = properties;
const contentBgColor = useMemo(() => {
return {
backgroundColor:

View file

@ -0,0 +1,88 @@
import React, { useEffect } from 'react';
import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container';
import { showGridLinesOnSlot, hideGridLinesOnSlot } from '@/AppBuilder/AppCanvas/Grid/gridUtils';
import { useResizable } from '@/AppBuilder/_hooks/useMoveable';
export const HorizontalSlot = React.memo(
({
id,
height = 0,
width,
darkMode,
isDisabled,
isActive,
slotName = 'header', // 'header' or 'footer'
slotStyle = {},
onResize,
isEditing,
maxHeight,
}) => {
const parsedHeight = parseInt(height, 10);
const { getRootProps, getHandleProps, getResizeState } = useResizable({
initialHeight: parsedHeight,
initialWidth: '100%', // Now respects parent's width
minHeight: 10,
maxHeight: maxHeight || 400,
maxWidth: '100%',
stepHeight: 10, // Height will change in steps of 10px
onResize: () => {},
onDragEnd: (values) => {
onResize(values);
},
isReverseVerticalDrag: slotName === 'footer', // Reverse dragging for Footer
});
const { height: resizedHeight, isDragging } = getResizeState();
useEffect(() => {
if (isDragging) {
showGridLinesOnSlot(id);
} else {
hideGridLinesOnSlot(id);
}
}, [isDragging, id]);
const canvasHeight = parseInt(resizedHeight, 10) / 10;
const resizeStyle = {
backgroundColor: darkMode ? '#1F2837' : '#fff',
};
return (
<div className={`jet-form-${slotName} wj-form-${slotName}`} style={slotStyle}>
<div
className={`resizable-slot only-${slotName} ${isActive ? 'active' : ''} ${isEditing && 'is-editing'} ${
isDragging ? 'dragging' : ''
}`}
{...getRootProps()}
>
<SubContainer
id={id}
canvasHeight={canvasHeight}
canvasWidth={width}
allowContainerSelect={false}
darkMode={darkMode}
styles={{
margin: 0,
backgroundColor: 'transparent',
overflow: 'hidden',
}}
componentType="Form"
/>
{isEditing && <div className="resize-handle" {...getHandleProps()} style={resizeStyle} />}
</div>
{isDisabled && (
<div
id={`${id}-disabled`}
className="tj-form-disabled-overlay"
style={{ height: resizedHeight || '100%' }}
onClick={() => {}}
onDrop={(e) => e.stopPropagation()}
/>
)}
</div>
);
}
);

View file

@ -2,7 +2,7 @@ import React, { useRef, useState, useEffect } from 'react';
import { Container as SubContainer } from '@/AppBuilder/AppCanvas/Container';
// eslint-disable-next-line import/no-unresolved
import _, { debounce, omit } from 'lodash';
import { generateUIComponents } from './FormUtils';
import { generateUIComponents, getBodyHeight } from './FormUtils';
import { useMounted } from '@/_hooks/use-mount';
import { onComponentClick, removeFunctionObjects } from '@/_helpers/appUtils';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
@ -14,12 +14,10 @@ import {
CONTAINER_FORM_CANVAS_PADDING,
SUBCONTAINER_CANVAS_BORDER_WIDTH,
} from '@/AppBuilder/AppCanvas/appCanvasConstants';
import './form.scss';
import { HorizontalSlot } from './Components/HorizontalSlot';
import { useActiveSlot } from '@/AppBuilder/_hooks/useActiveSlot';
const getCanvasHeight = (height) => {
const parsedHeight = height.includes('px') ? parseInt(height, 10) : height;
return Math.ceil(parsedHeight);
};
import './form.scss';
export const Form = function Form(props) {
const {
@ -35,26 +33,19 @@ export const Form = function Form(props) {
properties,
resetComponent = () => {},
dataCy,
onComponentClick,
} = props;
const childComponents = useStore((state) => state.getChildComponents(id), shallow);
const {
borderRadius,
borderColor,
boxShadow,
headerHeight,
footerHeight,
footerBackgroundColor,
headerBackgroundColor,
} = styles;
const { borderRadius, borderColor, boxShadow, footerBackgroundColor, headerBackgroundColor } = styles;
const {
buttonToSubmit,
loadingState,
advanced,
JSONSchema,
showHeader = false,
showFooter = false,
visibility,
disabledState,
headerHeight = 80,
footerHeight = 80,
canvasHeight,
} = properties;
const { isDisabled, isVisible, isLoading } = useExposeState(
properties.loadingState,
@ -65,6 +56,10 @@ export const Form = function Form(props) {
);
const backgroundColor =
['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor;
const computedFormBodyHeight = getBodyHeight(height, showHeader, showFooter, headerHeight, footerHeight);
const computedBorderRadius = `${borderRadius ? parseFloat(borderRadius) : 0}px`;
const computedStyles = {
backgroundColor,
borderRadius: borderRadius ? parseFloat(borderRadius) : 0,
@ -74,16 +69,7 @@ export const Form = function Form(props) {
position: 'relative',
boxShadow,
flexDirection: 'column',
};
const formHeader = {
flexShrink: 0,
paddingBottom: '3px',
paddingTop: '7px',
paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`,
paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`,
backgroundColor:
['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor,
clipPath: `inset(0 round ${computedBorderRadius})`,
};
const formContent = {
@ -96,13 +82,6 @@ export const Form = function Form(props) {
paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`,
};
const formFooter = {
flexShrink: 0,
padding: `${CONTAINER_FORM_CANVAS_PADDING}px`,
backgroundColor:
['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor,
};
const parentRef = useRef(null);
const childDataRef = useRef({});
@ -110,8 +89,6 @@ export const Form = function Form(props) {
const [isValid, setValidation] = useState(true);
const [uiComponents, setUIComponents] = useState([]);
const mounted = useMounted();
const canvasHeaderHeight = getCanvasHeight(headerHeight) / 10;
const canvasFooterHeight = getCanvasHeight(footerHeight) / 10;
useEffect(() => {
const exposedVariables = {
@ -287,9 +264,61 @@ export const Form = function Form(props) {
setChildrenData(childDataRef.current);
};
const mode = useStore((state) => state.currentMode, shallow);
const isEditing = mode === 'edit';
const activeSlot = useActiveSlot(isEditing ? id : null); // Track the active slot for this widget
const setComponentProperty = useStore((state) => state.setComponentProperty, shallow);
// const updateContainerAutoHeight = useStore((state) => state.updateContainerAutoHeight);
const updateHeaderSizeInStore = ({ newHeight }) => {
const _height = parseInt(newHeight, 10);
setComponentProperty(id, `headerHeight`, _height, 'properties', 'value', false);
};
const updateFooterSizeInStore = ({ newHeight }) => {
const _height = parseInt(newHeight, 10);
setComponentProperty(id, `footerHeight`, _height, 'properties', 'value', false);
};
const [canHeight, setCanHeight] = useState('100%');
useEffect(() => {
// const newHeight = parseInt(height, 10) - 14;
// const autoCanvasHeight = document.querySelector(`#canvas-${id}`)?.scrollHeight;
const wrapHeight = parseInt(computedFormBodyHeight, 10);
// Set height to the larger value between computed body height and canvas scroll height
const maxHeight = Math.max(wrapHeight, canvasHeight || 10);
const roundedHeight = Math.round(maxHeight / 10) * 10;
setCanHeight(`${roundedHeight}px`);
}, [computedFormBodyHeight, canvasHeight]);
const headerMaxHeight = parseInt(height, 10) - parseInt(footerHeight, 10) - 100 - 10;
const footerMaxHeight = parseInt(height, 10) - parseInt(headerHeight, 10) - 100 - 10;
const formFooter = {
flexShrink: 0,
paddingTop: '3px',
paddingBottom: '7px',
paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`,
paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`,
maxHeight: `${footerMaxHeight}px`,
backgroundColor:
['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor,
};
const formHeader = {
flexShrink: 0,
paddingBottom: '3px',
paddingTop: '7px',
paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`,
paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`,
maxHeight: `${headerMaxHeight}px`,
backgroundColor:
['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor,
};
return (
<form
className={`jet-container ${advanced && 'jet-container-json-form'}`}
className={`jet-container jet-form-widget ${advanced && 'jet-container-json-form'}`}
id={id}
data-cy={dataCy}
ref={parentRef}
@ -300,31 +329,21 @@ export const Form = function Form(props) {
}} //Hack, should find a better solution - to prevent losing z index+1 when container element is clicked
>
{!advanced && showHeader && (
<div style={formHeader} className="wj-form-header">
<SubContainer
id={`${id}-header`}
canvasHeight={canvasHeaderHeight}
canvasWidth={width}
allowContainerSelect={false}
darkMode={darkMode}
styles={{
backgroundColor: 'transparent',
height: headerHeight,
}}
componentType="Form"
/>
{isDisabled && (
<div
id={`${id}-header-disabled`}
className="tj-form-disabled-overlay"
style={{ height: headerHeight || '100%' }}
onClick={() => {}}
onDrop={(e) => e.stopPropagation()}
/>
)}
</div>
<HorizontalSlot
slotName="header"
slotStyle={formHeader}
isEditing={isEditing}
id={`${id}-header`}
height={headerHeight}
width={width}
darkMode={darkMode}
isDisabled={isDisabled}
isActive={activeSlot === `${id}-header`}
onResize={updateHeaderSizeInStore}
/>
)}
<div className="jet-form-body" style={formContent}>
<div className="jet-form-body sub-container-overflow-wrap" style={formContent}>
{isLoading ? (
<div className="p-2 tw-flex tw-items-center tw-justify-center" style={{ margin: '0px auto' }}>
<div className="spinner-border" role="status"></div>
@ -332,14 +351,17 @@ export const Form = function Form(props) {
) : (
<fieldset disabled={isDisabled} style={{ width: '100%' }}>
{!advanced && (
<div className={'json-form-wrapper-disabled'} style={{ width: '100%', height: '100%' }}>
<div className={'json-form-wrapper-disabled'} style={{ width: '100%', height: canHeight || '100%' }}>
<SubContainer
id={id}
canvasHeight={computedStyles.height}
canvasHeight={parseInt(computedFormBodyHeight, 10)}
canvasWidth={width}
onOptionChange={onOptionChange}
onOptionsChange={onOptionsChange}
styles={{ backgroundColor: computedStyles.backgroundColor }}
styles={{
backgroundColor: computedStyles.backgroundColor,
height: canHeight,
}}
darkMode={darkMode}
componentType="Form"
/>
@ -382,30 +404,18 @@ export const Form = function Form(props) {
)}
</div>
{!advanced && showFooter && (
<div className="jet-form-footer wj-form-footer" style={formFooter}>
<SubContainer
id={`${id}-footer`}
canvasHeight={canvasFooterHeight}
canvasWidth={width}
allowContainerSelect={false}
darkMode={darkMode}
styles={{
margin: 0,
backgroundColor: 'transparent',
height: footerHeight,
}}
componentType="Form"
/>
{isDisabled && (
<div
id={`${id}-footer-disabled`}
className="tj-form-disabled-overlay"
style={{ height: footerHeight || '100%' }}
onClick={() => {}}
onDrop={(e) => e.stopPropagation()}
/>
)}
</div>
<HorizontalSlot
slotName="footer"
slotStyle={formFooter}
isEditing={isEditing}
id={`${id}-footer`}
height={footerHeight}
width={width}
darkMode={darkMode}
isDisabled={isDisabled}
onResize={updateFooterSizeInStore}
isActive={activeSlot === `${id}-footer`}
/>
)}
</form>
);

View file

@ -533,3 +533,22 @@ const validBooleanChecker = (input) => {
if (/^(true|false)$/i.test(input) == true) return JSON.parse(input);
return true;
};
export const getBodyHeight = (height, showHeader, showFooter, headerHeight = 60, footerHeight = 60) => {
let modalHeight = height ? parseInt(height, 10) : 0;
let parsedHeaderHeight = showHeader ? parseInt(headerHeight, 10) : 0;
let parsedFooterHeight = showFooter ? parseInt(footerHeight, 10) : 0;
if (showHeader) {
// 10 is header padding
modalHeight = modalHeight - parsedHeaderHeight - 10;
}
if (showFooter) {
// 14 is footer padding
modalHeight = modalHeight - parsedFooterHeight - 14;
}
const rounded = Math.ceil(modalHeight / 10) * 10;
return `${Math.max(rounded - 20, 40)}px`;
};

View file

@ -1,11 +1,17 @@
.jet-form-widget {
display: flex;
flex-direction: column;
height: 100%;
}
.wj-form-header {
position: relative;
&::after {
content: "";
position: absolute;
bottom: 0;
left: -7px;
right: -7px;
left: -2px;
right: -2px;
height: 1px;
background-color: var(--border-weak);
}
@ -17,8 +23,8 @@
content: "";
position: absolute;
top: 0;
left: -7px;
right: -7px;
left: -2px;
right: -2px;
height: 1px;
background-color: var(--border-weak);
}
@ -39,6 +45,67 @@
padding: 4px 0;
}
.resizable-slot {
position: relative;
height: auto;
box-shadow: 0 0 0 1px transparent; /* Acts as a border */
transition: box-shadow 0.15s ease-in-out;
max-height: 100%;
&.is-editing:hover {
box-shadow: 0 0 0 1px var(--border-weak);
}
&.is-editing.active {
box-shadow: 0 0 0 1px var(--border-accent-weak);
}
&.is-editing.dragging {
box-shadow: 0 0 0 1px var(--border-accent-strong);
}
.resize-handle {
position: absolute;
bottom: -4px;
left: 50%; /* Center horizontally */
transform: translateX(-50%); /* Ensure proper centering */
width: 24px;
height: 8px;
border-radius: 4px;
background-color: initial;
border: 1px solid var(--background-accent-strong);
cursor: ns-resize;
z-index: 1;
visibility: hidden;
transition: visibility 0.15s ease-in-out;
}
&.active .resize-handle {
visibility: visible;
}
}
.only-bottom {
}
.jet-form-header {
min-height: 10px;
}
.jet-form-body {
min-height: 100px;
background-color: inherit;
}
.jet-form-footer {
min-height: 10px;
}
.jet-form-footer .resize-handle {
top: -4px;
bottom: unset;
}
.jet-container.jet-container-json-form {
padding: 0px;

View file

@ -0,0 +1,84 @@
import { useState, useEffect } from 'react';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
const useIsWidgetSelected = (id) => {
// Get selected components from store using shallow comparison
const selectedComponents = useStore((state) => state.selectedComponents, shallow);
// Check if the only selected component is the provided `id`
return selectedComponents.length === 1 && selectedComponents[0] === id;
};
export const useActiveSlot = (widgetId) => {
const [activeSlot, setActiveSlot] = useState(''); // Default to widget ID
const isSelected = useIsWidgetSelected(widgetId); // Check if widget is selected
useEffect(() => {
if (!isSelected) {
setActiveSlot('');
}
}, [isSelected]);
useEffect(() => {
const handleDoubleClick = (event) => {
let target = event.target;
if (!widgetId) {
setActiveSlot(null);
return;
}
// Traverse up to find a slot with an id
while (target && target !== document.body) {
if (target.id && target.id.startsWith('canvas-')) {
const slotId = target.id.replace(/^canvas-/, ''); // ✅ Strip "canvas-"
setActiveSlot(slotId);
return;
}
target = target.parentElement;
}
// If no slot is found, reset to widget ID
setActiveSlot(widgetId);
};
const handleSingleClick = (event) => {
let target = event.target;
if (!widgetId) {
setActiveSlot(null);
return;
}
// Traverse up to find a valid main slot (not header/footer)
while (target && target !== document.body) {
if (
target.id &&
target.id.startsWith('canvas-') &&
!target.id.endsWith('-header') &&
!target.id.endsWith('-footer')
) {
const slotId = target.id.replace(/^canvas-/, ''); // Strip "canvas-"
setActiveSlot(slotId);
return;
}
target = target.parentElement;
}
// If no main slot is found, fallback to widget ID
setActiveSlot(widgetId);
};
// Attach single click if the widget is selected, otherwise listen for double-click
document.addEventListener('dblclick', handleDoubleClick);
document.addEventListener('click', handleSingleClick);
return () => {
document.removeEventListener('dblclick', handleDoubleClick);
document.removeEventListener('click', handleSingleClick);
};
}, [widgetId]); // Re-run when widgetId or selection state changes
return activeSlot;
};

View file

@ -0,0 +1,135 @@
import { useRef, useState } from 'react';
const defaultProps = {
minHeight: 50,
maxHeight: 600,
minWidth: 50,
maxWidth: 600,
lockHorizontal: false,
lockVertical: false,
stepHeight: 10, // Default step size for height
stepWidth: 10, // Default step size for width
onResize: null,
onDragStart: null,
onDragEnd: null,
isReverseVerticalDrag: false,
};
export const useResizable = (options = {}) => {
const props = { ...defaultProps, ...options };
const parentRef = useRef(null);
const [isDragging, setIsDragging] = useState(false); // ✅ Track dragging state
const [height, setHeight] = useState(
typeof props.initialHeight === 'string' ? props.initialHeight : `${props.initialHeight || 200}px`
);
const [width, setWidth] = useState(
typeof props.initialWidth === 'string' ? props.initialWidth : `${props.initialWidth || 200}px`
);
const getRootProps = () => ({
ref: parentRef,
style: { height, width },
});
const getResizeState = () => ({
height,
width,
isDragging,
});
const getHandleProps = () => {
const handleMouseDown = (e) => {
// Prevent right-click drag activation (button === 2)
if (e.button === 2) return;
if (!parentRef.current) return;
e.stopPropagation();
e.preventDefault();
const startHeight = parseInt(parentRef.current.clientHeight);
const startWidth = parseInt(parentRef.current.clientWidth);
const parentWidth = parentRef.current.parentElement ? parentRef.current.parentElement.clientWidth : startWidth;
const startY = e.clientY;
const startX = e.clientX;
const isPercentage = typeof props.initialWidth === 'string' && props.initialWidth.includes('%');
setIsDragging(true); // ✅ Set dragging state to true
if (props.onDragStart) {
props.onDragStart({ newHeight: startHeight, newWidth: startWidth });
}
const handleMouseMove = (moveEvent) => {
moveEvent.stopPropagation();
moveEvent.preventDefault();
let newHeight = startHeight;
let newWidth = startWidth;
if (!props.lockVertical) {
const deltaY = props.isReverseVerticalDrag ? startY - moveEvent.clientY : moveEvent.clientY - startY;
newHeight = startHeight + deltaY;
newHeight = Math.max(props.minHeight, Math.min(props.maxHeight, newHeight));
newHeight = Math.round(newHeight / props.stepHeight) * props.stepHeight; // Snap to stepHeight
}
if (!props.lockHorizontal) {
newWidth = startWidth + (moveEvent.clientX - startX);
newWidth = Math.max(props.minWidth, Math.min(props.maxWidth, newWidth));
newWidth = Math.round(newWidth / props.stepWidth) * props.stepWidth; // Snap to stepWidth
if (isPercentage) {
newWidth = (newWidth / parentWidth) * 100; // Convert to percentage
newWidth = `${newWidth.toFixed(2)}%`;
} else {
newWidth = `${newWidth}px`;
}
}
setHeight(`${newHeight}px`);
setWidth(newWidth);
if (parentRef.current) {
parentRef.current.style.height = `${newHeight}px`;
parentRef.current.style.width = newWidth;
}
if (props.onResize) {
props.onResize({
newHeight,
newWidth,
heightDiff: newHeight - startHeight,
widthDiff: isPercentage
? parseInt(newWidth) - (startWidth / parentWidth) * 100
: parseInt(newWidth) - startWidth,
});
}
};
const handleMouseUp = () => {
setIsDragging(false); // ✅ Set dragging state to false
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
if (props.onDragEnd) {
// Get the updated height and width from the DOM instead of relying on state
const finalHeight = parentRef.current ? parseInt(parentRef.current.clientHeight) : parseInt(height);
const finalWidth = parentRef.current ? parseInt(parentRef.current.clientWidth) : parseInt(width);
props.onDragEnd({ newHeight: finalHeight, newWidth: finalWidth });
}
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
return {
onMouseDown: handleMouseDown,
};
};
return { rootRef: parentRef, getRootProps, getHandleProps, getResizeState };
};
export default useResizable;

View file

@ -1896,4 +1896,28 @@ export const createComponentsSlice = (set, get) => ({
state.modalsOpenOnCanvas = newModalOpenOnCanvas;
});
},
updateContainerAutoHeight: (componentId) => {
if (
!componentId ||
componentId === 'canvas' ||
componentId.includes('-header') ||
componentId.includes('-footer')
) {
return;
}
const { currentLayout, getCurrentPageComponents, setComponentProperty } = get();
const allComponents = getCurrentPageComponents();
const childComponents = getAllChildComponents(allComponents, componentId);
const maxHeight = Object.values(childComponents).reduce((max, component) => {
const layout = component?.layouts?.[currentLayout];
if (!layout) {
return max;
}
const sum = layout.top + layout.height;
return Math.max(max, sum);
}, 0);
setComponentProperty(componentId, `canvasHeight`, maxHeight, 'properties', 'value', false);
},
});

View file

@ -3,7 +3,7 @@ export const containerConfig = {
displayName: 'Container',
description: 'Group components',
defaultSize: {
width: 5,
width: 10,
height: 200,
},
component: 'Container',
@ -47,10 +47,16 @@ export const containerConfig = {
defaultValue: true,
},
},
headerHeight: {
type: 'numberInput',
displayName: 'Header height',
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
},
defaultChildren: [
{
componentName: 'Text',
slotName: 'header',
layout: {
top: 20,
left: 1,
@ -98,15 +104,6 @@ export const containerConfig = {
},
accordian: 'container',
},
headerHeight: {
type: 'numberInput',
displayName: 'Height',
validation: {
schema: { type: 'number' },
defaultValue: 80,
},
accordian: 'header',
},
borderRadius: {
type: 'numberInput',
displayName: 'Border',
@ -158,6 +155,7 @@ export const containerConfig = {
loadingState: { value: `{{false}}` },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
headerHeight: { value: `{{80}}` },
},
events: [],
styles: {

View file

@ -4,7 +4,7 @@ export const formConfig = {
description: 'Wrapper for multiple components',
defaultSize: {
width: 13,
height: 480,
height: 450,
},
defaultChildren: [
{
@ -19,8 +19,8 @@ export const formConfig = {
accessorKey: 'text',
styles: ['fontWeight', 'textSize', 'textColor'],
defaultValue: {
text: 'Form title',
textSize: 20,
text: 'Form',
textSize: 16,
textColor: '#000',
},
},
@ -34,203 +34,68 @@ export const formConfig = {
},
properties: ['text'],
defaultValue: {
text: 'Button2',
text: 'Submit',
padding: 'none',
},
},
{
componentName: 'Text',
layout: {
top: 40,
left: 10,
height: 30,
width: 17,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'User Details',
fontWeight: 'bold',
textSize: 18,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'Text',
layout: {
top: 90,
left: 10,
height: 30,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'Name',
fontWeight: 'normal',
textSize: 14,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'Text',
layout: {
top: 160,
left: 10,
height: 30,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'Age',
fontWeight: 'normal',
textSize: 14,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'TextInput',
layout: {
top: 120,
left: 10,
height: 30,
width: 25,
top: 20,
left: 5,
height: 40,
width: 31,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
placeholder: 'Enter your name',
label: '',
label: 'Name',
width: '{{60}}',
direction: 'left',
alignment: 'side',
auto: '{{false}}',
padding: 'default',
},
},
{
componentName: 'NumberInput',
layout: {
top: 190,
left: 10,
height: 30,
width: 25,
top: 80,
left: 5,
height: 40,
width: 31,
},
properties: ['value', 'label'],
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
value: 24,
label: '',
placeholder: 'Age',
label: 'Age',
width: '{{60}}',
direction: 'left',
alignment: 'side',
auto: '{{false}}',
padding: 'default',
},
},
{
componentName: 'Button',
componentName: 'TextInput',
layout: {
top: 240,
left: 10,
height: 30,
width: 10,
top: 140,
left: 5,
height: 40,
width: 31,
},
properties: ['text'],
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
text: 'Submit',
placeholder: 'Tomy',
label: 'Pet name',
width: '{{60}}',
alignment: 'side',
direction: 'left',
auto: '{{false}}',
padding: 'default',
},
},
],
@ -276,6 +141,24 @@ export const formConfig = {
},
showHeader: { type: 'toggle', displayName: 'Header' },
showFooter: { type: 'toggle', displayName: 'Footer' },
headerHeight: {
type: 'numberInput',
displayName: 'Header height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
canvasHeight: {
type: 'numberInput',
displayName: 'Canvas height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
footerHeight: {
type: 'numberInput',
displayName: 'Footer height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
visibility: {
type: 'toggle',
displayName: 'Visibility',
@ -294,6 +177,13 @@ export const formConfig = {
defaultValue: false,
},
},
tooltip: {
type: 'code',
displayName: 'Tooltip',
validation: { schema: { type: 'string' } },
section: 'additionalActions',
placeholder: 'Enter tooltip text',
},
},
events: {
onSubmit: { displayName: 'On submit' },
@ -316,24 +206,8 @@ export const formConfig = {
defaultValue: '#ffffffff',
},
},
headerHeight: {
type: 'code',
displayName: 'Header height',
validation: {
schema: { type: 'string' },
defaultValue: '80px',
},
},
footerHeight: {
type: 'code',
displayName: 'Footer height',
validation: {
schema: { type: 'string' },
defaultValue: '80px',
},
},
backgroundColor: {
type: 'color',
type: 'colorSwatches',
displayName: 'Background color',
validation: {
schema: { type: 'string' },
@ -351,7 +225,7 @@ export const formConfig = {
},
},
borderColor: {
type: 'color',
type: 'colorSwatches',
displayName: 'Border color',
validation: {
schema: { type: 'string' },
@ -403,18 +277,18 @@ export const formConfig = {
value:
"{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}",
},
showHeader: { value: '{{false}}' },
showFooter: { value: '{{false}}' },
showHeader: { value: '{{true}}' },
showFooter: { value: '{{true}}' },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
headerHeight: { value: 60 },
footerHeight: { value: 60 },
},
events: [],
styles: {
backgroundColor: { value: '#fff' },
borderRadius: { value: '0' },
borderColor: { value: '#fff' },
headerHeight: { value: '60px' },
footerHeight: { value: '60px' },
},
},
};

View file

@ -3,7 +3,7 @@ export const containerConfig = {
displayName: 'Container',
description: 'Group components',
defaultSize: {
width: 5,
width: 10,
height: 200,
},
component: 'Container',
@ -47,10 +47,16 @@ export const containerConfig = {
defaultValue: true,
},
},
headerHeight: {
type: 'numberInput',
displayName: 'Header height',
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
},
defaultChildren: [
{
componentName: 'Text',
slotName: 'header',
layout: {
top: 20,
left: 1,
@ -98,15 +104,6 @@ export const containerConfig = {
},
accordian: 'container',
},
headerHeight: {
type: 'numberInput',
displayName: 'Height',
validation: {
schema: { type: 'number' },
defaultValue: 80,
},
accordian: 'header',
},
borderRadius: {
type: 'numberInput',
displayName: 'Border',
@ -158,6 +155,7 @@ export const containerConfig = {
loadingState: { value: `{{false}}` },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
headerHeight: { value: `{{80}}` },
},
events: [],
styles: {

View file

@ -4,7 +4,7 @@ export const formConfig = {
description: 'Wrapper for multiple components',
defaultSize: {
width: 13,
height: 480,
height: 450,
},
defaultChildren: [
{
@ -19,8 +19,8 @@ export const formConfig = {
accessorKey: 'text',
styles: ['fontWeight', 'textSize', 'textColor'],
defaultValue: {
text: 'Form title',
textSize: 20,
text: 'Form',
textSize: 16,
textColor: '#000',
},
},
@ -34,203 +34,68 @@ export const formConfig = {
},
properties: ['text'],
defaultValue: {
text: 'Button2',
text: 'Submit',
padding: 'none',
},
},
{
componentName: 'Text',
layout: {
top: 40,
left: 10,
height: 30,
width: 17,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'User Details',
fontWeight: 'bold',
textSize: 18,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'Text',
layout: {
top: 90,
left: 10,
height: 30,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'Name',
fontWeight: 'normal',
textSize: 14,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'Text',
layout: {
top: 160,
left: 10,
height: 30,
},
properties: ['text'],
styles: [
'textSize',
'fontWeight',
'fontStyle',
'textColor',
'isScrollRequired',
'lineHeight',
'textIndent',
'textAlign',
'verticalAlignment',
'decoration',
'transformation',
'letterSpacing',
'wordSpacing',
'fontVariant',
'backgroundColor',
'borderColor',
'borderRadius',
'boxShadow',
'padding',
],
defaultValue: {
text: 'Age',
fontWeight: 'normal',
textSize: 14,
textColor: '#000',
backgroundColor: '#fff00000',
textAlign: 'left',
decoration: 'none',
transformation: 'none',
fontStyle: 'normal',
lineHeight: 1.5,
textIndent: '0',
letterSpacing: '0',
wordSpacing: '0',
fontVariant: 'normal',
verticalAlignment: 'top',
padding: 'default',
boxShadow: '0px 0px 0px 0px #00000090',
borderRadius: '0',
isScrollRequired: 'enabled',
},
},
{
componentName: 'TextInput',
layout: {
top: 120,
left: 10,
height: 30,
width: 25,
top: 20,
left: 5,
height: 40,
width: 31,
},
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
placeholder: 'Enter your name',
label: '',
label: 'Name',
width: '{{60}}',
direction: 'left',
alignment: 'side',
auto: '{{false}}',
padding: 'default',
},
},
{
componentName: 'NumberInput',
layout: {
top: 190,
left: 10,
height: 30,
width: 25,
top: 80,
left: 5,
height: 40,
width: 31,
},
properties: ['value', 'label'],
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
value: 24,
label: '',
placeholder: 'Age',
label: 'Age',
width: '{{60}}',
direction: 'left',
alignment: 'side',
auto: '{{false}}',
padding: 'default',
},
},
{
componentName: 'Button',
componentName: 'TextInput',
layout: {
top: 240,
left: 10,
height: 30,
width: 10,
top: 140,
left: 5,
height: 40,
width: 31,
},
properties: ['text'],
properties: ['placeholder', 'label'],
styles: ['alignment', 'width', 'auto', 'padding', 'direction'],
defaultValue: {
text: 'Submit',
placeholder: 'Tomy',
label: 'Pet name',
width: '{{60}}',
alignment: 'side',
direction: 'left',
auto: '{{false}}',
padding: 'default',
},
},
],
@ -276,6 +141,24 @@ export const formConfig = {
},
showHeader: { type: 'toggle', displayName: 'Header' },
showFooter: { type: 'toggle', displayName: 'Footer' },
headerHeight: {
type: 'numberInput',
displayName: 'Header height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
canvasHeight: {
type: 'numberInput',
displayName: 'Canvas height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
footerHeight: {
type: 'numberInput',
displayName: 'Footer height',
isHidden: true,
validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 },
},
visibility: {
type: 'toggle',
displayName: 'Visibility',
@ -294,6 +177,13 @@ export const formConfig = {
defaultValue: false,
},
},
tooltip: {
type: 'code',
displayName: 'Tooltip',
validation: { schema: { type: 'string' } },
section: 'additionalActions',
placeholder: 'Enter tooltip text',
},
},
events: {
onSubmit: { displayName: 'On submit' },
@ -316,22 +206,6 @@ export const formConfig = {
defaultValue: '#ffffffff',
},
},
headerHeight: {
type: 'code',
displayName: 'Header height',
validation: {
schema: { type: 'string' },
defaultValue: '80px',
},
},
footerHeight: {
type: 'code',
displayName: 'Footer height',
validation: {
schema: { type: 'string' },
defaultValue: '80px',
},
},
backgroundColor: {
type: 'colorSwatches',
displayName: 'Background color',
@ -403,19 +277,18 @@ export const formConfig = {
value:
"{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}",
},
buttonToSubmit: { value: '{{"none"}}' },
showHeader: { value: '{{false}}' },
showFooter: { value: '{{false}}' },
showHeader: { value: '{{true}}' },
showFooter: { value: '{{true}}' },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
headerHeight: { value: 60 },
footerHeight: { value: 60 },
},
events: [],
styles: {
backgroundColor: { value: '#fff' },
borderRadius: { value: '0' },
borderColor: { value: '#fff' },
headerHeight: { value: '60px' },
footerHeight: { value: '60px' },
},
},
};