From 558d66fed061c725f7089cb4f09858a3b0bb95c1 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com> Date: Wed, 4 Dec 2024 03:50:32 -0800 Subject: [PATCH] feat: Adds header for container widget (#11309) --- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 15 ++++- .../AppCanvas/appCanvasConstants.js | 2 +- .../AppBuilder/AppCanvas/appCanvasUtils.js | 26 ++++++--- .../WidgetManager/widgets/container.js | 55 ++++++++++++++++++- frontend/src/AppBuilder/Widgets/Container.jsx | 55 +++++++++++++++++-- .../Editor/WidgetManager/configs/container.js | 49 ++++++++++++++++- server/src/helpers/widget-config/container.js | 50 ++++++++++++++++- 7 files changed, 233 insertions(+), 19 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index deea310a10..c8f1311033 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -685,7 +685,9 @@ export default function Grid({ gridWidth, currentLayout }) { let left = e.lastEvent?.translate[0]; let top = e.lastEvent?.translate[1]; if ( - ['Listview', 'Kanban'].includes(boxList.find((box) => box.id === draggedOverElemId)?.component?.component) + ['Listview', 'Kanban', 'Container'].includes( + boxList.find((box) => box.id === draggedOverElemId)?.component?.component + ) ) { const elemContainer = e.target.closest('.real-canvas'); const containerHeight = elemContainer.clientHeight; @@ -702,10 +704,19 @@ export default function Grid({ gridWidth, currentLayout }) { if (draggedOverElemId !== currentParentId) { if (isParentChangeAllowed) { const draggedOverWidget = boxList.find((box) => box.id === draggedOverElemId); + + let parentWidgetType = boxList.find((box) => box.id === draggedOverElemId)?.component?.component; + // @TODO - When dropping back to container from canvas, the boxList doesn't have canvas header, + // boxList will return null. But we need to tell getMouseDistanceFromParentDiv parentWidgetType is container + // As container id is like 'canvas-2375e23765e-123234' + if (parentId && !parentWidgetType && draggedOverElemId.includes('-header')) { + parentWidgetType = 'Container'; + } + let { left: _left, top: _top } = getMouseDistanceFromParentDiv( e, draggedOverWidget?.component?.component === 'Kanban' ? draggedOverElem : draggedOverElemId, - boxList.find((box) => box.id === draggedOverElemId)?.component?.component + parentWidgetType ); left = _left; top = _top; diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js index 6d15fea9ad..a96dc9f9c7 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasConstants.js @@ -6,7 +6,7 @@ export const CANVAS_WIDTHS = Object.freeze({ rightSideBarWidth: 300, }); -export const WIDGETS_WITH_DEFAULT_CHILDREN = ['Listview', 'Tabs', 'Form', 'Kanban']; +export const WIDGETS_WITH_DEFAULT_CHILDREN = ['Listview', 'Tabs', 'Form', 'Kanban', 'Container']; export const DEFAULT_CANVAS_WIDTH = 1292; diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index 6171473174..c4e4ec6402 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -3,7 +3,7 @@ import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { componentTypes } from '../WidgetManager'; import useStore from '@/AppBuilder/_stores/store'; import { toast } from 'react-hot-toast'; -import { CANVAS_WIDTHS, NO_OF_GRIDS } from './appCanvasConstants'; +import { CANVAS_WIDTHS, NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN } from './appCanvasConstants'; import _ from 'lodash'; export function snapToGrid(canvasWidth, x, y) { @@ -42,8 +42,6 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre componentData.definition.others.showOnMobile.value = `{{true}}`; } - const widgetsWithDefaultComponents = ['Listview', 'Tabs', 'Form', 'Kanban']; - const nonActiveLayout = currentLayout === 'desktop' ? 'mobile' : 'desktop'; const newComponent = { id: uuidv4(), @@ -66,7 +64,7 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre height: defaultHeight, }, }, - withDefaultChildren: widgetsWithDefaultComponents.includes(componentData.component), + withDefaultChildren: WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentData.component), }; return newComponent; @@ -136,13 +134,14 @@ export function addChildrenWidgetsToParent(componentType, parentId, currentLayou } const nonActiveLayout = currentLayout === 'desktop' ? 'mobile' : 'desktop'; + const _parent = getParentComponentIdByType(child, parentMeta.component, parentId); const newChildComponent = { id: uuidv4(), name: widgetName, component: { ...componentData, - parent: parentMeta.component === 'Tabs' ? parentId + '-' + tab : parentId, + parent: getParentComponentIdByType(child, parentMeta.component, parentId), }, layouts: { [currentLayout]: { @@ -193,7 +192,8 @@ export const getAllChildComponents = (allComponents, parentId) => { const isParentTabORCalendar = allComponents[parentId]?.component?.component === 'Tabs' || allComponents[parentId]?.component?.component === 'Calendar' || - allComponents[parentId]?.component?.component === 'Kanban'; + allComponents[parentId]?.component?.component === 'Kanban' || + allComponents[parentId]?.component?.component === 'Container'; if (componentParentId && isParentTabORCalendar) { let childComponent = deepClone(allComponents[componentId]); @@ -336,7 +336,11 @@ const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentI const parentComponent = allComponents?.[parentId]; if (parentComponent) { - return parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar'; + return ( + parentComponent.component.component === 'Tabs' || + parentComponent.component.component === 'Calendar' || + parentComponent.component.component === 'Container' + ); } return false; @@ -457,3 +461,11 @@ export const computeViewerBackgroundColor = (isAppDarkMode, canvasBgColor) => { } return canvasBgColor; }; + +export const getParentComponentIdByType = (child, parentComponent, parentId) => { + const { tab } = child; + + if (parentComponent === 'Tabs') return `${parentId}-${tab}`; + else if (parentComponent === 'Container') return `${parentId}-header`; + return parentId; +}; diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/container.js b/frontend/src/AppBuilder/WidgetManager/widgets/container.js index dc2dcf34ec..f06c8ae182 100644 --- a/frontend/src/AppBuilder/WidgetManager/widgets/container.js +++ b/frontend/src/AppBuilder/WidgetManager/widgets/container.js @@ -39,17 +39,68 @@ export const containerConfig = { defaultValue: false, }, }, + showHeader: { + type: 'toggle', + displayName: 'Show header', + validation: { + schema: { type: 'boolean' }, + defaultValue: true, + }, + }, + headerHeight: { + type: 'numberInput', + displayName: 'Header height', + validation: { schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] }, defaultValue: 80 }, + accordian: 'field', + }, }, + defaultChildren: [ + { + componentName: 'Text', + layout: { + top: 20, + left: 1, + height: 40, + }, + displayName: 'ContainerText', + properties: ['text'], + accessorKey: 'text', + styles: ['fontWeight', 'textSize', 'textColor'], + defaultValue: { + text: 'Container title', + fontWeight: 'bold', + textSize: 16, + textColor: '#000', + }, + }, + ], events: {}, styles: { backgroundColor: { type: 'color', - displayName: 'Background color', + displayName: 'Background', validation: { schema: { type: 'string' }, defaultValue: '#fff', }, }, + headerBackgroundColor: { + type: 'color', + displayName: 'Header', + validation: { + schema: { type: 'string' }, + defaultValue: '#fff', + }, + }, + headerHeight: { + type: 'numberInput', + displayName: 'Header height', + validation: { + schema: { type: 'number' }, + defaultValue: 80, + }, + accordian: 'field', + }, borderRadius: { type: 'code', displayName: 'Border radius', @@ -105,8 +156,10 @@ export const containerConfig = { events: [], styles: { backgroundColor: { value: '#fff' }, + headerBackgroundColor: { value: '#fff' }, borderRadius: { value: '4' }, borderColor: { value: '#fff' }, + headerHeight: { value: '80' }, }, }, }; diff --git a/frontend/src/AppBuilder/Widgets/Container.jsx b/frontend/src/AppBuilder/Widgets/Container.jsx index 2b6f8008ed..11ce20e464 100644 --- a/frontend/src/AppBuilder/Widgets/Container.jsx +++ b/frontend/src/AppBuilder/Widgets/Container.jsx @@ -13,8 +13,6 @@ export const Container = ({ setExposedVariables, setExposedVariable, }) => { - const { borderRadius, borderColor, boxShadow } = styles; - const { isDisabled, isVisible, isLoading } = useExposeState( properties.loadingState, properties.visibility, @@ -23,15 +21,25 @@ export const Container = ({ setExposedVariable ); - const bgColor = useMemo(() => { + const { borderRadius, borderColor, boxShadow, headerHeight = 80 } = styles; + const contentBgColor = useMemo(() => { return { backgroundColor: ['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor, }; }, [styles.backgroundColor, darkMode]); + const headerBgColor = useMemo(() => { + return { + backgroundColor: + ['#fff', '#ffffffff'].includes(styles.headerBackgroundColor) && darkMode + ? '#232E3C' + : styles.headerBackgroundColor, + }; + }, [styles.headerBackgroundColor, darkMode]); + const computedStyles = { - backgroundColor: bgColor.backgroundColor, + backgroundColor: contentBgColor.backgroundColor, borderRadius: borderRadius ? parseFloat(borderRadius) : 0, border: `1px solid ${borderColor}`, height, @@ -40,9 +48,26 @@ export const Container = ({ position: 'relative', boxShadow, }; + + const computedHeaderStyles = { + ...headerBgColor, + height: `${headerHeight}px`, + flexShrink: 0, + flexGrow: 0, + borderBottom: `1px solid var(--border-weak)`, + }; + + const computedContentStyles = { + ...contentBgColor, + flex: 1, + overflow: 'auto', + }; + return (
) : ( -