Skip to content

Commit ce51a67

Browse files
feat: styling option for nav layout
1 parent 8e39c18 commit ce51a67

File tree

4 files changed

+272
-27
lines changed

4 files changed

+272
-27
lines changed

client/packages/lowcoder/src/comps/comps/layout/navLayout.tsx

Lines changed: 196 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Layout, Menu as AntdMenu, MenuProps } from "antd";
1+
import { Layout, Menu as AntdMenu, MenuProps, Segmented } from "antd";
22
import MainContent from "components/layout/MainContent";
33
import { LayoutMenuItemComp, LayoutMenuItemListComp } from "comps/comps/layout/layoutMenuItemComp";
44
import { menuPropertyView } from "comps/comps/navComp/components/MenuItemList";
@@ -8,17 +8,47 @@ import { withDispatchHook } from "comps/generators/withDispatchHook";
88
import { NameAndExposingInfo } from "comps/utils/exposingTypes";
99
import { ALL_APPLICATIONS_URL } from "constants/routesURL";
1010
import { TopHeaderHeight } from "constants/style";
11-
import { Section, sectionNames } from "lowcoder-design";
11+
import { Section, controlItem, sectionNames } from "lowcoder-design";
1212
import { trans } from "i18n";
1313
import { EditorContainer, EmptyContent } from "pages/common/styledComponent";
1414
import { useCallback, useEffect, useMemo, useState } from "react";
15-
import styled from "styled-components";
15+
import styled, { css } from "styled-components";
1616
import { isUserViewMode, useAppPathParam } from "util/hooks";
1717
import { StringControl } from "comps/controls/codeControl";
1818
import { styleControl } from "comps/controls/styleControl";
19-
import { NavLayoutStyle } from "comps/controls/styleControlConstants";
19+
import {
20+
NavLayoutStyle,
21+
NavLayoutItemStyle,
22+
NavLayoutItemStyleType,
23+
NavLayoutItemHoverStyle,
24+
NavLayoutItemHoverStyleType,
25+
NavLayoutItemActiveStyle,
26+
NavLayoutItemActiveStyleType,
27+
} from "comps/controls/styleControlConstants";
28+
import { dropdownControl } from "comps/controls/dropdownControl";
2029

2130
const DEFAULT_WIDTH = 240;
31+
const ModeOptions = [
32+
{ label: trans("navLayout.modeInline"), value: "inline" },
33+
{ label: trans("navLayout.modeVertical"), value: "vertical" },
34+
] as const;
35+
36+
type MenuItemStyleOptionValue = "normal" | "hover" | "active";
37+
38+
const menuItemStyleOptions = [
39+
{
40+
value: "normal",
41+
label: "Normal",
42+
},
43+
{
44+
value: "hover",
45+
label: "Hover",
46+
},
47+
{
48+
value: "active",
49+
label: "Active",
50+
}
51+
]
2252

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

