diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
index 4e7b56ea70..41bc116ec3 100644
--- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
+++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
@@ -206,6 +206,7 @@ export const getAllChildComponents = (allComponents, parentId) => {
allComponents[parentId]?.component?.component === 'Calendar' ||
allComponents[parentId]?.component?.component === 'Kanban' ||
allComponents[parentId]?.component?.component === 'Container' ||
+ allComponents[parentId]?.component?.component === 'Form' ||
allComponents[parentId]?.component?.component === 'ModalV2';
if (componentParentId && isParentTabORCalendar) {
@@ -327,6 +328,7 @@ const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentI
parentComponent.component.component === 'Tabs' ||
parentComponent.component.component === 'Calendar' ||
parentComponent.component.component === 'Container' ||
+ parentComponent.component.component === 'Form' ||
parentComponent.component.component === 'ModalV2'
);
}
@@ -665,11 +667,14 @@ export const computeViewerBackgroundColor = (isAppDarkMode, canvasBgColor) => {
return canvasBgColor;
};
-export const getParentComponentIdByType = ({ child, parentComponent, parentId, slotName = 'header' }) => {
+export const getParentComponentIdByType = ({ child, parentComponent, parentId, slotName }) => {
const { tab } = child;
if (parentComponent === 'Tabs') return `${parentId}-${tab}`;
- else if (parentComponent === 'Container' || parentComponent === 'ModalV2') {
+ else if (
+ slotName &&
+ (parentComponent === 'Form' || parentComponent === 'Container' || parentComponent === 'ModalV2')
+ ) {
return `${parentId}-${slotName}`;
}
return parentId;
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
index d4676ad4b6..b39924854e 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
@@ -19,16 +19,34 @@ export const Form = ({
allComponents,
pages,
}) => {
- const properties = Object.keys(componentMeta.properties);
+ const tempComponentMeta = deepClone(componentMeta);
+
+ let properties = [];
+ let additionalActions = [];
+ let dataProperties = [];
+
const events = Object.keys(componentMeta.events);
const validations = Object.keys(componentMeta.validation || {});
- const tempComponentMeta = deepClone(componentMeta);
+
+ for (const [key] of Object.entries(componentMeta?.properties)) {
+ if (componentMeta?.properties[key]?.section === 'additionalActions') {
+ additionalActions.push(key);
+ } else if (componentMeta?.properties[key]?.accordian === 'Data') {
+ dataProperties.push(key);
+ } else {
+ properties.push(key);
+ }
+ }
const { id } = component;
const newOptions = [{ name: 'None', value: 'none' }];
- Object.entries(allComponents).forEach(([componentId, component]) => {
- if (component.component.parent === id && component?.component?.component === 'Button') {
- newOptions.push({ name: component.component.name, value: componentId });
+ Object.entries(allComponents).forEach(([componentId, _component]) => {
+ const validParent =
+ _component.component.parent === id ||
+ _component.component.parent === `${id}-footer` ||
+ _component.component.parent === `${id}-header`;
+ if (validParent && _component?.component?.component === 'Button') {
+ newOptions.push({ name: _component.component.name, value: componentId });
}
});
@@ -48,7 +66,8 @@ export const Form = ({
allComponents,
validations,
darkMode,
- pages
+ pages,
+ additionalActions
);
return ;
@@ -68,7 +87,8 @@ export const baseComponentProperties = (
allComponents,
validations,
darkMode,
- pages
+ pages,
+ additionalActions
) => {
let items = [];
if (properties.length > 0) {
@@ -90,6 +110,24 @@ export const baseComponentProperties = (
});
}
+ items.push({
+ title: 'Additional actions',
+ isOpen: true,
+ children: additionalActions?.map((property) =>
+ renderElement(
+ component,
+ componentMeta,
+ paramUpdated,
+ dataQueries,
+ property,
+ 'properties',
+ currentState,
+ allComponents,
+ darkMode
+ )
+ ),
+ });
+
if (events.length > 0) {
items.push({
title: `${i18next.t('widget.common.events', 'Events')}`,
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/form.js b/frontend/src/AppBuilder/WidgetManager/widgets/form.js
index 0e9f5f4ce3..7ab4bd26a6 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/form.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/form.js
@@ -4,9 +4,40 @@ export const formConfig = {
description: 'Wrapper for multiple components',
defaultSize: {
width: 13,
- height: 330,
+ height: 480,
},
defaultChildren: [
+ {
+ componentName: 'Text',
+ slotName: 'header',
+ layout: {
+ top: 10,
+ left: 1,
+ height: 40,
+ },
+ properties: ['text'],
+ accessorKey: 'text',
+ styles: ['fontWeight', 'textSize', 'textColor'],
+ defaultValue: {
+ text: 'Form title',
+ textSize: 20,
+ textColor: '#000',
+ },
+ },
+ {
+ componentName: 'Button',
+ slotName: 'footer',
+ layout: {
+ top: 12,
+ left: 32,
+ height: 36,
+ },
+ properties: ['text'],
+ defaultValue: {
+ text: 'Button2',
+ padding: 'none',
+ },
+ },
{
componentName: 'Text',
layout: {
@@ -225,6 +256,7 @@ export const formConfig = {
loadingState: {
type: 'toggle',
displayName: 'Loading state',
+ section: 'additionalActions',
validation: {
schema: { type: 'boolean' },
defaultValue: false,
@@ -242,12 +274,64 @@ export const formConfig = {
value: true,
},
},
+ showHeader: { type: 'toggle', displayName: 'Header' },
+ showFooter: { type: 'toggle', displayName: 'Footer' },
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: true,
+ },
+ },
+ disabledState: {
+ type: 'toggle',
+ displayName: 'Disable',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ },
},
events: {
onSubmit: { displayName: 'On submit' },
onInvalid: { displayName: 'On invalid' },
},
styles: {
+ headerBackgroundColor: {
+ type: 'color',
+ displayName: 'Header background color',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: '#ffffffff',
+ },
+ },
+ footerBackgroundColor: {
+ type: 'color',
+ displayName: 'Footer background color',
+ validation: {
+ schema: { type: 'string' },
+ 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',
displayName: 'Background color',
@@ -274,22 +358,6 @@ export const formConfig = {
defaultValue: '#fff',
},
},
- visibility: {
- type: 'toggle',
- displayName: 'Visibility',
- validation: {
- schema: { type: 'boolean' },
- defaultValue: true,
- },
- },
- disabledState: {
- type: 'toggle',
- displayName: 'Disable',
- validation: {
- schema: { type: 'boolean' },
- defaultValue: false,
- },
- },
},
exposedVariables: {
data: {},
@@ -317,15 +385,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}}' },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
},
events: [],
styles: {
backgroundColor: { value: '#fff' },
borderRadius: { value: '0' },
borderColor: { value: '#fff' },
- visibility: { value: '{{true}}' },
- disabledState: { value: '{{false}}' },
+ headerHeight: { value: '60px' },
+ footerHeight: { value: '60px' },
},
},
};
diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
index 674d707f03..c9ea9b7601 100644
--- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx
+++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
@@ -13,6 +13,12 @@ import RenderSchema from './RenderSchema';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
+const getCanvasHeight = (height) => {
+ const parsedHeight = height.includes('px') ? parseInt(height, 10) : height;
+
+ return Math.ceil(parsedHeight);
+};
+
export const Form = function Form(props) {
const {
id,
@@ -29,8 +35,25 @@ export const Form = function Form(props) {
dataCy,
} = props;
const childComponents = useStore((state) => state.getChildComponents(id), shallow);
- const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles;
- const { buttonToSubmit, loadingState, advanced, JSONSchema } = properties;
+ const {
+ borderRadius,
+ borderColor,
+ boxShadow,
+ headerHeight,
+ footerHeight,
+ footerBackgroundColor,
+ headerBackgroundColor,
+ } = styles;
+ const {
+ buttonToSubmit,
+ loadingState,
+ advanced,
+ JSONSchema,
+ showHeader = false,
+ showFooter = false,
+ visibility,
+ disabledState,
+ } = properties;
const backgroundColor =
['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor;
const computedStyles = {
@@ -40,16 +63,32 @@ export const Form = function Form(props) {
height,
display: visibility ? 'flex' : 'none',
position: 'relative',
- overflow: 'hidden auto',
boxShadow,
+ flexDirection: 'column',
};
- const childIdNameMap = useMemo(() => {
- return Object.keys(childComponents).reduce((acc, id) => {
- const component = childComponents[id]?.component?.component;
- return { ...acc, [id]: component?.name };
- }, {});
- }, [childComponents]);
+ const formHeader = {
+ flexShrink: 0,
+ // height: headerHeight,
+ padding: '10px 6px',
+ borderBottom: '1px solid var(--border-weak)',
+ backgroundColor:
+ ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor,
+ };
+ const formFooter = {
+ flexShrink: 0,
+ // height: footerHeight,
+ padding: '10px 6px',
+ borderTop: '1px solid var(--border-weak)',
+ backgroundColor:
+ ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor,
+ };
+ const formContent = {
+ overflow: 'hidden auto',
+ display: 'flex',
+ height: '100%',
+ padding: '10px 6px',
+ };
const parentRef = useRef(null);
const childDataRef = useRef({});
@@ -58,6 +97,8 @@ 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 = {
@@ -155,7 +196,7 @@ export const Form = function Form(props) {
};
setExposedVariables(exposedVariables);
setValidation(childValidation);
- }, [childrenData, advanced, JSON.stringify(childIdNameMap)]);
+ }, [childrenData, advanced]);
useEffect(() => {
document.addEventListener('submitForm', handleFormSubmission);
@@ -245,105 +286,138 @@ export const Form = function Form(props) {
if (e.target.className === 'real-canvas') onComponentClick(id, component);
}} //Hack, should find a better solution - to prevent losing z index+1 when container element is clicked
>
- {loadingState ? (
-
-
-
-
+ {showHeader && (
+
+
- ) : (
-