Skip to content

Nav Layout Fixes/Updates #510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 20, 2023
Merged
Prev Previous commit
Next Next commit
feat: styling option for nav layout
  • Loading branch information
raheeliftikhar5 committed Nov 20, 2023
commit ce51a678e7a27e5c33e96af6ad9339086c3b1c78
220 changes: 196 additions & 24 deletions client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Layout, Menu as AntdMenu, MenuProps } from "antd";
import { Layout, Menu as AntdMenu, MenuProps, Segmented } from "antd";
import MainContent from "components/layout/MainContent";
import { LayoutMenuItemComp, LayoutMenuItemListComp } from "comps/comps/layout/layoutMenuItemComp";
import { menuPropertyView } from "comps/comps/navComp/components/MenuItemList";
Expand All @@ -8,17 +8,47 @@ import { withDispatchHook } from "comps/generators/withDispatchHook";
import { NameAndExposingInfo } from "comps/utils/exposingTypes";
import { ALL_APPLICATIONS_URL } from "constants/routesURL";
import { TopHeaderHeight } from "constants/style";
import { Section, sectionNames } from "lowcoder-design";
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 styled from "styled-components";
import styled, { css } from "styled-components";
import { isUserViewMode, useAppPathParam } from "util/hooks";
import { StringControl } from "comps/controls/codeControl";
import { styleControl } from "comps/controls/styleControl";
import { NavLayoutStyle } from "comps/controls/styleControlConstants";
import {
NavLayoutStyle,
NavLayoutItemStyle,
NavLayoutItemStyleType,
NavLayoutItemHoverStyle,
NavLayoutItemHoverStyleType,
NavLayoutItemActiveStyle,
NavLayoutItemActiveStyleType,
} from "comps/controls/styleControlConstants";
import { dropdownControl } from "comps/controls/dropdownControl";

const DEFAULT_WIDTH = 240;
const ModeOptions = [
{ label: trans("navLayout.modeInline"), value: "inline" },
{ label: trans("navLayout.modeVertical"), value: "vertical" },
] as const;

type MenuItemStyleOptionValue = "normal" | "hover" | "active";

const menuItemStyleOptions = [
{
value: "normal",
label: "Normal",
},
{
value: "hover",
label: "Hover",
},
{
value: "active",
label: "Active",
}
]

const StyledSide = styled(Layout.Sider)`
max-height: calc(100vh - ${TopHeaderHeight});
Expand All @@ -44,34 +74,138 @@ const ContentWrapper = styled.div`
}
`;

const StyledMenu = styled(AntdMenu)<{
$navItemStyle?: NavLayoutItemStyleType & { width: string},
$navItemHoverStyle?: NavLayoutItemHoverStyleType,
$navItemActiveStyle?: NavLayoutItemActiveStyleType,
}>`
.ant-menu-item {
height: auto;
width: ${(props) => props.$navItemStyle?.width};
background-color: ${(props) => props.$navItemStyle?.background};
color: ${(props) => props.$navItemStyle?.text};
border-radius: ${(props) => props.$navItemStyle?.radius} !important;
border: ${(props) => `1px solid ${props.$navItemStyle?.border}`};
margin: ${(props) => props.$navItemStyle?.margin};
padding: ${(props) => props.$navItemStyle?.padding};

}
.ant-menu-item-active {
background-color: ${(props) => props.$navItemHoverStyle?.background} !important;
color: ${(props) => props.$navItemHoverStyle?.text} !important;
border: ${(props) => `1px solid ${props.$navItemHoverStyle?.border}`};
}

.ant-menu-item-selected {
background-color: ${(props) => props.$navItemActiveStyle?.background} !important;
color: ${(props) => props.$navItemActiveStyle?.text} !important;
border: ${(props) => `1px solid ${props.$navItemActiveStyle?.border}`};
}

.ant-menu-submenu {
margin: ${(props) => props.$navItemStyle?.margin};
width: ${(props) => props.$navItemStyle?.width};

.ant-menu-submenu-title {
width: 100%;
height: auto !important;
background-color: ${(props) => props.$navItemStyle?.background};
color: ${(props) => props.$navItemStyle?.text};
border-radius: ${(props) => props.$navItemStyle?.radius} !important;
border: ${(props) => `1px solid ${props.$navItemStyle?.border}`};
margin: 0;
padding: ${(props) => props.$navItemStyle?.padding};

}

.ant-menu-item {
width: 100%;
}

&.ant-menu-submenu-active {
>.ant-menu-submenu-title {
width: 100%;
background-color: ${(props) => props.$navItemHoverStyle?.background} !important;
color: ${(props) => props.$navItemHoverStyle?.text} !important;
border: ${(props) => `1px solid ${props.$navItemHoverStyle?.border}`};
}
}
&.ant-menu-submenu-selected {
>.ant-menu-submenu-title {
width: 100%;
background-color: ${(props) => props.$navItemActiveStyle?.background} !important;
color: ${(props) => props.$navItemActiveStyle?.text} !important;
border: ${(props) => `1px solid ${props.$navItemActiveStyle?.border}`};
}
}
}