77+
const StyledMenu = styled(AntdMenu)<{
78+
$navItemStyle?: NavLayoutItemStyleType & { width: string},
79+
$navItemHoverStyle?: NavLayoutItemHoverStyleType,
80+
$navItemActiveStyle?: NavLayoutItemActiveStyleType,
81+
}>`
82+
.ant-menu-item {
83+
height: auto;
84+
width: ${(props) => props.$navItemStyle?.width};
85+
background-color: ${(props) => props.$navItemStyle?.background};
86+
color: ${(props) => props.$navItemStyle?.text};
87+
border-radius: ${(props) => props.$navItemStyle?.radius} !important;
88+
border: ${(props) => `1px solid ${props.$navItemStyle?.border}`};
89+
margin: ${(props) => props.$navItemStyle?.margin};
90+
padding: ${(props) => props.$navItemStyle?.padding};
91+
92+
}
93+
.ant-menu-item-active {
94+
background-color: ${(props) => props.$navItemHoverStyle?.background} !important;
95+
color: ${(props) => props.$navItemHoverStyle?.text} !important;
96+
border: ${(props) => `1px solid ${props.$navItemHoverStyle?.border}`};
97+
}
98+
99+
.ant-menu-item-selected {
100+
background-color: ${(props) => props.$navItemActiveStyle?.background} !important;
101+
color: ${(props) => props.$navItemActiveStyle?.text} !important;
102+
border: ${(props) => `1px solid ${props.$navItemActiveStyle?.border}`};
103+
}
104+
105+
.ant-menu-submenu {
106+
margin: ${(props) => props.$navItemStyle?.margin};
107+
width: ${(props) => props.$navItemStyle?.width};
108+
109+
.ant-menu-submenu-title {
110+
width: 100%;
111+
height: auto !important;
112+
background-color: ${(props) => props.$navItemStyle?.background};
113+
color: ${(props) => props.$navItemStyle?.text};
114+
border-radius: ${(props) => props.$navItemStyle?.radius} !important;
115+
border: ${(props) => `1px solid ${props.$navItemStyle?.border}`};
116+
margin: 0;
117+
padding: ${(props) => props.$navItemStyle?.padding};
118+
119+
}
120+
121+
.ant-menu-item {
122+
width: 100%;
123+
}
124+
125+
&.ant-menu-submenu-active {
126+
>.ant-menu-submenu-title {
127+
width: 100%;
128+
background-color: ${(props) => props.$navItemHoverStyle?.background} !important;
129+
color: ${(props) => props.$navItemHoverStyle?.text} !important;
130+
border: ${(props) => `1px solid ${props.$navItemHoverStyle?.border}`};
131+
}
132+
}
133+
&.ant-menu-submenu-selected {
134+
>.ant-menu-submenu-title {
135+
width: 100%;
136+
background-color: ${(props) => props.$navItemActiveStyle?.background} !important;
137+
color: ${(props) => props.$navItemActiveStyle?.text} !important;
138+
border: ${(props) => `1px solid ${props.$navItemActiveStyle?.border}`};
139+
}
140+
}
141+
}
142+
143+
`;
144+
145+
const defaultStyle = {
146+
radius: '0px',
147+
margin: '0px',
148+
padding: '0px',
149+
}
150+
47151
let NavTmpLayout = (function () {
48152
const childrenMap = {
49153
items: withDefault(LayoutMenuItemListComp, [
50154
{
51155
label: trans("menuItem") + " 1",
52156
},
53157
]),
54-
width: StringControl,
55-
style: styleControl(NavLayoutStyle),
158+
width: withDefault(StringControl, DEFAULT_WIDTH),
159+
mode: dropdownControl(ModeOptions, "inline"),
160+
navStyle: withDefault(styleControl(NavLayoutStyle), defaultStyle),
161+
navItemStyle: withDefault(styleControl(NavLayoutItemStyle), defaultStyle),
162+
navItemHoverStyle: withDefault(styleControl(NavLayoutItemHoverStyle), {}),
163+
navItemActiveStyle: withDefault(styleControl(NavLayoutItemActiveStyle), {}),
56164
};
57165
return new MultiCompBuilder(childrenMap, (props) => {
58166
return null;
59167
})
60168
.setPropertyViewFn((children) => {
169+
const [styleSegment, setStyleSegment] = useState('normal')
170+
61171
return (
62-
<>
172+
<div style={{overflowY: 'auto'}}>
63173
<Section name={trans("menu")}>{menuPropertyView(children.items)}</Section>
64174
<Section name={sectionNames.layout}>
65175
{ children.width.propertyView({
66-
label: trans("drawer.width"),
67-
tooltip: trans("drawer.widthTooltip"),
176+
label: trans("navLayout.width"),
177+
tooltip: trans("navLayout.widthTooltip"),
68178
placeholder: DEFAULT_WIDTH + "",
69-
})}
179+
})}
180+
{ children.mode.propertyView({
181+
label: trans("labelProp.position"),
182+
radioButton: true
183+
})}
70184
</Section>
71-
<Section name={sectionNames.style}>
72-
{ children.style.getPropertyView() }
185+
<Section name={trans("navLayout.navStyle")}>
186+
{ children.navStyle.getPropertyView() }
73187
</Section>
74-
</>
188+
<Section name={trans("navLayout.navItemStyle")}>
189+
{controlItem({}, (
190+
<Segmented
191+
block
192+
options={menuItemStyleOptions}
193+
value={styleSegment}
194+
// className="comp-panel-tab"
195+
onChange={(k) => setStyleSegment(k as MenuItemStyleOptionValue)}
196+
/>
197+
))}
198+
{styleSegment === 'normal' && (
199+
children.navItemStyle.getPropertyView()
200+
)}
201+
{styleSegment === 'hover' && (
202+
children.navItemHoverStyle.getPropertyView()
203+
)}
204+
{styleSegment === 'active' && (
205+
children.navItemActiveStyle.getPropertyView()
206+
)}
207+
</Section>
208+
</div>
75209
);
76210
})
77211
.build();
@@ -82,13 +216,19 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
82216
const isViewMode = isUserViewMode(pathParam);
83217
const [selectedKey, setSelectedKey] = useState("");
84218
const items = useMemo(() => comp.children.items.getView(), [comp.children.items]);
85-
219+
const navWidth = useMemo(() => comp.children.width.getView(), [comp.children.width]);
220+
const navMode = useMemo(() => comp.children.mode.getView(), [comp.children.mode]);
221+
const navStyle = useMemo(() => comp.children.navStyle.getView(), [comp.children.navStyle]);
222+
const navItemStyle = useMemo(() => comp.children.navItemStyle.getView(), [comp.children.navItemStyle]);
223+
const navItemHoverStyle = useMemo(() => comp.children.navItemHoverStyle.getView(), [comp.children.navItemHoverStyle]);
224+
const navItemActiveStyle = useMemo(() => comp.children.navItemActiveStyle.getView(), [comp.children.navItemActiveStyle]);
225+
console.log(navItemActiveStyle);
86226
// filter out hidden. unauthorised items filtered by server
87227
const filterItem = useCallback((item: LayoutMenuItemComp): boolean => {
88228
return !item.children.hidden.getView();
89229
}, []);
90230

