diff --git a/client/packages/lowcoder/src/components/ThemeSettingsSelector.tsx b/client/packages/lowcoder/src/components/ThemeSettingsSelector.tsx index f2004d43a..67cbb9c20 100644 --- a/client/packages/lowcoder/src/components/ThemeSettingsSelector.tsx +++ b/client/packages/lowcoder/src/components/ThemeSettingsSelector.tsx @@ -254,7 +254,7 @@ export default function ThemeSettingsSelector(props: ColorConfigProps) { }; const gridPaddingInputBlur = (padding: string) => { - let result = 20; + let result = 0; if (padding !== '') { result = Number(padding); } diff --git a/client/packages/lowcoder/src/comps/comps/appSettingsComp.tsx b/client/packages/lowcoder/src/comps/comps/appSettingsComp.tsx index 64122daba..b38260eed 100644 --- a/client/packages/lowcoder/src/comps/comps/appSettingsComp.tsx +++ b/client/packages/lowcoder/src/comps/comps/appSettingsComp.tsx @@ -219,8 +219,8 @@ const childrenMap = { gridColumns: RangeControl.closed(1, 48, 24), gridRowHeight: RangeControl.closed(4, 100, 8), gridRowCount: withDefault(NumberControl, DEFAULT_ROW_COUNT), - gridPaddingX: withDefault(NumberControl, 20), - gridPaddingY: withDefault(NumberControl, 20), + gridPaddingX: withDefault(NumberControl, 0), + gridPaddingY: withDefault(NumberControl, 0), gridBg: ColorControl, gridBgImage: StringControl, gridBgImageRepeat: StringControl, @@ -342,6 +342,10 @@ function AppGeneralSettingsModal(props: ChildrenInstance) { function AppCanvasSettingsModal(props: ChildrenInstance) { const isPublicApp = useSelector(isPublicApplication); + const application = useSelector(currentApplication); + const isAggregation = !!application && isAggregationApp( + AppUILayoutType[application.applicationType] + ); const { themeList, defaultTheme, @@ -397,7 +401,7 @@ function AppCanvasSettingsModal(props: ChildrenInstance) { return ( <> - {maxWidth.propertyView({ + {!isAggregation && maxWidth.propertyView({ dropdownLabel: trans("appSetting.canvasMaxWidth"), inputLabel: trans("appSetting.userDefinedMaxWidth"), inputPlaceholder: trans("appSetting.inputUserDefinedPxValue"), @@ -462,25 +466,25 @@ function AppCanvasSettingsModal(props: ChildrenInstance) { min: 350, lastNode: {trans("appSetting.maxWidthTip")}, })} - {gridColumns.propertyView({ + {!isAggregation && gridColumns.propertyView({ label: trans("appSetting.gridColumns"), placeholder: '24', })} - {gridRowHeight.propertyView({ + {!isAggregation && gridRowHeight.propertyView({ label: trans("appSetting.gridRowHeight"), placeholder: '8', })} - {gridRowCount.propertyView({ + {!isAggregation && gridRowCount.propertyView({ label: trans("appSetting.gridRowCount"), placeholder: 'Infinity', })} {gridPaddingX.propertyView({ label: trans("appSetting.gridPaddingX"), - placeholder: '20', + placeholder: '0', })} {gridPaddingY.propertyView({ label: trans("appSetting.gridPaddingY"), - placeholder: '20', + placeholder: '0', })} {gridBg.propertyView({ label: trans("style.background"), diff --git a/client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx index 8ae653ffa..bd4016c16 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/mobileTabLayout.tsx @@ -11,11 +11,11 @@ import { AppSelectComp } from "comps/comps/layout/appSelectComp"; import { NameAndExposingInfo } from "comps/utils/exposingTypes"; import { ConstructorToComp, ConstructorToDataType } from "lowcoder-core"; import { CanvasContainer } from "comps/comps/gridLayoutComp/canvasView"; -import { CanvasContainerID } from "constants/domLocators"; -import { PreviewContainerID } from "constants/domLocators"; +import { CanvasContainerID, PreviewContainerID } from "constants/domLocators"; import { EditorContainer, EmptyContent } from "pages/common/styledComponent"; import { Layers } from "constants/Layers"; import { ExternalEditorContext } from "util/context/ExternalEditorContext"; +import { EditorContext } from "comps/editorState"; import { default as Skeleton } from "antd/es/skeleton"; import { hiddenPropertyView } from "comps/utils/propertyUtils"; import { dropdownControl } from "@lowcoder-ee/comps/controls/dropdownControl"; @@ -47,6 +47,21 @@ const TabBarItem = React.lazy(() => ); const EventOptions = [clickEvent] as const; +/** Mobile nav editor: tab bar uses position:absolute bottom; this root is the containing block */ +const MobileNavCanvasRoot = styled(CanvasContainer)` + position: relative; +`; + +/** Strip shared EditorContainer defaults (16px padding + scrollbar-gutter: stable) for mobile nav */ +const MobileNavEditorContainer = styled(EditorContainer)` + padding: 0; + padding-right: 0; + scrollbar-gutter: auto; + overflow-x: auto; + overflow-y: auto; + background: transparent; +`; + const AppViewContainer = styled.div` position: absolute; width: 100%; @@ -221,17 +236,17 @@ const TabBarWrapper = styled.div<{ $readOnly: boolean, $canvasBg: string, $tabBarHeight: string, - $maxWidth: number, $verticalAlignment: string; }>` + box-sizing: border-box; max-width: inherit; background: ${(props) => (props.$canvasBg)}; margin: 0 auto; - position: fixed; + position: ${(props) => (props.$readOnly ? "fixed" : "absolute")}; bottom: 0; left: 0; right: 0; - width: ${(props) => props.$readOnly ? "100%" : `${props.$maxWidth - 30}px`}; + width: 100%; z-index: ${Layers.tabBar}; padding-bottom: env(safe-area-inset-bottom, 0); @@ -389,7 +404,6 @@ function convertTreeData(data: any) { function TabBarView(props: TabBarProps & { tabBarHeight: string; - maxWidth: number; verticalAlignment: string; showSeparator: boolean; navIconSize: string; @@ -404,7 +418,6 @@ function TabBarView(props: TabBarProps & { $readOnly={props.readOnly} $canvasBg={canvasBg} $tabBarHeight={props.tabBarHeight} - $maxWidth={props.maxWidth} $verticalAlignment={props.verticalAlignment} > { const bgColor = (useContext(ThemeContext)?.theme || defaultTheme).canvas; const onEvent = comp.children.onEvent.getView(); + // Pull app-level Theme / Canvas Settings (managed via the left-sidebar + // "Canvas" pane and shared with normal apps + modules). Mobile nav already + // owns its own maxWidth + grid behaviour, so we only consume the + // background + padding subset here. + const editorState = useContext(EditorContext); + const appSettings = editorState?.getAppSettings(); + const canvasBg = appSettings?.gridBg; + const canvasBgImage = appSettings?.gridBgImage; + const canvasBgImageRepeat = appSettings?.gridBgImageRepeat || "no-repeat"; + const canvasBgImageSize = appSettings?.gridBgImageSize || "cover"; + const canvasBgImagePosition = appSettings?.gridBgImagePosition || "center"; + const canvasBgImageOrigin = appSettings?.gridBgImageOrigin || "padding-box"; + const canvasPaddingX = appSettings?.gridPaddingX ?? 0; + const canvasPaddingY = appSettings?.gridPaddingY ?? 0; + + const canvasBackgroundStyle: React.CSSProperties = { + background: "#FFFFFF", + }; + if (canvasBg) { + canvasBackgroundStyle.background = canvasBg; + } + if (canvasBgImage) { + canvasBackgroundStyle.backgroundImage = `url('${canvasBgImage}')`; + canvasBackgroundStyle.backgroundRepeat = canvasBgImageRepeat; + canvasBackgroundStyle.backgroundSize = canvasBgImageSize; + canvasBackgroundStyle.backgroundPosition = canvasBgImagePosition; + canvasBackgroundStyle.backgroundOrigin = canvasBgImageOrigin; + } + const canvasContentPadding = `${canvasPaddingY}px ${canvasPaddingX}px`; + const getContainer = useCallback(() => document.querySelector(`#${PreviewContainerID}`) || document.querySelector(`#${CanvasContainerID}`) || @@ -702,7 +745,7 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => { currentTab.children.app.getView()) || ( ); } @@ -712,7 +755,7 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => { currentTab.children.action.getView()) || ( ) }, [tabIndex, tabViews, dataOptionType]); @@ -769,7 +812,6 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => { tabItemActiveStyle={navItemActiveStyle} tabBarHeight={tabBarHeight} navIconSize={navIconSize} - maxWidth={maxWidth} verticalAlignment={verticalAlignment} showSeparator={showSeparator} /> @@ -870,8 +912,12 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => { if (readOnly) { return ( - - {appView} + + {appView} {menuMode === MobileMode.Hamburger ? ( <> {hamburgerButton} @@ -885,8 +931,12 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => { } return ( - - {appView} + + {appView} {menuMode === MobileMode.Hamburger ? ( <> {hamburgerButton} @@ -895,7 +945,7 @@ MobileTabLayoutTmp = withViewFn(MobileTabLayoutTmp, (comp) => { ) : ( tabBarView )} - + ); }); diff --git a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx index 4a7e2b355..66f23635c 100644 --- a/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx +++ b/client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx @@ -6,6 +6,7 @@ import MainContent from "components/layout/MainContent"; import { LayoutMenuItemComp, LayoutMenuItemListComp } from "comps/comps/layout/layoutMenuItemComp"; import { menuPropertyView } from "comps/comps/navComp/components/MenuItemList"; import { registerLayoutMap } from "comps/comps/uiComp"; +import { EditorContext } from "comps/editorState"; import { MultiCompBuilder, withDefault, withViewFn } from "comps/generators"; import { withDispatchHook } from "comps/generators/withDispatchHook"; import { NameAndExposingInfo } from "comps/utils/exposingTypes"; @@ -14,7 +15,7 @@ import { TopHeaderHeight } from "constants/style"; import { Section, controlItem, sectionNames } from "lowcoder-design"; import { trans } from "i18n"; import { EditorContainer, EmptyContent } from "pages/common/styledComponent"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import styled from "styled-components"; import { isUserViewMode, useAppPathParam } from "util/hooks"; import { StringControl, jsonControl } from "comps/controls/codeControl"; @@ -381,6 +382,21 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { const dataOptionType = comp.children.dataOptionType.getView(); const onEvent = comp.children.onEvent.getView(); + // Pull app-level Theme / Canvas Settings (managed via the left-sidebar + // "Canvas" pane and shared with normal apps + modules). For aggregation + // apps the grid sizing fields are intentionally hidden in the settings UI; + // we only consume the background + padding subset here. + const editorState = useContext(EditorContext); + const appSettings = editorState?.getAppSettings(); + const canvasBg = appSettings?.gridBg; + const canvasBgImage = appSettings?.gridBgImage; + const canvasBgImageRepeat = appSettings?.gridBgImageRepeat || "no-repeat"; + const canvasBgImageSize = appSettings?.gridBgImageSize || "cover"; + const canvasBgImagePosition = appSettings?.gridBgImagePosition || "center"; + const canvasBgImageOrigin = appSettings?.gridBgImageOrigin || "padding-box"; + const canvasPaddingX = appSettings?.gridPaddingX ?? 0; + const canvasPaddingY = appSettings?.gridPaddingY ?? 0; + // filter out hidden. unauthorised items filtered by server const filterItem = useCallback((item: LayoutMenuItemComp): boolean => { return !item.children.hidden.getView(); @@ -685,8 +701,25 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { /> ); + // Build canvas background style (color + optional image), driven by the + // shared app-level Canvas Settings. + const canvasBackgroundStyle: React.CSSProperties = {}; + if (canvasBg) { + canvasBackgroundStyle.background = canvasBg; + } + if (canvasBgImage) { + canvasBackgroundStyle.backgroundImage = `url('${canvasBgImage}')`; + canvasBackgroundStyle.backgroundRepeat = canvasBgImageRepeat; + canvasBackgroundStyle.backgroundSize = canvasBgImageSize; + canvasBackgroundStyle.backgroundPosition = canvasBgImagePosition; + canvasBackgroundStyle.backgroundOrigin = canvasBgImageOrigin; + } + let content = ( - + {(navPosition === 'top') && (
{ navMenu } @@ -697,7 +730,15 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => { {navMenu} )} - {pageView} + + {pageView} + {(navPosition === 'bottom') && (