`;

const defaultStyle = {
radius: '0px',
margin: '0px',
padding: '0px',
}

let NavTmpLayout = (function () {
const childrenMap = {
items: withDefault(LayoutMenuItemListComp, [
{
label: trans("menuItem") + " 1",
},
]),
width: StringControl,
style: styleControl(NavLayoutStyle),
width: withDefault(StringControl, DEFAULT_WIDTH),
mode: dropdownControl(ModeOptions, "inline"),
navStyle: withDefault(styleControl(NavLayoutStyle), defaultStyle),
navItemStyle: withDefault(styleControl(NavLayoutItemStyle), defaultStyle),
navItemHoverStyle: withDefault(styleControl(NavLayoutItemHoverStyle), {}),
navItemActiveStyle: withDefault(styleControl(NavLayoutItemActiveStyle), {}),
};
return new MultiCompBuilder(childrenMap, (props) => {
return null;
})
.setPropertyViewFn((children) => {
const [styleSegment, setStyleSegment] = useState('normal')

return (
<>
<div style={{overflowY: 'auto'}}>
<Section name={trans("menu")}>{menuPropertyView(children.items)}</Section>
<Section name={sectionNames.layout}>
{ children.width.propertyView({
label: trans("drawer.width"),
tooltip: trans("drawer.widthTooltip"),
label: trans("navLayout.width"),
tooltip: trans("navLayout.widthTooltip"),
placeholder: DEFAULT_WIDTH + "",
})}
})}
{ children.mode.propertyView({
label: trans("labelProp.position"),
radioButton: true
})}
</Section>
<Section name={sectionNames.style}>
{ children.style.getPropertyView() }
<Section name={trans("navLayout.navStyle")}>
{ children.navStyle.getPropertyView() }
</Section>
</>
<Section name={trans("navLayout.navItemStyle")}>
{controlItem({}, (
<Segmented
block
options={menuItemStyleOptions}
value={styleSegment}
// className="comp-panel-tab"
onChange={(k) => setStyleSegment(k as MenuItemStyleOptionValue)}
/>
))}
{styleSegment === 'normal' && (
children.navItemStyle.getPropertyView()
)}
{styleSegment === 'hover' && (
children.navItemHoverStyle.getPropertyView()
)}
{styleSegment === 'active' && (
children.navItemActiveStyle.getPropertyView()
)}
</Section>
</div>
);
})
.build();
Expand All @@ -82,13 +216,19 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
const isViewMode = isUserViewMode(pathParam);
const [selectedKey, setSelectedKey] = useState("");
const items = useMemo(() => comp.children.items.getView(), [comp.children.items]);

const navWidth = useMemo(() => comp.children.width.getView(), [comp.children.width]);
const navMode = useMemo(() => comp.children.mode.getView(), [comp.children.mode]);
const navStyle = useMemo(() => comp.children.navStyle.getView(), [comp.children.navStyle]);
const navItemStyle = useMemo(() => comp.children.navItemStyle.getView(), [comp.children.navItemStyle]);
const navItemHoverStyle = useMemo(() => comp.children.navItemHoverStyle.getView(), [comp.children.navItemHoverStyle]);
const navItemActiveStyle = useMemo(() => comp.children.navItemActiveStyle.getView(), [comp.children.navItemActiveStyle]);
console.log(navItemActiveStyle);
// filter out hidden. unauthorised items filtered by server
const filterItem = useCallback((item: LayoutMenuItemComp): boolean => {
return !item.children.hidden.getView();
}, []);

const generateItemKeyRecord = useCallback((items: LayoutMenuItemComp[]) => {
const generateItemKeyRecord = (items: LayoutMenuItemComp[]) => {
const result: Record<string, LayoutMenuItemComp> = {};
items.forEach((item) => {
const subItems = item.children.items.getView();
Expand All @@ -98,13 +238,13 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
result[item.getItemKey()] = item;
});
return result;
}, [items])
}

const itemKeyRecord = useMemo(() => {
return generateItemKeyRecord(items)
}, [generateItemKeyRecord, items]);
}, [items]);

const onMenuItemClick = ({key}: {key: string}) => {
const onMenuItemClick = useCallback(({key}: {key: string}) => {
const itemComp = itemKeyRecord[key];
const url = [
ALL_APPLICATIONS_URL,
Expand All @@ -113,7 +253,7 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
itemComp.getItemKey(),
].join("/");
itemComp.children.action.act(url);
}
}, [pathParam.applicationId, pathParam.viewMode, itemKeyRecord])

const getMenuItem = useCallback(
(itemComps: LayoutMenuItemComp[]): MenuProps["items"] => {
Expand All @@ -131,7 +271,7 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
};
});
},
[filterItem]
[onMenuItemClick, filterItem]
);

const menuItems = useMemo(() => getMenuItem(items), [items, getMenuItem]);
Expand Down Expand Up @@ -210,15 +350,47 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
}
}

const getVerticalMargin = (margin: string[]) => {
if(margin.length === 1) return `${margin[0]}`;
if(margin.length === 2) return `(${margin[0]} + ${margin[0]})`;
if(margin.length === 3 || margin.length === 4)
return `(${margin[0]} + ${margin[2]})`;

return '0px';
}
const getHorizontalMargin = (margin: string[]) => {
if(margin.length === 1) return `(${margin[0]} + ${margin[0]})`;
if(margin.length === 2) return `(${margin[1]} + ${margin[1]})`;
if(margin.length === 3 || margin.length === 4)
return `(${margin[1]} + ${margin[3]})`;

return '0px';
}

let content = (
<Layout>
<StyledSide theme="light" width={DEFAULT_WIDTH}>
<AntdMenu
<StyledSide theme="light" width={navWidth}>
<StyledMenu
items={menuItems}
mode="inline"
style={{ height: "100%" }}
mode={navMode}
style={{
height: `calc(100% - ${getVerticalMargin(navStyle.margin.split(' '))})`,
width: `calc(100% - ${getHorizontalMargin(navStyle.margin.split(' '))})`,
borderRadius: navStyle.radius,
color: navStyle.text,
margin: navStyle.margin,
padding: navStyle.padding,
background: navStyle.background,
borderRight: `1px solid ${navStyle.border}`,
}}
defaultOpenKeys={defaultOpenKeys}
selectedKeys={[selectedKey]}
$navItemStyle={{
width: `calc(100% - ${getHorizontalMargin(navItemStyle.margin.split(' '))})`,
...navItemStyle
}}
$navItemHoverStyle={navItemHoverStyle}
$navItemActiveStyle={navItemActiveStyle}
/>
</StyledSide>
<MainContent>{pageView}</MainContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -821,9 +821,7 @@ export const TreeStyle = [

export const TreeSelectStyle = [...multiSelectCommon, ...ACCENT_VALIDATE] as const;

export const DrawerStyle = [getBackground()] as const;

export const NavLayoutStyle = [getBackground()] as const;
export const DrawerStyle = [getBackground()] as const

export const JsonEditorStyle = [LABEL] as const;

Expand Down Expand Up @@ -930,6 +928,59 @@ export const ResponsiveLayoutColStyle = [
PADDING,
] as const;

export const NavLayoutStyle = [
...getBgBorderRadiusByBg(),
{
name: "text",
label: trans("text"),
depName: "background",
// depTheme: "primary",
depType: DEP_TYPE.CONTRAST_TEXT,
transformer: contrastText,
},
MARGIN,
PADDING,
] as const;

export const NavLayoutItemStyle = [
getBackground("primarySurface"),
getStaticBorder('transparent'),
RADIUS,
{
name: "text",
label: trans("text"),
depName: "background",
depType: DEP_TYPE.CONTRAST_TEXT,
transformer: contrastText,
},
MARGIN,
PADDING,
] as const;

export const NavLayoutItemHoverStyle = [
getBackground("canvas"),
getStaticBorder('transparent'),
{
name: "text",
label: trans("text"),
depName: "background",
depType: DEP_TYPE.CONTRAST_TEXT,
transformer: contrastText,
},
] as const;

export const NavLayoutItemActiveStyle = [
getBackground("primary"),
getStaticBorder('transparent'),
{
name: "text",
label: trans("text"),
depName: "background",
depType: DEP_TYPE.CONTRAST_TEXT,
transformer: contrastText,
},
] as const;

export const CarouselStyle = [getBackground("canvas")] as const;

export const RichTextEditorStyle = [getStaticBorder(), RADIUS] as const;
Expand Down Expand Up @@ -970,6 +1021,10 @@ export type CarouselStyleType = StyleConfigType<typeof CarouselStyle>;
export type RichTextEditorStyleType = StyleConfigType<typeof RichTextEditorStyle>;
export type ResponsiveLayoutRowStyleType = StyleConfigType<typeof ResponsiveLayoutRowStyle>;
export type ResponsiveLayoutColStyleType = StyleConfigType<typeof ResponsiveLayoutColStyle>;
export type NavLayoutStyleType = StyleConfigType<typeof NavLayoutStyle>;
export type NavLayoutItemStyleType = StyleConfigType<typeof NavLayoutItemStyle>;
export type NavLayoutItemHoverStyleType = StyleConfigType<typeof NavLayoutItemHoverStyle>;
export type NavLayoutItemActiveStyleType = StyleConfigType<typeof NavLayoutItemActiveStyle>;

export function widthCalculator(margin: string) {
const marginArr = margin?.trim().replace(/\s+/g,' ').split(" ") || "";
Expand Down
Loading