91-
const generateItemKeyRecord = useCallback((items: LayoutMenuItemComp[]) => {
231+
const generateItemKeyRecord = (items: LayoutMenuItemComp[]) => {
92232
const result: Record<string, LayoutMenuItemComp> = {};
93233
items.forEach((item) => {
94234
const subItems = item.children.items.getView();
@@ -98,13 +238,13 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
98238
result[item.getItemKey()] = item;
99239
});
100240
return result;
101-
}, [items])
241+
}
102242

103243
const itemKeyRecord = useMemo(() => {
104244
return generateItemKeyRecord(items)
105-
}, [generateItemKeyRecord, items]);
245+
}, [items]);
106246

107-
const onMenuItemClick = ({key}: {key: string}) => {
247+
const onMenuItemClick = useCallback(({key}: {key: string}) => {
108248
const itemComp = itemKeyRecord[key];
109249
const url = [
110250
ALL_APPLICATIONS_URL,
@@ -113,7 +253,7 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
113253
itemComp.getItemKey(),
114254
].join("/");
115255
itemComp.children.action.act(url);
116-
}
256+
}, [pathParam.applicationId, pathParam.viewMode, itemKeyRecord])
117257

118258
const getMenuItem = useCallback(
119259
(itemComps: LayoutMenuItemComp[]): MenuProps["items"] => {
@@ -131,7 +271,7 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
131271
};
132272
});
133273
},
134-
[filterItem]
274+
[onMenuItemClick, filterItem]
135275
);
136276

137277
const menuItems = useMemo(() => getMenuItem(items), [items, getMenuItem]);
@@ -210,15 +350,47 @@ NavTmpLayout = withViewFn(NavTmpLayout, (comp) => {
210350
}
211351
}
212352

353+
const getVerticalMargin = (margin: string[]) => {
354+
if(margin.length === 1) return `${margin[0]}`;
355+
if(margin.length === 2) return `(${margin[0]} + ${margin[0]})`;
356+
if(margin.length === 3 || margin.length === 4)
357+
return `(${margin[0]} + ${margin[2]})`;
358+
359+
return '0px';
360+
}
361+
const getHorizontalMargin = (margin: string[]) => {
362+
if(margin.length === 1) return `(${margin[0]} + ${margin[0]})`;
363+
if(margin.length === 2) return `(${margin[1]} + ${margin[1]})`;
364+
if(margin.length === 3 || margin.length === 4)
365+
return `(${margin[1]} + ${margin[3]})`;
366+
367+
return '0px';
368+
}
369+
213370
let content = (
214371
<Layout>
215-
<StyledSide theme="light" width={DEFAULT_WIDTH}>
216-
<AntdMenu
372+
<StyledSide theme="light" width={navWidth}>
373+
<StyledMenu
217374
items={menuItems}
218-
mode="inline"
219-
style={{ height: "100%" }}
375+
mode={navMode}
376+
style={{
377+
height: `calc(100% - ${getVerticalMargin(navStyle.margin.split(' '))})`,
378+
width: `calc(100% - ${getHorizontalMargin(navStyle.margin.split(' '))})`,
379+
borderRadius: navStyle.radius,
380+
color: navStyle.text,
381+
margin: navStyle.margin,
382+
padding: navStyle.padding,
383+
background: navStyle.background,
384+
borderRight: `1px solid ${navStyle.border}`,
385+
}}
220386
defaultOpenKeys={defaultOpenKeys}
221387
selectedKeys={[selectedKey]}
388+
$navItemStyle={{
389+
width: `calc(100% - ${getHorizontalMargin(navItemStyle.margin.split(' '))})`,
390+
...navItemStyle
391+
}}
392+
$navItemHoverStyle={navItemHoverStyle}
393+
$navItemActiveStyle={navItemActiveStyle}
222394
/>
223395
</StyledSide>
224396
<MainContent>{pageView}</MainContent>

client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -821,9 +821,7 @@ export const TreeStyle = [
821821

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

824-
export const DrawerStyle = [getBackground()] as const;
825-
826-
export const NavLayoutStyle = [getBackground()] as const;
824+
export const DrawerStyle = [getBackground()] as const
827825

828826
export const JsonEditorStyle = [LABEL] as const;
829827

@@ -930,6 +928,59 @@ export const ResponsiveLayoutColStyle = [
930928
PADDING,
931929
] as const;
932930

931+
export const NavLayoutStyle = [
932+
...getBgBorderRadiusByBg(),
933+
{
934+
name: "text",
935+
label: trans("text"),
936+
depName: "background",
937+
// depTheme: "primary",
938+
depType: DEP_TYPE.CONTRAST_TEXT,
939+
transformer: contrastText,
940+
},
941+
MARGIN,
942+
PADDING,
943+
] as const;
944+
945+
export const NavLayoutItemStyle = [
946+
getBackground("primarySurface"),
947+
getStaticBorder('transparent'),
948+
RADIUS,
949+
{
950+
name: "text",
951+
label: trans("text"),
952+
depName: "background",
953+
depType: DEP_TYPE.CONTRAST_TEXT,
954+
transformer: contrastText,
955+
},
956+
MARGIN,
957+
PADDING,
958+
] as const;
959+
960+
export const NavLayoutItemHoverStyle = [
961+
getBackground("canvas"),
962+
getStaticBorder('transparent'),
963+
{
964+
name: "text",
965+
label: trans("text"),
966+
depName: "background",
967+
depType: DEP_TYPE.CONTRAST_TEXT,
968+
transformer: contrastText,
969+
},
970+
] as const;
971+
972+
export const NavLayoutItemActiveStyle = [
973+
getBackground("primary"),
974+
getStaticBorder('transparent'),
975+
{
976+
name: "text",
977+
label: trans("text"),
978+
depName: "background",
979+
depType: DEP_TYPE.CONTRAST_TEXT,
980+
transformer: contrastText,
981+
},
982+
] as const;
983+
933984
export const CarouselStyle = [getBackground("canvas")] as const;
934985

935986
export const RichTextEditorStyle = [getStaticBorder(), RADIUS] as const;
@@ -970,6 +1021,10 @@ export type CarouselStyleType = StyleConfigType<typeof CarouselStyle>;
9701021
export type RichTextEditorStyleType = StyleConfigType<typeof RichTextEditorStyle>;
9711022
export type ResponsiveLayoutRowStyleType = StyleConfigType<typeof ResponsiveLayoutRowStyle>;
9721023
export type ResponsiveLayoutColStyleType = StyleConfigType<typeof ResponsiveLayoutColStyle>;
1024+
export type NavLayoutStyleType = StyleConfigType<typeof NavLayoutStyle>;
1025+
export type NavLayoutItemStyleType = StyleConfigType<typeof NavLayoutItemStyle>;
1026+
export type NavLayoutItemHoverStyleType = StyleConfigType<typeof NavLayoutItemHoverStyle>;
1027+
export type NavLayoutItemActiveStyleType = StyleConfigType<typeof NavLayoutItemActiveStyle>;
9731028

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

0 commit comments

Comments
 (0)